最近想学DLL反射注入,但是发现要手动生成内存的DLL文件,所以就先尝试学PE加载器的实现

跟着佬的博客学的,感觉讲的很详细

手工模拟PE加载器 - 我可是会飞的啊


模拟加载PE文件

将DLL或者EXE文件手动加载到内存中,而不是用CreateProcess或者LoadLibrary函数来操作,防止了部分检测手段,同时将文件数据加入到资源节中可以实现不将其落地就可以执行的效果,减少了生成文件的检测手段

原理

  • 将对应PE文件按节区顺序从文件结构映射到映像内存结构放入内存
  • 根据重定位表修改硬编码数据指令
  • 从导入表获取所需函数,修正导入表函数地址(如果是相同进程,可以用本进程的导入表直接修改)
  • 执行/如果是DLL则是主动执行DLLMain

重要信息

映射PE头

1
OptionalHeader.SizeOfHeaders//获取PE头大小

映射Section

1
2
3
4
IMAGE_FIRST_SECTION(pedata.ntHeader)//获取节区头
sectionHeader[i].PointerToRawData//文件偏移
sectionHeader[i].VirtualAddress//内存偏移
sectionHeader[i].SizeOfRawData//文件大小

修复RelocTable

重定位表结构类似

image-20241217021229993

虽然标准载入PE(exe)的时候是不需要重定位节区的,但是这里是在加载器空间创建内存,所以依然需要修复重定位表。

重定位表中的每一项都是一个结构体

1
2
3
4
5
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;

TypeOffset是一个数组,每一个元素是一个偏移值说明这一个VirtualAddress段有哪些需要重定位的地址。SizeOfBlock包含了整个结构的大小和TypeOffset的大小

Block中TypeOffest为一个字,高4位表示类型,3表示x86,A表示x64,低12位表示偏移量
默认内存偏移量为

1
memBase + VirtualAddress + TypeOffest&0xfff

当前内存偏移量为

1
newMemBase + VirtualAddress + TypeOffest&0xfff

修复IAT

IAT表每个元素都是一个如下结构体IID,每个结构体对应一个DLL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)

DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;

该结构体中,Name字段标识了DLL名称的RVA

FirstThunk是最后的导入地址表指针,最后需要把每个导入的函数的真实地址填入到这里指向的位置

DUMMYUNIONNAME中,一般类型是OriginalFirstThunk,如果是Characteristics,就表示结束(0)
OriginalFirstThunk和导入函数的名称或者序号有关

OriginalFirstThunk和FirstThunk指向了IMAGE_THUNK_DATA结构体的地址

