Process Injection
Malware
Last updated
Malware
Last updated
Bài toán đặt ra là trường hợp chúng ta đã chiếm quyền điều khiển Victim thành công (RCE), tuy nhiên cần duy trì sự hiện diện thường xuyên, đồng thời che mắt AntiVirus và đội ngũ Blue Team. Trong quá trình hoạt động thông thường, nạn nhân có thể đóng ứng dụng hay mã độc bị phát hiện và bị xóa đi. Do đó chúng ta cần quá trình process injection
Để kéo dài tuổi thọ của Malware, chúng ta có thể "anh hùng núp" trong một process hợp pháp.Một trong những process như vậy là explorer.exe.
explorer.exe luôn là một mục tiêu lý tưởng để injection. Đơn giản nó luôn tồn tại và không thoát ra cho đến khi người dùng đăng xuất.
Theo định nghĩa, một process là một container được tạo ra để chứa một ứng dụng đang chạy. Mỗi tiến trình Windows thì duy trì một "virtual memory space" của riêng nó. Đồng thời chúng ta có thể tương tác với nó thông qua Win32 API
Về tổng quan, chúng ta có thể bắt đầu process injection bằng cách mở một channel từ process này sang process khác thông qua hàm OpenProcess
- sau đó chúng ta sẽ sử đổi không gian bộ nhớ thông qua hàm VirtualAllocEx
và WriteProcessMemory
và cuối cùng thực thi bên trong process với CreateRemoteThread
Đầu tiên, để có thể can thiệp vào một process - chúng ta cần có quyền làm điều đó với tham số check dwDesiredAccess
Mở notepad
với user thông thường - tiến trình notepad.exe khởi chạy
Kiểm tra tiến hình với process_dump (https://docs.microsoft.com/en-us/sysinternals/downloads/process-explorer)
Kiểm tra khả năng control của tài khoản với process này
Ta có thể thấy rằng, Notepad được bảo mật ở mức độ trung bình, đồng thời tài khoản user có quyền đọc và ghi với process này.
Mở notepad
với user admin
Khi này ta thấy mức độ bảo mật đã được nâng lên mức HIGH
và user thường không còn quyền truy cập vào process này nữa
Như vậy ta chỉ có thể injection vào process đang chạy ở cùng một mức độ toàn vẹn hoặc thấp hơn.
Quay lại function sau đây
bInheritHandle
xác định xem return có thể được kế thừa bởi một chương trình con hay không (BOOL)
dwProcessId
chỉ định ID process cần injection
Sau khi OpenProcess() để xác định process, chúng ta sử dụng VirtualAllocEx() để cấp phát bộ nhớ - mở rộng không gian để chứa shellcode - của chúng ta
Tiếp theo là WriteProcessMemory
cho phép chúng ta injection shellcode vào process.
và CreateRemoteThread
để run process
Gọi API Win32
Quay lại quá trình Process Injection ta cơ bản có 4 quá trình như sau
Bước 1 . Gọi OpenProcess để xác định process cần injection
Bước 2. Mở rộng không gian chứa shell code với VirtualAllocEx
Bước 3. Ghi shellcode vào process với WriteProcessMemory
Bước 4. Thực thi procees với CreateRemoteThread
Vậy để có thể thực hiện hành công quá trình process injection ta cần lần lượt gọi các API : OpenProcess , VirtualAllocEx , WriteProcessMemory , CreateRemoteThread sau đó thực thi chúng với các parameter thích hợp
Sử dụng Win32 API
Đầu tiên là API OpenProcess . Tra cứu trên tài liệu của Microsoft:
Tham số dwDesiredAccess là quyền truy cập mà chúng ta muốn có được cho process . Giá trị của nó sẽ được kiểm tra dựa trên "security descriptor" (2 giá trị này phải tương thích với nhau). Ở đây ra set quyền PROCESS_ALL_ACCESS
. Quyền này sẽ cung cấp cho chúng ta truy cập đầy đủ vào process
Tham số bInheritHandle cho chúng ta quyết định xem , liệu một child process đã tạo, có thể kế thừa xử lý này hay không.
Tham số dwProcessId cho ID của process
Tiếp theo, chúng ta tiến hành bước cấp phát bộ nhớ với VirtualAllocEx, tra cứu tài liệu API ta có các dùng như sau
Tham số hProcess chính là process handle
Tham số lpAddress là địa chỉ mong muốn mà ta muốn cấp phát cho remote process. Nếu API thành công,Buffer mới sẽ được cấp phát với một địa chỉ ban đầu như được cung cấp trong lpAddress . Tuy nhiên nếu giá trị này đã được cấp phát thì API sẽ không gọi thành công . Cho nên tốt nhất để giá trị null, API sẽ sử dụng một địa chỉ không sử dụng lpAddress = IntPtr.Zero
Quá trình này giống như khi ta sử dụng IP static trong mạng LAN. Nếu IP đã được dùng rồi, kết nối sẽ không thành công. Thay vì đó chúng ta sử dụng DHCP để tự động cấp các IP đang không được sử dụng
3 tham số dwSize, flAllocationType, flProtect chỉ định : Kích thước phân bổ mong muốn , Kiểu cấp phát , Bảo vệ bộ nhớ
Tiếp đến ta tiến hành sao chép Shellcode vào memory space của explorer.exe thông qua WriteProcessMemory
Tra cứu tài liệu Microsoft ta có thông số như sau
Tham số hProcess như bên trên
Tham số lpBaseAddress là địa chỉ bộ nhớ mới được cấp phát trong process
Tham số lpBuffer địa chỉ mảng byte chứa shellcode
Tham số nSize là kích thước của shellcode sẽ được sao chép
Tham số lpNumberOfBytesWritten để output ra bao nhiêu dữ liệu đã được sao chép
Tiếp đến ta thực thi shellcode thông qua CreateRemoteThread
Tra cứu tài liệu Microsoft ra có
Ở đây ta thấy có nhiều tham số, nhưng ta không cần phải gọi hết ,
Tham số hProcess như trên
Tham số lpThreadAttributes mô tả bảo mật mong muốn của thread mới (mặc định là NULL)
Tham số dwStackSize set kích thước Slack (Mặc định đặt là 0)
Tham số lpStartAddress chỉ định địa chỉ bắt đầu của thread, trong trường hợp của chúng ta, nó phải bắt đầu bằng địa chỉ buffer mà chúng ta đã cấp phát bên trên
Tham số lpParameter là một con trỏ tới các biến sẽ được chuyển đến hàm lpStartAddress . Vì Shellcode của chúng ta không có bất kỳ options nào, nên đặt một giá trị NULL tại đây
Hai tham số dwCreationFlags và lpThreadId ta bỏ qua
Process Injection cho phép chúng ta đưa shellcode vào một process và thực thi nó. Điều này là khá hiệu quả, tuy nhiên với những codebases lớn hơn hoặc tồn tại DLL từ trước. Chúng ta có thể đưa toàn bộ DLL vào một process thay vì chỉ shellcode đơn thuần
Khi một process cần sử dụng một API từ DLL , nó sẽ gọi tới LoadLibrary
API để load nó vảo virtual memory space. Trong trường hợp của chúng ta, chúng ta muốn process sẽ load DLL độc hại thông qua các API Win32.
Tuy nhiên , LoadLibrary
không thể được load trên một remote proces, vì vậy chúng ta phải thực hiện số một thủ thuật bắt buộc để buộc một process như explorer.exe
load DLL của chúng ta.
Kiểm tra hàm LoadLibrary
trên Microsoft ta có
Ta thấy hàm chỉ yêu cầu duy nhất một tham số là "lpLibFileName" : Tên của DLL để load Nhiều API Win32 có hai biến thể với hậu tố "A" hoặc "W". Trong trường hợp này nó sẽ là LoadLibraryA. Nhưng chức năng không thay đổi
Cách tiếp cận của chúng ta sẽ cố gắng đánh lừa process thực thi LoadLibrary
với đối số chính xác. Quay lại hàm CreateRemoteThread
Chúng ta quan tâm tới 2 tham số
lpStartAddress : Địa chỉ bắt đầu của hàm chạy trong thread mới
lpParameter: Địa chỉ bộ nhớ của buffer chứa tham số cho hàm đó (Shellcode là IntPtr.Zero do shell thì không có tham số)
Để cung cấp đối số chính xác (DLL độc hại), chúng ta phải cấp phát một "vùng" bên trong process , sau đó copy tên và đường dẫn của DLL vào đó . Địa chỉ của "vùng này" được cung cấp làm đối số lpParameter
Tuy nhiên, có một số hạn chế mà chúng ta sẽ phải xem xét.
Thứ nhất : DLL phải viết bằng C/C++ và phải Unmanaged. DLL được viết trên C# được managed sẽ không hoạt động. Đơn giản chúng ta không thể load DLL managed vào một Process Unmanaged
Tất cả các code mà được viết trong C# đều được trực tiếp thực thi bằng CLR(Common Language Runtime) mà chúng ta không thể can thiệp. CLR giúp quản lý bộ nhớ, xử lý các vấn đề bảo mật và xử lý các biến "rác".
Khắc phục điều này, chúng ta sẽ sử dụng msfvenom để tạo mã
Thứ 2 Thông thường, các DLL chứa các API sẽ được gọi sau khi DLL được load. Để gọi các API này, các ứng dụng phải phân giải tên của của chúng thành memory addresses với hàm GetProcAddress
. Tuy nhiên hàm này lại không thể phân giải API với dạng remote process (dạng ta tiêm vào rồi thực thi). Cho nên ta phải tạo những file DLL độc hại non-standard
À chút nữa quên, LoadLibrary chỉ chấp nhận các DLL tồn tại trên Disk. Do đó nó dễ bị phát hiện bởi AV và Blue Team
Có 2 khái niệm chúng ta dễ nhầm lẫn đó là DLL Injection vào DLL Hijacking. Đây là 2 khái niệm hoàn toàn khác nhau. Nếu như DLL injection can thiệp vào process, ép nó phải chạy DLL chúng ta mong muốn thì DLL Hijacking là quá trình tìm kiếm những file DLL có quyền bảo mật yếu - sau tiến hành thay thế nó với DLL độc hại
Thông thường, để đánh cắp thông tin đăng nhập RDP hay bất kỳ thông tin nào khác trên máy nạn nhân. Ta có thể nghĩ ngay tới Keyloggers. Tuy nhiên Keyloggers có một điểm yếu chí mạng là nó ghi lại toàn bộ thao tác gõ của người dùng. Khiến khi nhận được nội dung file, ta không thể phân biệt được thông tin nào là thông tin cần dùng để đăng nhập RDP. Module này sẽ hướng dẫn cho chúng ta chuyên biệt hóa nội dung này.
Khi người dùng sử dụng Remote Desktop với mstsc.exe
, họ nhập thông tin xác thực dạng clear-text vào ứng dụng. Bằng cách chèn các DLL độc hại vào đây ("mstsc.exe
") ta có thể ghi lại thao tác gõ của người sử dụng . DLL độc hại mang tên RdpThief
RdpThief được viết dưới dạng DLL unmanaged và được đưa vào mstsc.exe
trước khi ngươi dùng nhập thông tin đăng nhập
Quy trở lại mã DLL injection phía trên
Ta nhớ lại chức năng của đoạn code này là Injection malware.dll được tạo ra từ msfvenom cho pocess
explorer.exe
. Vậy mã sửa đổi ta sẽ thay thế với chức năng injection "RdpThief DLL" vào "mstsc" là xong
Ta tiến hành injection với code sau
Khác với mã trên, yêu cầu nạn nhân phải download DLL về rồi mới LoadlibaryA. Lần này ta tiến hành trỏ thẳng vào DLL độc hại.
Tuy nhiên ta vẫn còn chút lấn cấn ở đây.
Người dùng phải chạy remote desktop trước, sau đó chúng ta phải chạy malware để injection vào process (vì nếu người dùng không chạy trước, thì làm gì có PID để mà injection 😗)
Vì không biết khi nào người dùng khởi chạy mstsc.exe
để khởi chạy mã độc hại của chúng ta trước khi họ nhập thông tin vào. Khó hơn đánh đề !!!
Để cải thiện điều này, chúng ta có thể sử dụng vòng lặp while
tự động phát hiện khi có một tiến trình mstsc.exe
được khởi tạo và sau đó injection trước khi người dùng nhập thông tin đăng nhập của mình vào. Chúng ta cũng sử dụng Sleep để tạm dừng 1s giữa mỗi lần lặp
Code hoàn chỉnh sẽ là
Sau khi chúng ta thực thi "mã độc", nó sẽ phát hiện bất cứ session nào đang chạy của mstsc
và đưa DLL độc hại vào ứng dụng trước khi người dùng nhập thông tin đăng nhập.
Sử dụng Sysmon Event ID 8 với trường StartFunction là LoadLibraryA/W. Điều này cho thấy một thread mới đã được tạo để thực thi hàm LoadLibrary
, điều này thường được sử dụng để load một DLL vào quy trình đích .
Phát hiện thông qua Sysmon Event ID 8 nếu trường StartModule hoặc StartFunction bằng 0, đây là dấu hiệu của việc tạo thread để thực thi shellcode trong quy trình đích.
Kiểm tra quy trình có trường StartAddress của một thread không thuộc bất cứ module nào, có thể sử dụng công cụ CheckInject.exe -inject để phát hiện .
Monitor các dấu hiệu bất thường về cặp parent-child process và command line.
Hunting forensic bằng cách so sánh dữ liệu trên disk và trong memory, sử dụng công cụ như Volatility hoặc HollowHunter.exe .
Trace Stack của từng thread trong mỗi quy trình để tìm địa chỉ thực thi mà không thuộc vùng memory của module nào, đây là một phương pháp hunting tiên tiến .
Lưu ý rằng HollowHunter không thể phát hiện được hình thức inject này .
Các nhà phân tích cũng có thể sử dụng memory forensics để phát hiện và phân tích các hoạt động đáng ngờ, bao gồm việc sử dụng fileless malware. Công cụ như procdump, strings, windbg, và notepad++ có thể cung cấp thông tin quý giá từ dump process, từ đó phát hiện các thông tin như C&C server IOC một cách NHANH CHÓNG .
Nếu inject vào program x64 -> phải compile malware bằng compiler x64
Tắt AV hoặc cho vào exception
Kế thừa (inherit): Nếu process mà chúng ta đang dùng hàm Open tới đẻ ra process con thì mình có muốn có quyền điều khiển process đó k
"Page is a contiguous block of memory" nghĩa là một trang (page) là một khối bộ nhớ liên tục, không bị gián đoạn, trong không gian địa chỉ ảo hoặc vật lý của máy tính. Trong quản lý bộ nhớ của hệ điều hành, bộ nhớ được chia thành các đơn vị cố định gọi là "trang bộ nhớ" (memory pages).
Hãy cùng phân tích ý nghĩa và ứng dụng của khái niệm này:
Ý nghĩa
Liên tục (Contiguous): Mỗi trang bộ nhớ được cấp phát là một dãy liên tục các byte trong bộ nhớ. Nếu một trang bộ nhớ có kích thước là 4KB, điều này có nghĩa là 4096 byte liên tiếp trong không gian địa chỉ bộ nhớ được dành riêng cho trang đó.
Block of Memory: Trang bộ nhớ được xem như một "khối" hoặc một đơn vị của bộ nhớ. Hệ thống sẽ xử lý, quản lý, và ánh xạ các trang này từ bộ nhớ ảo sang bộ nhớ vật lý một cách độc lập với nhau.
Ứng dụng
Quản lý bộ nhớ: Việc chia bộ nhớ thành các trang giúp hệ điều hành quản lý bộ nhớ một cách hiệu quả hơn, bao gồm việc cấp phát, giải phóng và ánh xạ giữa bộ nhớ ảo và bộ nhớ vật lý.
Bảo vệ bộ nhớ: Hệ điều hành có thể thiết lập các quyền truy cập khác nhau cho từng trang bộ nhớ, như chỉ đọc, ghi, hoặc thực thi, giúp tăng cường bảo mật và ổn định cho hệ thống.
Phân trang (Paging): Phương pháp quản lý bộ nhớ này cho phép hệ điều hành sử dụng đĩa cứng để mở rộng bộ nhớ vật lý khi cần, thông qua kỹ thuật gọi là "phân trang" (paging). Khi không gian bộ nhớ vật lý không đủ, hệ điều hành có thể chuyển một số trang ra đĩa cứng (swap out) và sau đó đưa chúng trở lại bộ nhớ vật lý khi cần (swap in).
Ví dụ, trong một hệ thống có kích thước trang là 4KB, khi một ứng dụng yêu cầu bộ nhớ, hệ điều hành sẽ cấp phát một hoặc nhiều trang bộ nhớ 4KB cho ứng dụng đó. Mỗi trang này sẽ bao gồm một dãy liên tục 4KB trong không gian địa chỉ, cho dù đó là trong bộ nhớ ảo hay bộ nhớ vật lý.
VirtualAllocEx: https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex
WriteProcessMemory: https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory
CreateRemoteThreadEx: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createremotethreadex
DllMain: https://learn.microsoft.com/en-us/windows/win32/dlls/dllmain
LoadLibraryW: https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw
rundll32.exe .\MessageBoxDll.dll,DllMain
GetModuleHandleW: https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlew
Sửa shellcode phải giữ nguyên độ dài bằng cách thêm null bytes
© 2024,Pham Quoc Trung. All rights reserved.