导入表注入
在动态链接库一章中提到 DllMain,这里再回顾一次:
- 当 DLL 被加载进 4GB 空间时,会调用一次
DllMain
(入口方法)。
- 当程序执行完毕,将 DLL 从 4GB 空间卸载时,也会调用一次
DllMain
。
注入的本质是想方设法将自己的 DLL 投放到其他进程的 4GB 空间中。而在前面讲解的导入表一章中提到:
- 只有隐式调用(静态使用)时,DLL 的名字和相关函数才会出现在调用者 PE 文件的导入表中。
- 显式调用(动态使用)时,被调用的 DLL 名和相关函数不会出现在调用者的导入表中。
换句话说:
- 写入导入表中的 DLL 必定是被隐式调用的(静态使用)。
- 如果将毫不相关的 DLL 写入某个 PE 文件的完整导入表中,那么打开这个 PE 文件时,该 DLL 会被直接加载(操作系统的规则)。
导入表注入的原理与过程
原理
当 .exe
文件被加载时,系统会根据其导入表信息加载需要的 DLL。导入表注入的原理就是修改 .exe
的导入表,将自己的 DLL 添加进去。这样,运行 .exe
时,系统会加载该 DLL 并执行其 DllMain
中的代码,从而实现注入目的。
实现步骤
- 移动导入表到新增节
通常原导入表后面没有足够空间添加新表,因此需要将整个旧导入表移动到新增节中。
- 增加新的导入表
在新增节中,旧导入表后增加一张新的导入表及其附表。
以下是新增内容的结构:
1 2 3 4 5 6 7 8 9 10 11
| typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; }; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR; typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
|
新增导入表需要完整填写相关内容,例如:
- OriginalFirstThunk 指向 DLL 的 INT 表的 RVA。
- Name 指向 DLL 名的 RVA。
- FirstThunk 指向 DLL 的 IAT 表的 RVA。
INT、IAT 表的填充规则
- INT 表和 IAT 表 至少包含一个导入函数;
- 第二个函数项必须为 0;
- 确保表结构正确且完整,否则操作系统不会加载该 DLL。
新增节结构如下:
内容 |
长度 |
描述 |
IMAGE_IMPORT_BY_NAME 结构 |
30 字节 |
填写导出函数名称字符串 |
DLL 名称 |
30 字节 |
填写 DLL 名字符串 |
INT 表(OriginalFirstThunk) |
8 字节 |
指向 IMAGE_IMPORT_BY_NAME |
IAT 表(FirstThunk) |
8 字节 |
指向 IMAGE_IMPORT_BY_NAME |
原导入表 |
SizeOfImport |
移动后的旧导入表内容 |
新增导入表 |
20 字节 |
包括 RVA 和其他信息 |
DLL 示例
为了测试注入,我们需要准备一个简单的 DLL。
以下是 MyDll.h
的头文件:
1 2 3
| void Init(); void Destroy(); extern "C" _declspec(dllexport) void ExportFunction();
|
对应的实现代码 MyDll.cpp
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <windows.h>
void Init() { MessageBox(0, "Init", "Init", MB_OK); }
void Destroy() { MessageBox(0, "Destroy", "Destroy", MB_OK); }
void ExportFunction() { MessageBox(0, "ExportFunction", "ExportFunction", MB_OK); }
|
DLL 的 DllMain
实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include "MyDll.h"
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: Init(); break; case DLL_PROCESS_DETACH: Destroy(); break; } return TRUE; }
|
导入表注入代码
以下是完成注入的关键代码及详细注释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| #include "Currency.h" #include "windows.h" #include "stdio.h"
VOID h331() { char FilePath[] = "CrackHead.exe"; char CopyFilePath[] = "CrackHeadcopy.exe"; LPVOID pFileBuffer = NULL; LPVOID* ppFileBuffer = &pFileBuffer; LPVOID pNewFileBuffer = NULL; LPVOID* ppNewFileBuffer = &pNewFileBuffer;
PIMAGE_DOS_HEADER pDos = 0; PIMAGE_NT_HEADERS32 pNts = 0; PIMAGE_DATA_DIRECTORY pDir = 0; PIMAGE_IMPORT_DESCRIPTOR pImportTable = 0; PIMAGE_SECTION_HEADER pSection = 0; IMAGE_IMPORT_DESCRIPTOR NewImportTable = { 0 };
int NumOfImport = 0; int SizeOfImport = 0;
DWORD SizeOfNewFileBuffer = 0; DWORD SizeOfFileBuffer = ReadPEFile(FilePath, ppFileBuffer);
if (!SizeOfFileBuffer) { printf("文件读取失败\n"); return; }
pDos = (PIMAGE_DOS_HEADER)pFileBuffer; pNts = (PIMAGE_NT_HEADERS32)((DWORD)pFileBuffer + pDos->e_lfanew); pDir = &pNts->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pFileBuffer + RVA2FOA(pFileBuffer, pDir->VirtualAddress)); while (pImportTable->OriginalFirstThunk && pImportTable->Name) { NumOfImport++; pImportTable++; } SizeOfImport = NumOfImport * sizeof(IMAGE_IMPORT_DESCRIPTOR);
SizeOfNewFileBuffer = AddNewSection(pFileBuffer, SizeOfFileBuffer, SizeOfImport + 256, ppNewFileBuffer); free(pFileBuffer);
pDos = (PIMAGE_DOS_HEADER)pNewFileBuffer; pNts = (PIMAGE_NT_HEADERS32)((DWORD)pNewFileBuffer + pDos->e_lfanew); pDir = &pNts->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pNewFileBuffer + RVA2FOA(pNewFileBuffer, pDir->VirtualAddress)); pSection = IMAGE_FIRST_SECTION(pNts);
for (int i = 1; i < pNts->FileHeader.NumberOfSections; i++) { pSection++; }
memcpy((PVOID)((DWORD)pNewFileBuffer + pSection->PointerToRawData + 2), "ExportFunction", sizeof("ExportFunction")); memcpy((PVOID)((DWORD)pNewFileBuffer + pSection->PointerToRawData + 30), "InjectDll.dll", sizeof("InjectDll.dll"));
memcpy((PVOID)((DWORD)pNewFileBuffer + pSection->PointerToRawData + 30 + 30), &pSection->VirtualAddress, 4); memcpy((PVOID)((DWORD)pNewFileBuffer + pSection->PointerToRawData + 30 + 30 + 8), &pSection->VirtualAddress, 4);
memcpy((PVOID)((DWORD)pNewFileBuffer + pSection->PointerToRawData + 30 + 30 + 8 + 8), pImportTable, SizeOfImport); pDir->VirtualAddress = pSection->VirtualAddress + 30 + 30 + 8 + 8; pDir->Size += sizeof(IMAGE_IMPORT_DESCRIPTOR);
NewImportTable.OriginalFirstThunk = pSection->VirtualAddress + 30 + 30; NewImportTable.FirstThunk = pSection->VirtualAddress + 30 + 30 + 8; NewImportTable.Name = pSection->VirtualAddress + 30; memcpy((PVOID)((DWORD)pNewFileBuffer + pSection->PointerToRawData + 30 + 30 + 8 + 8 + SizeOfImport), &NewImportTable, sizeof(IMAGE_IMPORT_DESCRIPTOR));
MemeryToFile(pNewFileBuffer, SizeOfNewFileBuffer, CopyFilePath); free(pNewFileBuffer); }
|
注意事项
新增节的属性:
新增节的 Characteristics
必须包含 C0000040
。
C0000000:支持 Execute
和 Read
。
0040:支持 Write
。
填充对齐:
新增节的大小必须是文件对齐的倍数。
多次测试:
确保注入的 DLL 能正常运行,避免修改后 PE 文件崩溃。
Work
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
| #include "PE.h"
bool InsertDllByImportTab( IN LPVOID pe, IN DWORD index = -1 ) { PIMAGE_DOS_HEADER pDosHeader = nullptr; PIMAGE_FILE_HEADER pFileHeader = nullptr; PIMAGE_OPTIONAL_HEADER pOptionalHeader = nullptr; PIMAGE_SECTION_HEADER* pSectionHeaderArr = nullptr; AnlyzePE(pe, pDosHeader, pFileHeader, pOptionalHeader, pSectionHeaderArr);
if (index == -1) { index = pFileHeader->NumberOfSections - 1; }
PIMAGE_IMPORT_DESCRIPTOR importTable = (PIMAGE_IMPORT_DESCRIPTOR)((PCHAR)pe + RvaToFoa(pe, pOptionalHeader->DataDirectory[1].VirtualAddress)); DWORD importTableNumber = (pOptionalHeader->DataDirectory[1].Size - 0x14) / 0x14;
size_t importTableSize = (sizeof(*importTable) * importTableNumber) + 0x10 + sizeof(PIMAGE_IMPORT_BY_NAME); if (pSectionHeaderArr[index]->SizeOfRawData - pSectionHeaderArr[index]->Misc.VirtualSize >= importTableSize) { memcpy_s( (PCHAR)pe + pSectionHeaderArr[index]->PointerToRawData + pSectionHeaderArr[index]->Misc.VirtualSize, (sizeof(*importTable) * importTableNumber), (PCHAR)importTable, (sizeof(*importTable) * importTableNumber) ); pOptionalHeader->DataDirectory[1].VirtualAddress = FoaToRva(pe, pSectionHeaderArr[index]->PointerToRawData + pSectionHeaderArr[index]->Misc.VirtualSize); pSectionHeaderArr[index]->Misc.VirtualSize += sizeof(*importTable) * importTableNumber;
memset((PCHAR)pe + pSectionHeaderArr[index]->PointerToRawData + pSectionHeaderArr[index]->Misc.VirtualSize, 0, sizeof(IMAGE_IMPORT_DESCRIPTOR)); PIMAGE_IMPORT_DESCRIPTOR insertDllTab = (PIMAGE_IMPORT_DESCRIPTOR)((PCHAR)pe + pSectionHeaderArr[index]->PointerToRawData + pSectionHeaderArr[index]->Misc.VirtualSize); pSectionHeaderArr[index]->Misc.VirtualSize += sizeof(IMAGE_IMPORT_DESCRIPTOR) * 2;
PCHAR insertDllName = (PCHAR)"Inject.dll"; memcpy_s( (PCHAR)pe + pSectionHeaderArr[index]->PointerToRawData + pSectionHeaderArr[index]->Misc.VirtualSize, strlen(insertDllName) + 1, insertDllName, strlen(insertDllName) + 1 ); insertDllTab->Name = FoaToRva(pe, pSectionHeaderArr[index]->PointerToRawData + pSectionHeaderArr[index]->Misc.VirtualSize); pSectionHeaderArr[index]->Misc.VirtualSize += strlen(insertDllName) + 1;
memset((PCHAR)pe + pSectionHeaderArr[index]->PointerToRawData + pSectionHeaderArr[index]->Misc.VirtualSize, 0, 8); PDWORD intTab = (PDWORD)((PCHAR)pe + pSectionHeaderArr[index]->PointerToRawData + pSectionHeaderArr[index]->Misc.VirtualSize); insertDllTab->OriginalFirstThunk = FoaToRva(pe, pSectionHeaderArr[index]->PointerToRawData + pSectionHeaderArr[index]->Misc.VirtualSize); pSectionHeaderArr[index]->Misc.VirtualSize += 8;
memset((PCHAR)pe + pSectionHeaderArr[index]->PointerToRawData + pSectionHeaderArr[index]->Misc.VirtualSize, 0, 8); PDWORD iatTab = (PDWORD)((PCHAR)pe + pSectionHeaderArr[index]->PointerToRawData + pSectionHeaderArr[index]->Misc.VirtualSize); insertDllTab->FirstThunk = FoaToRva(pe, pSectionHeaderArr[index]->PointerToRawData + pSectionHeaderArr[index]->Misc.VirtualSize); pSectionHeaderArr[index]->Misc.VirtualSize += 8;
memset((PCHAR)pe + pSectionHeaderArr[index]->PointerToRawData + pSectionHeaderArr[index]->Misc.VirtualSize, 0, sizeof(IMAGE_IMPORT_BY_NAME)); PIMAGE_IMPORT_BY_NAME ibn = (PIMAGE_IMPORT_BY_NAME)((PCHAR)pe + pSectionHeaderArr[index]->PointerToRawData + pSectionHeaderArr[index]->Misc.VirtualSize); ibn->Hint = 0; const char* ibnName = "Load"; memcpy_s( (PCHAR)pe + pSectionHeaderArr[index]->PointerToRawData + pSectionHeaderArr[index]->Misc.VirtualSize + 0x2, strlen(ibnName) + 1, ibnName, strlen(ibnName) + 1 ); *intTab = FoaToRva(pe, pSectionHeaderArr[index]->PointerToRawData + pSectionHeaderArr[index]->Misc.VirtualSize); *iatTab = FoaToRva(pe, pSectionHeaderArr[index]->PointerToRawData + pSectionHeaderArr[index]->Misc.VirtualSize); pSectionHeaderArr[index]->Misc.VirtualSize += strlen(ibnName) + 1 + 2;
pOptionalHeader->DataDirectory[1].Size += 0x14; } free(pSectionHeaderArr); return true; }
int main() { LPVOID pe = ReadPE(R"(D:\source\repos\00\Debug\00.exe)"); if (pe && CreateSection(pe, 0x1000)) { InsertDllByImportTab(pe); WriteMyFilePeData(pe, R"(D:\source\repos\00\Debug\00.exe)"); free(pe); } return 0; }
|