实现64位与32位的PELoader

结合了前文PE文件格式的知识就可以做出一个简易的PE加载器(就是模拟了启动.exe这个过程)

实现了32与64位不同情况下的PELoader

步骤

节区表扩展

节区表在PE头NT之后,结构如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

取真实偏移扩展到RVA就行

节区表个数在NT头下的FileHeader中

修复重定位表

重定位表在NT头中

OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]

结构如下

1
2
3
4
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

重定位表由n个 一个开头和开头中声明的后续 组成

- 开头部分声明当前部分的基地址与大小
- 后续部分以WORD定义,具体如下

高4位代表重定位类型,低12位代表重定位地址,这里的重定位地址的真实地址需要+开头部分定义的基地址

注意这里开头部分基地址+后续部分重定位地址=磁盘偏移

运行时扩展地址=磁盘偏移-磁盘开头偏移+真实开头偏移

修复导入表

导入表如重定位表 也在DataDirectory中 结构一致

如下是导入表的结构

1
2
3
4
5
6
7
8
9
10
11
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union
DWORD Characteristics;
DWORD OriginalFirstThunk; //INT表
} DUMMYUNIONNAM;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Nam;
DWORD FirstThunk; //IAT表
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

导入表也是数组存储,需要改变的是FirstThunk(IAT表),通过OriginalFirstThunk(INT表)中的信息查找dll返回值改变IAT表,IAT与INT表中结构如下

1
2
3
4
5
6
7
8
9
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString;
DWORD Function;
DWORD Ordinal;
DWORD AddressOfData;
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;

源码

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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
#include <stdio.h>
#include <windows.h>
#include <stdbool.h>

PBYTE OpenFile(PCHAR filename)
{
FILE* pfile = NULL;
fopen_s(&pfile, filename, "rb");

if (pfile == NULL)
{
printf("[-]: open file!\n");
return 0;
}
if (fseek(pfile, 0, SEEK_END))
{
printf("[-]: fseek wrong!\n");
return 0;
}
DWORD size = ftell(pfile);
if (fseek(pfile, 0, SEEK_SET))
{
printf("[-]: fseek wrong!\n");
return 0;
}
PBYTE Buffer = (PBYTE)calloc(size, 1);
if (fread(Buffer, 1, size, pfile) != size)
{
printf("[-]: fread error!\n");
return 0;
}
fclose(pfile);
return Buffer;
}

VOID FixSection32(PIMAGE_NT_HEADERS32 pNt, PBYTE Buffer, PBYTE pAlloc)
{
//节区表的开始
PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)((LPBYTE)pNt + sizeof(IMAGE_NT_HEADERS32));

//节区表指针,节区表紧随NT部分后,所以通过NT结构指针+NT结构大小的方式来获得节区表起始位置
DWORD SecNum = pNt->FileHeader.NumberOfSections; //NT部分规定了节区表的数目
for (int i = 0; i < SecNum; i++)
{
//如果当前节区表的数据为空,复制下一个节区表的数据
if (pSec->SizeOfRawData == 0 || pSec->PointerToRawData == 0) {
pSec++;
continue;
}
PCHAR chSrcMem = (PCHAR)(Buffer + pSec->PointerToRawData); //chSrcMem =节区的文件偏移地址
PCHAR chDestMem = (PCHAR)(pAlloc + pSec->VirtualAddress); //chDestMem=节区的RVA地址
DWORD dwSizeOfRawData = pSec->SizeOfRawData; //文件大小
RtlCopyMemory(chDestMem, chSrcMem, dwSizeOfRawData); //复制过程
pSec++; //结构指针++就是下一个结构
}
}
VOID FixSection64(PIMAGE_NT_HEADERS pNt, PBYTE Buffer, PBYTE pAlloc)
{
//节区表的开始
PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)((LPBYTE)pNt + sizeof(IMAGE_NT_HEADERS));
//节区表指针,节区表紧随NT部分后,所以通过NT结构指针+NT结构大小的方式来获得节区表起始位置
DWORD SecNum = pNt->FileHeader.NumberOfSections; //NT部分规定了节区表的数目
for (int i = 0; i < SecNum; i++)
{
//如果当前节区表的数据为空,复制下一个节区表的数据
if (pSec->SizeOfRawData == 0 || pSec->PointerToRawData == 0) {
pSec++;
continue;
}
PCHAR chSrcMem = (PCHAR)(Buffer + pSec->PointerToRawData); //chSrcMem =节区的文件偏移地址
PCHAR chDestMem = (PCHAR)(pAlloc + pSec->VirtualAddress); //chDestMem=节区的RVA地址
DWORD dwSizeOfRawData = pSec->SizeOfRawData; //文件大小
RtlCopyMemory(chDestMem, chSrcMem, dwSizeOfRawData); //复制过程
pSec++; //结构指针++就是下一个结构
}
}

