Đây là phương pháp thực thi mã nhằm tránh bị phát hiện dựa vào việc chạy thread dưới process hợp lệ của hệ điều hành.
Đây là phương thức cổ điển tạo thread trên target process để thực thi LoadLibrary()
với tham số là đường dẫn của dll.
Step1: Mình sẽ lấy 2 tham số đầu vào là đường dẫn và process ID của target process ta muốn inject. Sử dụng OpenProcess
lấy process handle để tương tác với target process.
Step2: Cấp phát và ghi đường dẫn của DLL ta muốn load vào vùng nhớ của target process.
Step3: Xác định entry point và thực thi bằng cách tạo một thread trên target process, LoadLibraryA() với tham số là đường dẫn DLL. Khi đó thread sẽ thực thi DllMain của DLL.
Đây là kỹ thuật sau khi malicious Dll được đút vào target process sẽ tự thực hiện các thao tác như một windows loader để thực thi ở trên target process.
I: Đầu tiên thực thi 1 file exe có nhiệm vụ:
ReflectiveLoader
của injected malicious Dll trong target process (Sử dụng hàm GetReflectiveLoaderOffset
để tính toán offset).
GetReflectiveLoaderOffset
là parse file và tìm đến export directory của file Dll và so sánh với từng chuỗi trong trường AddressOfNames
(mảng chứa địa chỉ trỏ tới tên của các export functions). Giá trị offset này không đổi với từng Dll file, nhưng có thể để hàm này vào để làm đau đầu mấy đứa reverser :v.II: Sau I, hàm ReflectiveLoader
trong target process sẽ được thực thi, hàm này sẽ thực hiện:
1: Tính toán base address của chính nó trong target process. Sử dụng kỹ thuật khá hay ho, hàm caller() sẽ return về địa chỉ của câu lệnh tiếp theo, hay một địa chỉ ở .text section. Ta sẽ quay ngược lại địa chỉ này đến khi tìm được holy magic bytes "MZ".
2: Tính toán địa chỉ của 3 hàm cần thiết: LoadLibraryA
, GetProcAddress
, VirtualAlloc
từ PEB (Process enviroment block) struct.
3: Cấp phát vùng nhớ mới trên target process, và load từng image section vào vùng nhớ này.
4: Chỉnh lại bảng IAT- Sửa địa chỉ các hàm được import tương ứng.
Sử dụng pointers tới hàm GetProcAddress
và LoadLibraryA
đã resolved từ bước trước.
Ta sẽ dùng bảng INT (import name table) để tìm địa chỉ đúng của các hàm với sự giúp đỡ của LoadLibrary
và sửa lại địa chỉ được trỏ bởi FirstChunk
(IAT-Import Address Table).
ordinal
), nếu không ta sẽ dùng tên function đó. 5: Rebasing lại Dll file trên vùng nhớ đã cấp phát ở 3:
- Nhiệm vụ của bước này là sửa lại giá trị của một số địa chỉ VA (địa chỉ ảo) (đã bị fixed cứng trong quá trình compiler), khi mà image được load tại địa chỉ khác với trường OptinalHeader.ImageBase
(thường là 0x400000)
- .reloc section (or relocation section) là các block chứa cấu trúc IMAGE_BASE_RELOCATION
, mỗi cấu trúc này chia image (đã được load lên memory) thành các phần 4KB để sửa các giá trị cần fix.
VirtualAddress
sẽ xác định địa chỉ bắt đầu của khối 4KB đó. Trương SizeOfBlock
sẽ xác định block tiếp theo ở đâu. Trong block, ngoài 2 trường trên thì còn lại là mảng các phần tử 2 bytes (WORD), chứa offset đến vị trí cần sửa. Hình dưới đây chứa 2 block (tương ứng cho .text section và .data section).ImageBase + VirtulAddress + (TypeOffset & 0xFFF)
delta
là khoảng cách giữa vị trí load mới của file lên mem và ImageBase mặc định của file.delta = newImageBase - preferredImageBase
6: Cuối cùng sẽ gọi hàm entry point của image mới được load (DllMain với DLL_PROCESS_ATTACH
- khi Dll mới được load vào memory)
Kỹ thuật này inject chính PE file vào process khác và gọi chính nó trong đó, na ná Reflective DLL injection. But it's ezier than Reflective way.
Step1: inject PE file vào target process.
Step2: Tính giá trị delta và rebasing image.
Step3: Tạo một thread trên target process với entrypoint là hàm ta muốn thực thi.
Ý tưởng của kỹ thuật này là mã độc khởi chạy một process mới từ tệp thực thi trên máy victim (Ex: explorer.exe). Empty/hollow image của tệp thực thi đó trong chính VAS (Virtual Address Space) của nó, thay đổi vùng nhớ vừa rồi thành malicious code.
Khi CreateProcess() được sử dụng:
Khi hoàn thành, hệ điều hành tạo một thread để thực thi code. Thread này sẽ bắt đầu tại EntryPoint của tệp thực thi. Nếu sử dụng flag CREATE_SUSPENDED,quá trình thực thi của thread sẽ bị tạm dừng ngay trước khi chạy lệnh đầu tiên.
Sau khi suspended process được tạo, ta có thể truy xuất thông tin về target process bao gồm PEB từ đó lấy được baseAddress của process -> EntryPoint.
Step1: tạo target process bị "treo" với CREATE_SUSPENDED
flag.
Step2: Làm trống target process.
ZwUnmapViewOfSection
.
Sau khi unmap
Step3: Cấp phát vùng nhớ cho malicious code và fill từng section vào target process tương ứng. Sau đó chỉnh lại các phần tủ trong relocation section.
GetFileSize
.VirtualAlloc
và ReadFile
để load raw malicious file vào vùng nhớ được cấp phát mới.WriteProcesMemory
để ghi lần lượt Header file và các sections.Step4: Đặt lại entrypoint của target process (Mỗi khi có một image mới được nạp vào bộ nhớ thì thanh ghi EAX của luồng bị treo sẽ được đặt là giá trị của entry point (điểm bắt đầu chương trình). Process sau đó được tiếp tục và entry point của image mới sẽ được thực thi) và chuyển trạng thái của process từ suspend -> running (đợi tín hiệu từ hàm ResumeThread
)
NTFS, FAT32, exFAT là các kiểu định dạng hệ thống tập tin (file system) trên Windows. Kỹ thuật này lợi dụng cơ chế của định dạng NTFS: Transactional NTFS (TxF).
Transactional NTFS được tạo ra với mục đích update hay thay đổi file. Giúp file toàn vẹn nhờ cơ chế commit và rollback sau mỗi lần thay đổi của file. Chẳng hạn trong quá trình cài đặt một package nào đó lên máy, hay quá trình update của windows.
Dưới đây là demo đơn giản về quá trình tạo một transaction với đối tượng là một file chưa tồn tại trên ổ đĩa cùng với cơ chế commit và rollback.
Ta có thể tạo 1 file bên trong một transaction để không process khác thấy được file này (cho đến khi transaction được commit). File đó có thể drop và chạy malicious payloads. Sau đó ta sẽ làm cho file này như chưa từng được tạo băng cách rollback transaction này.
Step1: ghi đè file sạch với malicious code.
Step2: Tạo một section từ transacted file. Như vậy, ta có một section object chứa malicious code.
Step3: Rollback lại transaction, làm transacted file trở về như lúc chưa bị ghi đè.
Step4: Tạo process và tham số để thực thi.
APC (Asynchronous Procedure Calls) của Windows là một hàm cho phép thực thi bất đồng bộ trong một thread cụ thể.
pfnAPC
Còn một cách khác để thực thi APC, bằng cách lợi dụng NtTestAlert (là hàm kiểm tra hàng đợi APC của thread, nếu có bất kỳ APC nào được xếp trong hàng đợi của thread, hàm sẽ chạy các APC tới khi hàng đợi rỗng).
Trước khi một thread bắt đầu thực thi, NtTestAlert được thực thi
trước tiên
(bên trong ntdll!LdrpInitialize gọi hàm NtTestAlert).
Bằng cách này, ta tạo một process (target process)ở trạng thái suspended, inject code và thêm APC (trỏ tới injected code) vào target process. Để thực thi trước khi bắt đầu process để tránh bị phát hiện bởi AV/EDR hooking.
Tìm đến dịa chỉ API, sửa đổi instruction/code trong api tới hàm mong muốn (eg: jump, …) -> thực thi hàm api gốc.
là cơ chế một ứng dụng can thiệp vào events (message, bàn phím, trỏ chuột). Tương tự với hàm ta có hook procedure. hook procedure có thể hoạt động với mỗi event nhận được để thay đổi event.
Hệ thống hỗ trợ nhiều loại hooks (WH_MOUSE, WH_KEYBOARD_LL, WH_GETMESSAGE…). Mỗi loại cung cấp truy cập vào một cơ chế xử lí message, và mỗi hook chain riêng biệt cho từng loại hook. Một hook chain là một list các con trỏ của hook procedure. Khi một message nhận được với loại hook tương ứng, hệ thống sẽ đưa message đó tới hook procedure trong hook chain tương ứng.
SetWindowsHookEx
:Được dùng để cài đặt một hook procedure vào đầu một hook chain.
Nếu tham số thứ 4 là 0 thì hệ thống hook mọi thread của các ứng dụng windows (system-wide remote hook).
NOTE: hệ điều hành 64 bit chỉ chạy được với file x64.
Dưới đây là demo keylogger
1. Tại sao force aslr enabled rồi mà tất cả các địa chỉ vẫn giống nhau giữa các lần load lại process?
A. How Dll is shared between processes?
Mỗi process sẽ load một dll độc lập. Nhưng nếu dll cần đã được loaded bởi một proces khác trước đó (đọc từ đĩa vật lý, rebasing, commit). dll sẽ được copy và đặt tại địa chỉ địa chỉ của process trước (nếu vùng địa chỉ này vẫn available).
Directly mapping the DLL file into memory is a small performance benefit since it avoids reading any of the DLL’s pages into physical memory until they are needed. A better reason for preferred base addresses is to ensure that only one copy of a DLL needs to be in memory. Without them, if three programs run that share a common DLL, but each loads that DLL at a different address, there would be three DLL copies in memory, each relocated to a different base. That would counteract a main benefit of using shared libraries in the first place. Aside from its security benefits, ASLR accomplishes the same thing—ensuring that the address spaces of loaded DLLs won’t overlap and loading only a single copy of a DLL into memory—in a more elegant way.
Windows contains an optimisation to save space in RAM-rather than loading that Dll into RAM dozens times, it loads it once and each process refers to that same chunk of RAM.
https://depthsecurity.com/blog/reflective-dll-injection-in-c
https://github.com/stephenfewer/ReflectiveDLLInjection
https://blog.sevagas.com/?PE-injection-explained
https://viblo.asia/p/tim-hieu-ve-process-hollowing-V3m5WRmxlO7