1
2
3
4
5
6
7
8
typedef struct _IMAGE_THUNK_DATA64 {
union {
ULONGLONG ForwarderString; // PBYTE
ULONGLONG Function; // PDWORD
ULONGLONG Ordinal;
ULONGLONG AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA64;

具体结构如上,其中AddressOfData是指向IMAGE_IMPORT_BY_NAME的结构体,具体结构如下

1
2
3
4
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
CHAR Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

如果指向的是导入名称表,那么u1是AddressOfData,
如果是导入地址表,分为两种情况
若是序号导入,u1是Ordinal,首位为1,低4位是导入序号
若是名称导入,u1是Function,目标导入地址

所以我们要同时遍历两个表进行赋值

代码

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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#include<windows.h>
#include<stdio.h>
#include<stdlib.h>
#include<memory>


struct PEdata {
PIMAGE_DOS_HEADER dosHeader;
PIMAGE_NT_HEADERS ntHeader;
PIMAGE_FILE_HEADER fileHeader;
PIMAGE_OPTIONAL_HEADER optionalHeader;

//内存映像整大小
size_t memSize;
//PE头映像大小
size_t memHeaderSize;
//节区映像大小
size_t sectionAlignment;

//内存起始地址
DWORD64 imageBase;
}pedata;





BOOL loadPEstruct(BYTE* rawdata) {
pedata.dosHeader = (PIMAGE_DOS_HEADER)rawdata;
pedata.ntHeader = (PIMAGE_NT_HEADERS)(rawdata + pedata.dosHeader->e_lfanew);
pedata.fileHeader = &(pedata.ntHeader->FileHeader);
pedata.optionalHeader = &(pedata.ntHeader->OptionalHeader);

pedata.imageBase = pedata.optionalHeader->ImageBase;
pedata.memSize = pedata.optionalHeader->SizeOfImage;
pedata.memHeaderSize = pedata.optionalHeader->SizeOfHeaders;
pedata.sectionAlignment = pedata.optionalHeader->SectionAlignment;

return TRUE;
}



std::unique_ptr<BYTE[]> mapToMemory(const char* filePath) {
FILE* file;
errno_t err = fopen_s(&file, filePath, "rb");
if (err != 0) return nullptr;

fseek(file, 0, SEEK_END);
size_t fileSize = ftell(file);
fseek(file, 0, SEEK_SET);

std::unique_ptr<BYTE[]> fileData(new BYTE[fileSize]);
if (!fileData) return nullptr;

fread(fileData.get(), 1, fileSize, file);
fclose(file);

printf("成功申请内存: 0x%llX\n", reinterpret_cast<DWORD64>(fileData.get()));
return fileData;
}


BOOL makeHeadandSectionMap(LPVOID memBase, const BYTE* fileBase) {
printf("--------开始映射节区--------\n");
RtlMoveMemory(memBase, fileBase, pedata.memHeaderSize);

PIMAGE_SECTION_HEADER sectionHeader = IMAGE_FIRST_SECTION(pedata.ntHeader);

for (int i = 0; i < pedata.fileHeader->NumberOfSections; i++) {
if (sectionHeader[i].VirtualAddress == 0 && sectionHeader[i].SizeOfRawData == 0) {
continue;
}
LPVOID srcMem = (LPVOID)(fileBase + sectionHeader[i].PointerToRawData);
LPVOID dstMem = (BYTE*)memBase + sectionHeader[i].VirtualAddress;
size_t sizeOfRawData = sectionHeader[i].SizeOfRawData;
RtlCopyMemory(dstMem, srcMem, sizeOfRawData);

printf(" - 节区%d: %s, 映射到内存 0x%llX\n", i, sectionHeader[i].Name, reinterpret_cast<DWORD64>(dstMem));
}
printf("--------映射节区结束--------\n");

return TRUE;
}


BOOL repareReloc(LPVOID memBase) {
printf("------开始修复重定位表-------\n");
PIMAGE_BASE_RELOCATION relTable = (PIMAGE_BASE_RELOCATION)((BYTE*)memBase + pedata.optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
size_t delta = reinterpret_cast<DWORD64>(memBase) - pedata.optionalHeader->ImageBase;
if (relTable == nullptr || delta == 0) {
return TRUE;
}
int j = 0;
while (relTable->SizeOfBlock != 0 && relTable->VirtualAddress != 0) {
WORD* pRdata = (WORD*)((BYTE*)relTable + sizeof(IMAGE_BASE_RELOCATION));
size_t dataNum = (relTable->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
printf(" - 表%d:\n", j);
for (size_t i = 0; i < dataNum; i++) {
DWORD type = pRdata[i] >> 12;
if (type == IMAGE_REL_BASED_DIR64) {
DWORD64* targetAddress = (DWORD64*)((BYTE*)memBase + relTable->VirtualAddress + (pRdata[i] & 0xFFF));
printf(" - 对应地址: 0x%llX 数据: 0x%llX\n", reinterpret_cast<DWORD64>(targetAddress), *targetAddress);
*targetAddress += delta;
printf(" -> 0x%llX delta: 0x%llX\n", *targetAddress, delta);
}
}
relTable = (PIMAGE_BASE_RELOCATION)((BYTE*)relTable + relTable->SizeOfBlock);
j++;
}
printf("------修复重定位表结束-------\n");

return TRUE;
}


BOOL repareIAT(LPVOID memBase) {
printf("--------开始修复IAT--------\n");
PIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR)((BYTE*)memBase + pedata.optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
while (importDesc->OriginalFirstThunk) {
char* Dllname = (char*)((BYTE*)memBase + importDesc->Name);
HMODULE hDll = GetModuleHandleA(Dllname);
if (!hDll) {
printf(" - 加载DLL:%s\n", Dllname);
hDll = LoadLibraryA(Dllname);
if (!hDll) {
printf(" - 加载DLL失败,导入函数终止\n");
return FALSE;
}
}
else {
printf(" - DLL已存在:%s\n", Dllname);
}

PIMAGE_THUNK_DATA INT = (PIMAGE_THUNK_DATA)((BYTE*)memBase + importDesc->OriginalFirstThunk);
PIMAGE_THUNK_DATA IAT = (PIMAGE_THUNK_DATA)((BYTE*)memBase + importDesc->FirstThunk);

for (int i = 0; INT[i].u1.AddressOfData; i++) {
DWORD64 pFunc = NULL;
DWORD64 targetFunc = reinterpret_cast<DWORD64>((BYTE*)memBase + INT[i].u1.AddressOfData);
if (targetFunc & IMAGE_ORDINAL_FLAG) {
printf(" - 以序号:%d 导入函数\n", IMAGE_ORDINAL(targetFunc));
pFunc = (DWORD64)GetProcAddress(hDll, MAKEINTRESOURCEA(IMAGE_ORDINAL(targetFunc)));
}
else {
printf(" - 以名称:%s 导入函数\n", ((PIMAGE_IMPORT_BY_NAME)targetFunc)->Name);
pFunc = (DWORD64)GetProcAddress(hDll, ((PIMAGE_IMPORT_BY_NAME)targetFunc)->Name);
}
if (pFunc) {
IAT[i].u1.Function = pFunc;
printf(" - 函数导入成功\n - 目的地址: 0x%llX\n", pFunc);
}
else {
printf(" - 函数导入失败\n");
}
}
importDesc++;
}
printf("--------修复IAT结束--------\n");
return TRUE;
}


BOOL setImageBase(LPVOID memBase) {
IMAGE_DOS_HEADER* dosHeader = (PIMAGE_DOS_HEADER)memBase;
IMAGE_NT_HEADERS* ntHeader = (PIMAGE_NT_HEADERS)((BYTE*)memBase + dosHeader->e_lfanew);
ntHeader->OptionalHeader.ImageBase = reinterpret_cast<DWORD64>(memBase);
printf("--------修正镜像基地址成功--------\n");
return TRUE;
}







int main() {
const char* targetPath = "xxx";

std::unique_ptr<BYTE[]> fileBase = mapToMemory(targetPath);
if (!fileBase) return 0;

loadPEstruct(fileBase.get());

LPVOID memBase = VirtualAllocEx(GetCurrentProcess(), nullptr, pedata.memSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!memBase) return 0;

makeHeadandSectionMap(memBase, fileBase.get());
repareReloc(memBase);
repareIAT(memBase);
setImageBase(memBase);

DWORD dwOldProtect = 0;
VirtualProtectEx(GetCurrentProcess(), memBase, pedata.memSize, PAGE_EXECUTE_READWRITE, &dwOldProtect);

printf("开始执行\n");
auto targetFunc = reinterpret_cast<int(*)(void)>((BYTE*)memBase + ((PIMAGE_NT_HEADERS)((BYTE*)memBase + ((PIMAGE_DOS_HEADER)memBase)->e_lfanew))->OptionalHeader.AddressOfEntryPoint);
targetFunc();

VirtualFreeEx(GetCurrentProcess(), memBase, 0, MEM_RELEASE);
return 0;
}

目前未解决的问题

  • 对DLL的载入,下次学反射DLL注入的时候试试
  • 若文件本身有UPX等壳,修正的所有内容均为壳的内容,壳解压后并不能还原PE本身的映射到偏移地址,导致报错