VOID FixBaseReloc32(PIMAGE_NT_HEADERS32 pNt, PBYTE Buffer, PBYTE pAlloc)
{
//开始检测并加载重定位表
PIMAGE_BASE_RELOCATION pBaseReloc = (PIMAGE_BASE_RELOCATION)(pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress + pAlloc);
//重定位表指针通过NT结构的数据目录表查找到位置
DWORD SizeOfBaseReloc = pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;//重定位表的大小也在NT结构中
DWORD RVABase = pNt->OptionalHeader.ImageBase;
if (pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress != NULL)
{
do {
PWORD TypeOffset = (WORD*)((PBYTE)pBaseReloc + sizeof(IMAGE_BASE_RELOCATION));//跳过重定位头
DWORD num = (pBaseReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2; //SizeOfBlock规定的是该单元的大小以及TypeOffset是一字的
for (DWORD i = 0; i < num; i++)
{
WORD type = TypeOffset[i] >> 12; //TypeOffset[i] >> 12相当于在查找TypeOffset的前四字节(类型)
WORD offset = TypeOffset[i] & 0x0FFF;
DWORD differ = 0;
if (type == 3)
{
DWORD RVA = offset + pBaseReloc->VirtualAddress; //RVA
PDWORD TargetAddress = (PDWORD)(RVA + (DWORD)pAlloc); //需要重定位的地址
DWORD OrginalAddress = *(PDWORD)TargetAddress; //原本的偏移
DWORD NewAddress = OrginalAddress - RVABase + (DWORD)pAlloc;
memmove(TargetAddress, &NewAddress, sizeof(DWORD));
}
}
SizeOfBaseReloc -= pBaseReloc->SizeOfBlock; //通过字节大小来间接表示个数
pBaseReloc = (PIMAGE_BASE_RELOCATION)((PBYTE)pBaseReloc + pBaseReloc->SizeOfBlock);//相当于结构指针++了,不过这么看来TypeOffset好像真不属于这个结构
} while (SizeOfBaseReloc);
}
}

VOID FixBaseReloc64(PIMAGE_NT_HEADERS pNt, PBYTE Buffer, PBYTE pAlloc)
{
//开始检测并加载重定位表
PIMAGE_BASE_RELOCATION pBaseReloc = (PIMAGE_BASE_RELOCATION)(pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress + pAlloc);
//重定位表指针通过NT结构的数据目录表查找到位置
int SizeOfBaseReloc = pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;//重定位表的大小也在NT结构中
ULONG_PTR RVABase = pNt->OptionalHeader.ImageBase;
if (pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress != NULL)
{
do {
PWORD TypeOffset = (WORD*)((PBYTE)pBaseReloc + sizeof(IMAGE_BASE_RELOCATION));//跳过重定位头
int num = (pBaseReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2; //SizeOfBlock规定的是该单元的大小以及TypeOffset是一字的
for (int i = 0; i < num; i++)
{
WORD type = TypeOffset[i] >> 12; //TypeOffset[i] >> 12相当于在查找TypeOffset的前四字节(类型)
WORD offset = TypeOffset[i] & 0x0FFF;
ULONG_PTR differ = 0;
if (type == IMAGE_REL_BASED_DIR64)
{
ULONG_PTR RVA = offset + pBaseReloc->VirtualAddress; //RVA
PULONG_PTR TargetAddress = (PULONG_PTR)(RVA + (ULONG_PTR)pAlloc); //需要重定位的地址
ULONG_PTR OrginalAddress = *TargetAddress; //原本的偏移
ULONG_PTR NewAddress = OrginalAddress - RVABase + (ULONG_PTR)pAlloc;
memmove(TargetAddress, &NewAddress, sizeof(ULONG_PTR));
}
}
SizeOfBaseReloc -= pBaseReloc->SizeOfBlock; //通过字节大小来间接表示个数
pBaseReloc = (PIMAGE_BASE_RELOCATION)((PBYTE)pBaseReloc + pBaseReloc->SizeOfBlock);//相当于结构指针++了,不过这么看来TypeOffset好像真不属于这个结构
} while (SizeOfBaseReloc);
}
}

VOID FixIAT32(PIMAGE_NT_HEADERS32 pNt, PBYTE Buffer, PBYTE pAlloc)
{
//导入表的处理
PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)(pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + pAlloc);
//这个是IID的指针
if (pImport != NULL)
{
while (pImport->Name != NULL)
{
char DLLname[50];
strncpy(DLLname, (char*)(pImport->Name + pAlloc), 49); //获得DLL的名称
HMODULE hProcess = LoadLibraryA(DLLname); //通过名称找句柄
if (!hProcess)
{
char err[100];
sprintf(err, "未找到%s", DLLname);
MessageBoxA(NULL, err, "Error", MB_OKCANCEL);
exit(1);
}
PIMAGE_THUNK_DATA32 pINT = (PIMAGE_THUNK_DATA32)(pImport->OriginalFirstThunk + pAlloc);
PIMAGE_THUNK_DATA32 pIAT = (PIMAGE_THUNK_DATA32)(pImport->FirstThunk + pAlloc);
while ((DWORD)(pINT->u1.AddressOfData) != NULL)
{
PIMAGE_IMPORT_BY_NAME pFucname = (PIMAGE_IMPORT_BY_NAME)(pINT->u1.AddressOfData + pAlloc); //找DLL中函数的名字
if (pINT->u1.AddressOfData & IMAGE_ORDINAL_FLAG32)//判断如果是序号就是第一种处理方式
{
pIAT->u1.AddressOfData = (DWORD)(GetProcAddress(hProcess, (LPCSTR)(pINT->u1.AddressOfData)));//通过序号来获取地址
}
else
{
pIAT->u1.AddressOfData = (DWORD)(GetProcAddress(hProcess, pFucname->Name)); //通过函数名来获取地址
}
pINT++;
pIAT++;
}
pImport++;
}
}
}

VOID FixIAT64(PIMAGE_NT_HEADERS pNt, PBYTE Buffer, PBYTE pAlloc)
{
//导入表的处理
PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)(pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + pAlloc);
//这个是IID的指针
if (pImport != NULL)
{
while (pImport->Name != NULL)
{
char DLLname[50];
strncpy(DLLname, (char*)(pImport->Name + pAlloc), 49); //获得DLL的名称
HMODULE hProcess = LoadLibraryA(DLLname); //通过名称找句柄
if (!hProcess)
{
char err[100];
sprintf(err, "未找到%s", DLLname);
MessageBoxA(NULL, err, "Error", MB_OKCANCEL);
exit(1);
}
PIMAGE_THUNK_DATA pINT = (PIMAGE_THUNK_DATA)(pImport->OriginalFirstThunk + pAlloc);
PIMAGE_THUNK_DATA pIAT = (PIMAGE_THUNK_DATA)(pImport->FirstThunk + pAlloc);
while ((ULONG_PTR)(pINT->u1.AddressOfData) != NULL)
{
PIMAGE_IMPORT_BY_NAME pFucname = (PIMAGE_IMPORT_BY_NAME)(pINT->u1.AddressOfData + pAlloc); //找DLL中函数的名字
if (pINT->u1.AddressOfData & IMAGE_ORDINAL_FLAG32)//判断如果是序号就是第一种处理方式
{
pIAT->u1.AddressOfData = (ULONG_PTR)(GetProcAddress(hProcess, (LPCSTR)(pINT->u1.AddressOfData)));//通过序号来获取地址
}
else
{
pIAT->u1.AddressOfData = (ULONG_PTR)(GetProcAddress(hProcess, pFucname->Name)); //通过函数名来获取地址
}
pINT++;
pIAT++;
}
pImport++;
}
}
}

int main(int argc, char* argv[], char* envp[])
{
if (argc < 2)
{
printf("使用方式 %s <文件路径>\n", argv[0]);
return 0;
}

PCHAR FileName = argv[1];

//返回文件在内存中的位置
PBYTE Buffer = OpenFile(FileName);
if (Buffer == NULL)
return 1;

#if defined(_WIN64) || defined(__x86_64__) || defined(__aarch64__)
{
//给内存分配空间,并对pAlloc进行初始化
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)Buffer; //定位DOS头
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(Buffer + pDos->e_lfanew); //定位NT头
DWORD ImageSize = pNt->OptionalHeader.SizeOfImage; //内存空间大小
PBYTE pAlloc = (PBYTE)VirtualAlloc(NULL, ImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (pAlloc == NULL)
return 0;
ZeroMemory(pAlloc, ImageSize); //把申请到的空间先用0填充
CopyMemory(pAlloc, Buffer, pNt->OptionalHeader.SizeOfHeaders); //DOS和NT部分复制

//节区
FixSection64(pNt, Buffer, pAlloc);
//重定位
FixBaseReloc64(pNt, Buffer, pAlloc);
//导入表
FixIAT64(pNt, Buffer, pAlloc);

FARPROC EOP = (FARPROC)((LPBYTE)pAlloc + pNt->OptionalHeader.AddressOfEntryPoint);
EOP();
free(Buffer);
free(pAlloc);
}
#else
{
//重新用32位Nt
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)Buffer; //定位DOS头
PIMAGE_NT_HEADERS32 pNt32 = (PIMAGE_NT_HEADERS32)(Buffer + pDos->e_lfanew);

DWORD SizeOfAlloc = pNt32->OptionalHeader.SizeOfImage; //内存空间大小
DWORD SizeOfHeader = pNt32->OptionalHeader.SizeOfHeaders;

PBYTE pAlloc = (PBYTE)VirtualAlloc(NULL, SizeOfAlloc, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (pAlloc == NULL)
return 0;
ZeroMemory(pAlloc, SizeOfAlloc); //把申请到的空间先用0填充
CopyMemory(pAlloc, Buffer, SizeOfHeader); //DOS和NT部分复制

FixSection32(pNt32, Buffer, pAlloc);

FixBaseReloc32(pNt32, Buffer, pAlloc);

FixIAT32(pNt32, Buffer, pAlloc);

FARPROC EOP = (FARPROC)((LPBYTE)pAlloc + pNt32->OptionalHeader.AddressOfEntryPoint);
EOP();
free(Buffer);
free(pAlloc);
}
#endif

return 0;
}