WindowsAPI查缺补漏-PE文件格式解析

碎碎念

PE格式为Win32可执行文件采用的文件格式,Win64下称为PE32+。虚拟地址VA表示数据在进程地址空间中内存地址,相对虚拟地址RVA表示数据相对模块基地址偏移,文件偏移地址FOA为文件中数据相对于文件头的偏移。

本节不讲PE文件结构,只讲代码实现。

DOS头

DOS MZ头是个IMAGE_DOS_HEADER结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct _IMAGE_DOS_HEADER {
WORD e_magic; //字符MZ
WORD e_cblp; //最后页字节数
WORD e_cp; //全部和部分页数
WORD e_crlc; //重定向表中指针数
WORD e_cparhdr; //DOS MZ长度
WORD e_minalloc; //所需最小附加段
WORD e_maxalloc; //所需最大附加段
WORD e_ss; //DOS代码初始SS值
WORD e_sp; //DOS代码初始SP值
WORD e_csum; //补码校验值
WORD e_ip; //DOS代码初始IP值
WORD e_cs; //DOS代码初始CS值
WORD e_lfarlc; //重定位表偏移量
WORD e_ovno; //覆盖号
WORD e_res[4]; //保留
WORD e_oemid; //OEM标识符
WORD e_oeminfo; //OEM信息
WORD e_res2[10]; //保留字段
LONG e_lfanew; //PE头偏移地址
} IMAGE_DOS_HEADER, * PIMAGE_DOS_HEADER;

除了e_magic和e_lfanew,其他字段全为0也不影响程序运行。DOS Stub也不重要。

PE头

PE头结构:

1
2
3
4
5
6
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature; //值IMAGE_NT_SIGNATURE
IMAGE_FILE_HEADER FileHeader; //标准PE头
IMAGE_OPTIONAL_HEADER64 OptionalHeader; //扩展PE头
} IMAGE_NT_HEADERS64, * PIMAGE_NT_HEADERS64;
typedef IMAGE_NT_HEADERS64 IMAGE_NT_HEADERS;

其中IMAGE_FILE_HEADER:

1
2
3
4
5
6
7
8
9
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //运行平台
WORD NumberOfSections; //节区数 最大96
DWORD TimeDateStamp; //编译器创建PE文件日期时间 UTC
DWORD PointerToSymbolTable; //COFF符号表偏移 无符号表0
DWORD NumberOfSymbols; //COFF符号数
WORD SizeOfOptionalHeader; //扩展PE头结构长度
WORD Characteristics; //文件属性
} IMAGE_FILE_HEADER, * PIMAGE_FILE_HEADER;

Machine可以是IMAGE_FILE_MACHINE_I386、IMAGE_FILE_MACHINE_IA64、IMAGE_FILE_MACHINE_AMD64。Characteristics,可以是组合:

枚举值 含义
IMAGE_FILE_RELOCS_STRIPPED 无重定位信息
IMAGE_FILE_EXECUTABLE_IMAGE 为可执行文件
IMAGE_FILE_LINE_NUMS_STRIPPED 不存在COFF行号
IMAGE_FILE_LOCAL_SYMS_STRIPPED 不存在COFF符号表
IMAGE_FILE_LARGE_ADDRESS_AWARE 该程序可处理大于2GB内存地址
IMAGE_FILE_32BIT_MACHINE 只能在32位平台上运行
IMAGE_FILE_DEBUG_STRIPPED 不含调试信息
IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 位于可移动媒体上时复制到交换文件并运行
IMAGE_FILE_NET_RUN_FROM_SWAP 位于网络上时复制到交换文件并运行
IMAGE_FILE_SYSTEM 系统文件
IMAGE_FILE_DLL DLL文件
IMAGE_FILE_UP_SYSTEM_ONLY 只能在单处理器计算机上运行

扩展PE头结构有:

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
typedef struct _IMAGE_OPTIONAL_HEADER64 {
//原COFF 标准字段
WORD Magic; //PE文件格式
BYTE MajorLinkerVersion; //链接器主版本号
BYTE MinorLinkerVersion; //链接器次版本号
DWORD SizeOfCode; //可执行代码总大小 对齐后
DWORD SizeOfInitializedData; //已初始化数据总大小 对齐后
DWORD SizeOfUninitializedData; //未初始化数据总大小 对齐后
DWORD AddressOfEntryPoint; //程序执行入口点RVA
DWORD BaseOfCode; //代码节RVA
//扩展字段
ULONGLONG ImageBase; //数据节RVA
DWORD SectionAlignment; //内存节对齐粒度
DWORD FileAlignment; //文件节对齐粒度
WORD MajorOperatingSystemVersion; //所需操作系统主版本号
WORD MinorOperatingSystemVersion; //所需操作系统次版本号
WORD MajorImageVersion; //PE主版本号
WORD MinorImageVersion; //PE次版本号
WORD MajorSubsystemVersion; //所需子系统主版本号
WORD MinorSubsystemVersion; //所需子系统次版本号
DWORD Win32VersionValue; //保留
DWORD SizeOfImage; //PE内存映像 对齐后
DWORD SizeOfHeaders; //DOS头+PE头+节表大小 对齐后
DWORD CheckSum; //校验和
WORD Subsystem; //所需界面子系统
WORD DllCharacteristics; //文件属性
ULONGLONG SizeOfStackReserve; //初始化栈大小
ULONGLONG SizeOfStackCommit; //初始化实际提交的栈大小
ULONGLONG SizeOfHeapReserve; //初始化堆大小
ULONGLONG SizeOfHeapCommit; //初始化实际提交的堆大小
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes; //数据目录结构数
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //数据目录结构
} IMAGE_OPTIONAL_HEADER64, * PIMAGE_OPTIONAL_HEADER64;

Magic可以是:

枚举值 含义
IMAGE_NT_OPTIONAL_HDR32_MAGIC 32位可执行映像PE
IMAGE_NT_OPTIONAL_HDR64_MAGIC 64位可执行映像PE32+
IMAGE_ROM_OPTIONAL_HDR_MAGIC ROM映像

Subsystem字段可以是:

枚举值 含义
IMAGE_SUBSYSTEM_UNKNOWN 未知
IMAGE_SUBSYSTEM_NATIVE 无需子系统
IMAGE_SUBSYSTEM_WINDOWS_GUI Windows GUI子系统
IMAGE_SUBSYSTEM_WINDOWS_CUI Windows CUI子系统
IMAGE_SUBSYSTEM_OS2_CUI OS/2 CUI子系统
IMAGE_SUBSYSTEM_POSIX_CUI POSIX CUI子系统
IMAGE_SUBSYSTEM_WINDOWS_CE_GUI Windows CE系统
IMAGE_SUBSYSTEM_EFI_APPLICATION 可扩展固件接口EFI应用程序
IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER 具有启动服务的EFI驱动程序
IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER 具有运行时服务的EFI驱动程序
IMAGE_SUBSYSTEM_EFI_ROM EFI ROM映像
IMAGE_SUBSYSTEM_XBOX Xbox系统
IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION 引导程序

DllCharacteristics字段实际上针对任何PE文件,不只是DLL,可以是组合:

枚举值 含义
IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA 可处理64位虚拟地址,即PE32+
IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 取消ASLR
IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 强制执行代码完整性检查
IMAGE_DLLCHARACTERISTICS_NX_COMPAT 该PE映像与DEP兼容
IMAGE_DLLCHARACTERISTICS_NO_ISOLATION 可识别但不应隔离
IMAGE_DLLCHARACTERISTICS_NO_SEH 不用SEH
IMAGE_DLLCHARACTERISTICS_NO_BIND 不要绑定PE映像
IMAGE_DLLCHARACTERISTICS_WDM_DRIVER WDM驱动程序
IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE 可识别WTS

数据块结构:

1
2
3
4
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //数据RVA地址
DWORD Size; //数据长度
} IMAGE_DATA_DIRECTORY, * PIMAGE_DATA_DIRECTORY;

数据块顺序依次为:

枚举值 含义 节区名
IMAGE_DIRECTORY_ENTRY_EXPORT 导出表 .edata
IMAGE_DIRECTORY_ENTRY_IMPORT 导入表 .idata
IMAGE_DIRECTORY_ENTRY_RESOURCE 资源表 .rsrc
IMAGE_DIRECTORY_ENTRY_EXCEPTION 异常表 .pdata
IMAGE_DIRECTORY_ENTRY_SECURITY 属性证书表
IMAGE_DIRECTORY_ENTRY_BASERELOC 重定位表 .reloc
IMAGE_DIRECTORY_ENTRY_DEBUG 调试信息 .debug
IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 平台相关数据
IMAGE_DIRECTORY_ENTRY_GLOBALPTR 指向全局指针寄存器值
IMAGE_DIRECTORY_ENTRY_TLS TLS .tls
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 加载配置信息表
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 绑定导入表
IMAGE_DIRECTORY_ENTRY_IAT 导入函数地址表IAT
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 延迟加载导入表
IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR CLR运行时头部数据 .cormeta

节表

在IMAGE_NT_HEADERS后紧接着就是IMAGE_SECTION_HEADER用于描述节区信息:

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]; //节区名 UTF-8
union {
DWORD PhysicalAddress;
DWORD VirtualSize; //节区大小 未对齐
} Misc;
DWORD VirtualAddress; //节区RVA地址
DWORD SizeOfRawData; //节区大小 对齐后
DWORD PointerToRawData; //节区文件偏移地址FOA
DWORD PointerToRelocations; //指向重定位表
DWORD PointerToLinenumbers; //行号表指针
WORD NumberOfRelocations; //重定位表数
WORD NumberOfLinenumbers; //行号表中行号数
DWORD Characteristics; //节区属性
} IMAGE_SECTION_HEADER, * PIMAGE_SECTION_HEADER;

Characteristics常用组合有:

枚举值 含义
IMAGE_SCN_CNT_CODE 包含可执行代码
IMAGE_SCN_CNT_INITIALIZED_DATA 包含已初始化数据
IMAGE_SCN_CNT_UNINITIALIZED_DATA 包含未初始化数据
IMAGE_SCN_GPREL 包含通过全局指针引用的数据
IMAGE_SCN_LNK_NRELOC_OVFL 包含扩展重定位
IMAGE_SCN_MEM_DISCARDABLE 可根据需要丢弃
IMAGE_SCN_MEM_NOT_CACHED 无法缓存
IMAGE_SCN_MEM_NOT_PAGED 无法分页
IMAGE_SCN_MEM_SHARED 可在内存中共享
IMAGE_SCN_MEM_EXECUTE 包含可执行属性
IMAGE_SCN_MEM_READ 包含可读属性
IMAGE_SCN_MEM_WRITE 包含可写属性

小结1

RVA转FOA:

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
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include "resource.h"
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
INT RVAToFOA(PIMAGE_NT_HEADERS pImageNtHeader, DWORD dwTargetRVA);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
TCHAR szFile[MAX_PATH] = { 0 };
OPENFILENAME ofn = { 0 };
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwndDlg;
ofn.lpstrFilter = TEXT("exe文件(*.exe)\0*.exe\0dll文件(*.dll)\0*.dll\0All(*.*)\0*.*\0");
ofn.nFilterIndex = 3;
ofn.lpstrFile = szFile;
ofn.lpstrFile[0] = NULL;
ofn.nMaxFile = _countof(szFile);
ofn.lpstrTitle = TEXT("请选择要打开的PE文件");
ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
HANDLE hFile, hFileMap;
LPVOID lpMemory;
LARGE_INTEGER liFileSize;
PIMAGE_DOS_HEADER pImageDosHeader;
PIMAGE_NT_HEADERS pImageNtHeader;
DWORD dwTargetRVA;
INT iTargetFOA;
TCHAR szBuf[32] = { 0 };
//TCHAR szFOA[64] = TEXT("0x");
switch (uMsg) {
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_BROWSE: {
if (GetOpenFileName(&ofn))
SetDlgItemText(hwndDlg, IDC_EDIT_PATH, szFile);
break;
};
case IDC_BTN_CONVERT: {
// 打开一个PE文件
GetDlgItemText(hwndDlg, IDC_EDIT_PATH, szFile, _countof(szFile));
hFile = CreateFile(szFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
MessageBox(hwndDlg, TEXT("CreateFile函数调用失败"), TEXT("提示"), MB_OK);
return TRUE;
}
else {
GetFileSizeEx(hFile, &liFileSize);
if (liFileSize.QuadPart == 0) {
MessageBox(hwndDlg, TEXT("文件大小为0"), TEXT("提示"), MB_OK);
return TRUE;
};
};
// 为hFile文件对象创建一个文件映射内核对象
hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (!hFileMap) {
MessageBox(hwndDlg, TEXT("CreateFileMapping调用失败"), TEXT("提示"), MB_OK);
return TRUE;
};
// 把文件映射对象hFileMap的全部映射到进程的虚拟地址空间中
lpMemory = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
if (!lpMemory) {
MessageBox(hwndDlg, TEXT("MapViewOfFile调用失败"), TEXT("提示"), MB_OK);
return TRUE;
};
// 打开的文件是不是PE文件
pImageDosHeader = (PIMAGE_DOS_HEADER)lpMemory;
if (pImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
MessageBox(hwndDlg, TEXT("打开的不是PE文件"), TEXT("提示"), MB_OK);
return TRUE;
};
pImageNtHeader = (PIMAGE_NT_HEADERS)((LPBYTE)pImageDosHeader + pImageDosHeader->e_lfanew);
if (pImageNtHeader->Signature != IMAGE_NT_SIGNATURE) {
MessageBox(hwndDlg, TEXT("打开的不是PE文件"), TEXT("提示"), MB_OK);
return TRUE;
};
// 获取RVA编辑控件中的字符串
GetDlgItemText(hwndDlg, IDC_EDIT_RVA, szBuf, _countof(szBuf));
// 数值形式的RVA字符串转换为十六进制数值的RVA
swscanf_s(szBuf, TEXT("%X"), &dwTargetRVA);
// RVAToFOA
if ((iTargetFOA = RVAToFOA(pImageNtHeader, dwTargetRVA)) >= 0) {
// FOA数值转换为字符串
_itot_s(iTargetFOA, szBuf, _countof(szBuf), 16);
// 转换为大写
_tcsupr_s(szBuf, _tcslen(szBuf) + 1);
// 前面添加0x
//_tcscat_s(szFOA, _countof(szFOA), szBuf);
// 把FOA数值形式的字符串显示到FOA编辑控件中
SetDlgItemText(hwndDlg, IDC_EDIT_FOA, szBuf);
};
// 清理工作
UnmapViewOfFile(lpMemory);
CloseHandle(hFileMap);
CloseHandle(hFile);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
INT RVAToFOA(PIMAGE_NT_HEADERS pImageNtHeader, DWORD dwTargetRVA) {
PIMAGE_SECTION_HEADER pImageSectionHeader;
INT iTargetFOA = -1;
// PE和PE32+的节表定位不同
if (pImageNtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
pImageSectionHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)pImageNtHeader + sizeof(IMAGE_NT_HEADERS32));
else
pImageSectionHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)pImageNtHeader + sizeof(IMAGE_NT_HEADERS64));
// 遍历节表
for (int i = 0; i < pImageNtHeader->FileHeader.NumberOfSections; i++) {
if ((dwTargetRVA >= pImageSectionHeader->VirtualAddress) && (dwTargetRVA <= (pImageSectionHeader->VirtualAddress + pImageSectionHeader->SizeOfRawData))) {
iTargetFOA = dwTargetRVA - pImageSectionHeader->VirtualAddress;
iTargetFOA += pImageSectionHeader->PointerToRawData;
};
// 指向下一个节区信息结构
pImageSectionHeader++;
};
return iTargetFOA;
};

导入表

导入表是一个导入表描述符结构IID数组,即IMAGE_IMPORT_DESCRIPTOR结构,最后以一个内容全为0的该结构结束。

1
2
3
4
5
6
7
8
9
10
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk; //IMAGE_THUNK_DATA结构 RVA
} DUMMYUNIONNAME;
DWORD TimeDateStamp; //与绑定相关时间戳
DWORD ForwarderChain; //首个被转发函数索引 没有为-1
DWORD Name; //指向DLL名称字符串 RVA UTF-8
DWORD FirstThunk; //IMAGE_THUNK_DATA结构 RVA
} IMAGE_IMPORT_DESCRIPTOR;

其中IMAGE_THUNK_DATA64定义如下,这结构就是个ULONGLONG,但不同时刻有不同含义。当ULONGLONG第一位为1时,该值低位为Oridianl,用IMAGE_ORDINAL_FLAG64测试。当第一位为0时,该值为AddressOfData,以函数名字符串形式导入。

1
2
3
4
5
6
7
8
typedef struct _IMAGE_THUNK_DATA64 {
union {
ULONGLONG ForwarderString;
ULONGLONG Function; //导入函数内存地址
ULONGLONG Ordinal; //导入函数序数
ULONGLONG AddressOfData; //IMAGE_IMPORT_BY_NAME RVA
} u1;
} IMAGE_THUNK_DATA64;

其中IMAGE_IMPORT_BY_NAME:

1
2
3
4
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; //函数编号 可0
CHAR Name[1]; //函数名字符串 UTF-8
} IMAGE_IMPORT_BY_NAME, * PIMAGE_IMPORT_BY_NAME;

上述OrigninalFirstThunk与FirstThunk内容完全一样,称为双桥结构。但PE文件被映射到内存中后,2桥FirstThunk数组其中元素由记录导入函数名变为函数入口地址。OriginalFirstThunk用于通过函数地址反查导入函数名,有些编译器不用而全填0,称为单桥结构。

FirstThunk指向导入函数内存地址,所有DLL导入函数内存地址数组顺序排列在一起,形成导入函数地址表IAT。OriginalFirstThunk最终指向函数名称形成一个导入函数名称数组,所有DLL导入函数名称数组顺序排列在一起,形成导入函数名称表INT。

导出表

导出表起始位置只有一个导出表目录IMAGE_EXPORT_DIRECOTRY结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; //保留
DWORD TimeDateStamp; //时间戳
WORD MajorVersion; //保留
WORD MinorVersion; //保留
DWORD Name; //指向模块文件名字符串RVA UTF-8
DWORD Base; //导出函数起始序数
DWORD NumberOfFunctions; //导出函数总数
DWORD NumberOfNames; //按函数名导出函数总数
DWORD AddressOfFunctions; //指向导出函数地址表EAT RVA
DWORD AddressOfNames; //指向函数名称地址表ENT RVA
DWORD AddressOfNameOrdinals; //指向函数序数表RVA
} IMAGE_EXPORT_DIRECTORY, * PIMAGE_EXPORT_DIRECTORY;

AddressOfFunctions指向全部导出函数入口地址的DWORD数组,每个DWORD表示一个导出函数的RVA内存地址,数组元素个数等于NumberOfFunctions,这些函数既可通过函数名导出,也可通过函数序数导出。AddressOfNames指向函数名称地址DWORD数组,数组中每个DWORD表示一个函数名称RVA,数组元素个数等于NumberOfNames。AddressOfNameOrdinals指向一个WORD数组,每个WORD表示导出函数地址表EAT索引,与AddressOfNames指向的ENT表一一对应。

通过函数名地址表ENT的索引找到函数序数表对应WORD值,该WORD值即为导出函数地址表EAT的索引。

重定位表

重定位算法为:操作数绝对地址+(模块实际载入地址-模块建议装载地址)。一个32位内存地址需要4字节,每个直接记录太占空间了。绝对地址相邻重定位项高位地址相同,当以4KB页为单位在页面中寻址时,只需12位内粗泥底质。

重定位表是一个重定位块结构IMAGE_BASE_RELOCATION数组:

1
2
3
4
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress; //重定位内存页起始RVA
DWORD SizeOfBlock; //本页重定位块大小 含本结构 单位字节
} IMAGE_BASE_RELOCATION;

该结构后紧跟一个WORD数组表示每个重定位项,WORD的高4位表示重定位项类型,低12位为相对页起始地址的相对地址。即操作数绝对地址的RVA等于VirtualAddress+WORD低12位。最后以一个全0的该结构为结束。WORD高4位类型可以是:

枚举值 含义
IMAGE_REL_BASED_ABSOLUTE 对齐用,有时用
IMAGE_REL_BASED_HIGH 操作数绝对地址高16位需被修正,x86常用
IMAGE_REL_BASED_LOW 操作数绝对地址低16位需被修正
IMAGE_REL_BASED_HIGHLOW 操作数绝对地址32位都要被修正
IMAGE_REL_BASED_HIGHADJ 当前项为高16位,下一个重定位项位低16位,即该重定向要占俩重定向位
IMAGE_REL_BASED_MACHINE_SPECIFIC_5 MIPS平台跳转指令用
IMAGE_REL_BASED_MACHINE_SPECIFIC_9 MIPS16平台跳转指令用
IMAGE_REL_BASED_DIR64 64位程序的64位操作操作数绝对地址,x64常用

PEInfo

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
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
#include <windows.h>
#include <tchar.h>
#include "resource.h"
// 全局变量
HWND g_hwndDlg; // 窗口句柄
HWND g_hwndEdit; // 多行编辑控件窗口句柄
LPCTSTR arrDataDirectory[] = { TEXT("导出表\t\t"), TEXT("导入表\t\t"),TEXT("资源表\t\t"), TEXT("异常表\t\t"),TEXT("属性证书表\t"), TEXT("重定位表\t"),TEXT("调试信息\t"), TEXT("与平台相关的数据"),TEXT("指向全局指针寄存器的值"), TEXT("线程局部存储\t"),TEXT("加载配置信息表\t"), TEXT("绑定导入表\t"),TEXT("导入函数地址表IAT"), TEXT("延迟加载导入表\t"),TEXT("CLR运行时头部数据"), TEXT("保留\t\t") };
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 通过指定类型数据(例如导入表、导出表、重定位表等)的RVA得到FOA
INT RVAToFOA(PIMAGE_NT_HEADERS pImageNtHeader, DWORD dwTargetRVA);
// 通过一个RVA值获取所在节区的名称
LPSTR GetSectionNameByRVA(PIMAGE_NT_HEADERS pImageNtHeader, DWORD dwRVA);
// 通过IMAGE_FILE_HEADER.TimeDateStamp字段获取日期时间格式字符串
LPTSTR GetDateTime(DWORD dwTimeDateStamp);
// 获取PE文件基本信息、节区信息、数据块信息
BOOL GetBaseInfo(PIMAGE_NT_HEADERS pImageNtHeader);
// 获取导入表中所有dll的导入函数的函数编号和函数名称
BOOL GetImportTable(PIMAGE_DOS_HEADER pImageDosHeader);
// 获取导出表中的所有导出函数
BOOL GetExportTable(PIMAGE_DOS_HEADER pImageDosHeader);
// 获取重定位表中所有需要重定位的操作数绝对地址的地址(RVA值)
BOOL GetRelocationTable(PIMAGE_DOS_HEADER pImageDosHeader);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
// 打开文件所用变量
TCHAR szFile[MAX_PATH] = { 0 };
OPENFILENAME ofn = { 0 };
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwndDlg;
ofn.lpstrFilter = TEXT("exe文件(*.exe)\0*.exe\0dll文件(*.dll)\0*.dll\0All(*.*)\0*.*\0");
ofn.nFilterIndex = 3;
ofn.lpstrFile = szFile;
ofn.lpstrFile[0] = NULL;
ofn.nMaxFile = _countof(szFile);
ofn.lpstrTitle = TEXT("请选择要打开的PE文件");
ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
// 内存映射文件所用变量
HANDLE hFile, hFileMap;
LPVOID lpMemory;
LARGE_INTEGER liFileSize;
// DOS头指针和PE文件头指针
PIMAGE_DOS_HEADER pImageDosHeader;
PIMAGE_NT_HEADERS pImageNtHeader;
// 多行编辑控件字体
HFONT hFont;
// 调整多行编辑控件窗口大小之用
static RECT rectWindow;
RECT rectEdit;
static int nWidthEdit, nHeightEdit;
int cx, cy;
switch (uMsg) {
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;
g_hwndEdit = GetDlgItem(hwndDlg, IDC_EDIT_INFO);
// 设置多行编辑控件为宋体
hFont = CreateFont(12, 0, 0, 0, 0, 0, 0, 0, GB2312_CHARSET, 0, 0, 0, 0, TEXT("宋体"));
SendMessage(g_hwndEdit, WM_SETFONT, (WPARAM)hFont, FALSE);
// 默认情况下编辑控件最大缓冲区大小约为32KB个字符,设为不限大小
SendMessage(g_hwndEdit, EM_SETLIMITTEXT, 0, 0);
// 保存程序窗口大小
GetClientRect(hwndDlg, &rectWindow);
// 保存多行编辑控件的宽度高度
GetWindowRect(g_hwndEdit, &rectEdit);
nWidthEdit = rectEdit.right - rectEdit.left;
nHeightEdit = rectEdit.bottom - rectEdit.top;
return TRUE;
};
case WM_SIZE: {
cx = LOWORD(lParam) - rectWindow.right;
cy = HIWORD(lParam) - rectWindow.bottom;
SetWindowPos(g_hwndEdit, NULL, 0, 0, nWidthEdit + cx, nHeightEdit + cy, SWP_NOZORDER | SWP_NOMOVE);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_BROWSE: {
if (GetOpenFileName(&ofn))
SetDlgItemText(hwndDlg, IDC_EDIT_PATH, szFile);
break;
};
case IDC_BTN_GET: {
// 打开一个PE文件
GetDlgItemText(hwndDlg, IDC_EDIT_PATH, szFile, _countof(szFile));
hFile = CreateFile(szFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
MessageBox(hwndDlg, TEXT("CreateFile函数调用失败"), TEXT("提示"), MB_OK);
return TRUE;
}
else {
GetFileSizeEx(hFile, &liFileSize);
if (liFileSize.QuadPart == 0) {
MessageBox(hwndDlg, TEXT("文件大小为0"), TEXT("提示"), MB_OK);
return TRUE;
};
};
// 为hFile文件对象创建一个文件映射内核对象
hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (!hFileMap) {
MessageBox(hwndDlg, TEXT("CreateFileMapping调用失败"), TEXT("提示"), MB_OK);
return TRUE;
};
// 把文件映射对象hFileMap的全部映射到进程的虚拟地址空间中
lpMemory = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
if (!lpMemory) {
MessageBox(hwndDlg, TEXT("MapViewOfFile调用失败"), TEXT("提示"), MB_OK);
return TRUE;
};
// 打开的文件是不是PE文件
pImageDosHeader = (PIMAGE_DOS_HEADER)lpMemory;
if (pImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
MessageBox(hwndDlg, TEXT("打开的不是PE文件"), TEXT("提示"), MB_OK);
return TRUE;
};
pImageNtHeader = (PIMAGE_NT_HEADERS)((LPBYTE)pImageDosHeader + pImageDosHeader->e_lfanew);
if (pImageNtHeader->Signature != IMAGE_NT_SIGNATURE) {
MessageBox(hwndDlg, TEXT("打开的不是PE文件"), TEXT("提示"), MB_OK);
return TRUE;
};
// 清空编辑控件
SetDlgItemText(hwndDlg, IDC_EDIT_INFO, TEXT(""));
//PE文件基本信息、节区信息、数据块信息
GetBaseInfo(pImageNtHeader);
//导入表信息
GetImportTable(pImageDosHeader);
//导出表信息
GetExportTable(pImageDosHeader);
// 重定位表
GetRelocationTable(pImageDosHeader);
// 清理工作
UnmapViewOfFile(lpMemory);
CloseHandle(hFileMap);
CloseHandle(hFile);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
BOOL GetBaseInfo(PIMAGE_NT_HEADERS pImageNtHeader) {
PIMAGE_SECTION_HEADER pImageSectionHeader;
TCHAR szSectionName[IMAGE_SIZEOF_SHORT_NAME + 1] = { 0 }; // Unicode节区名称
TCHAR szBuf[256] = { 0 };
// 基本信息
// 如果是PE32+则把pImageNtHeader强制转换为PIMAGE_NT_HEADERS64
if (pImageNtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
wsprintf(szBuf, TEXT("运行平台:\t0x%04X\r\n节区数量:\t0x%04X\r\n创建时间:\t%s\r\n文件属性:\t0x%04X\r\n文件格式:\t0x%04X\r\n建议装载地址:\t0x%016I64X\r\n入口地址:\t0x%08X\r\n内存对齐:\t0x%08X\r\n文件对齐:\t0x%08X\r\n内存映像大小:\t0x%08X\r\n校验和:\t0x%08X\r\n数据目录个数:\t0x%08X\r\n\r\n"), ((PIMAGE_NT_HEADERS64)pImageNtHeader)->FileHeader.Machine, ((PIMAGE_NT_HEADERS64)pImageNtHeader)->FileHeader.NumberOfSections, GetDateTime(((PIMAGE_NT_HEADERS64)pImageNtHeader)->FileHeader.TimeDateStamp), ((PIMAGE_NT_HEADERS64)pImageNtHeader)->FileHeader.Characteristics, ((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.Magic, ((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.ImageBase, ((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.AddressOfEntryPoint, ((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.SectionAlignment, ((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.FileAlignment, ((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.SizeOfImage, ((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.CheckSum, ((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.NumberOfRvaAndSizes);
// 如果是PE则把pImageNtHeader强制转换为PIMAGE_NT_HEADERS32
else
wsprintf(szBuf, TEXT("运行平台:\t0x%04X\r\n节区数量:\t0x%04X\r\n创建时间:\t%s\r\n文件属性:\t0x%04X\r\n文件格式:\t0x%04X\r\n建议装载地址:\t0x%08X\r\n入口地址:\t0x%08X\r\n内存对齐:\t0x%08X\r\n文件对齐:\t0x%08X\r\n内存映像大小:\t0x%08X\r\n校验和:\t0x%08X\r\n数据目录个数:\t0x%08X\r\n\r\n"), ((PIMAGE_NT_HEADERS32)pImageNtHeader)->FileHeader.Machine, ((PIMAGE_NT_HEADERS32)pImageNtHeader)->FileHeader.NumberOfSections, GetDateTime(((PIMAGE_NT_HEADERS32)pImageNtHeader)->FileHeader.TimeDateStamp), ((PIMAGE_NT_HEADERS32)pImageNtHeader)->FileHeader.Characteristics, ((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.Magic, ((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.ImageBase, ((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.AddressOfEntryPoint, ((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.SectionAlignment, ((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.FileAlignment, ((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.SizeOfImage, ((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.CheckSum, ((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.NumberOfRvaAndSizes);
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
// 节区信息
wsprintf(szBuf, TEXT("节区名称\t节区 RVA\t节区 FOA\t实际大小\t对齐大小\t节区属性\r\n"));
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
// PE和PE32+的节表定位不同
if (pImageNtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
pImageSectionHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)pImageNtHeader + sizeof(IMAGE_NT_HEADERS32));
else
pImageSectionHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)pImageNtHeader + sizeof(IMAGE_NT_HEADERS64));
// 遍历节表
for (int i = 0; i < pImageNtHeader->FileHeader.NumberOfSections; i++) {
// 节区名称
MultiByteToWideChar(CP_UTF8, 0, (LPSTR)pImageSectionHeader, IMAGE_SIZEOF_SHORT_NAME, szSectionName, IMAGE_SIZEOF_SHORT_NAME);
// 其他信息
wsprintf(szBuf, TEXT("%-8s\t0x%08X\t0x%08X\t0x%08X\t0x%08X\t0x%08X\r\n"), szSectionName, pImageSectionHeader->VirtualAddress, pImageSectionHeader->PointerToRawData, pImageSectionHeader->Misc.VirtualSize, pImageSectionHeader->SizeOfRawData, pImageSectionHeader->Characteristics);
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
// 指向下一个节区信息结构
pImageSectionHeader++;
};
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)TEXT("\r\n"));
// 各种类型数据块的信息
wsprintf(szBuf, TEXT("索引\t\t数据目录\t\t数据的RVA\t数据的大小\t数据的FOA\t所处的节区\r\n"));
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
// 如果是PE32+则把pImageNtHeader强制转换为PIMAGE_NT_HEADERS64
if (pImageNtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
for (DWORD i = 0; i < ((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.NumberOfRvaAndSizes; i++)
if (((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.DataDirectory[i].Size != 0) {
// 所处的节区名称
MultiByteToWideChar(CP_UTF8, 0, GetSectionNameByRVA(pImageNtHeader, ((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.DataDirectory[i].VirtualAddress), IMAGE_SIZEOF_SHORT_NAME, szSectionName, IMAGE_SIZEOF_SHORT_NAME);
// 其他信息
wsprintf(szBuf, TEXT("%d\t\t%s\t0x%08X\t0x%08X\t0x%08X\t%s\r\n"), i, arrDataDirectory[i], ((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.DataDirectory[i].VirtualAddress, ((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.DataDirectory[i].Size, RVAToFOA(pImageNtHeader, ((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.DataDirectory[i].VirtualAddress), szSectionName);
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
};
}
// 如果是PE则把pImageNtHeader强制转换为PIMAGE_NT_HEADERS32
else
for (DWORD i = 0; i < ((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.NumberOfRvaAndSizes; i++)
if (((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.DataDirectory[i].Size != 0) {
// 所处的节区名称
MultiByteToWideChar(CP_UTF8, 0, GetSectionNameByRVA(pImageNtHeader, ((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.DataDirectory[i].VirtualAddress), IMAGE_SIZEOF_SHORT_NAME, szSectionName, IMAGE_SIZEOF_SHORT_NAME);
// 其他信息
wsprintf(szBuf, TEXT("%d\t\t%s\t0x%08X\t0x%08X\t0x%08X\t%s\r\n"), i, arrDataDirectory[i], ((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.DataDirectory[i].VirtualAddress, ((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.DataDirectory[i].Size, RVAToFOA(pImageNtHeader, ((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.DataDirectory[i].VirtualAddress), szSectionName);
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
};
return TRUE;
};
BOOL GetImportTable(PIMAGE_DOS_HEADER pImageDosHeader) {
PIMAGE_NT_HEADERS pImageNtHeader; // PE文件头起始地址
PIMAGE_IMPORT_DESCRIPTOR pImageImportDescriptor;// 导入表起始地址
PIMAGE_THUNK_DATA32 pImageThunkData32; // IMAGE_THUNK_DATA32数组起始地址
PIMAGE_THUNK_DATA64 pImageThunkData64; // IMAGE_THUNK_DATA64数组起始地址
PIMAGE_IMPORT_BY_NAME pImageImportByName; // IMAGE_IMPORT_BY_NAME结构指针
TCHAR szDllName[128] = { 0 }; // dll名称
TCHAR szFuncName[128] = { 0 }; // 函数名称
TCHAR szBuf[256] = { 0 };
TCHAR szImportTableHead[] = TEXT("\r\n\r\n导入表信息:\r\ndll文件名\t\t\t\t\t函数编号\t函数名称\r\n");
// PE文件头起始地址
pImageNtHeader = (PIMAGE_NT_HEADERS)((LPBYTE)pImageDosHeader + pImageDosHeader->e_lfanew);
// 如果是PE32+则把pImageNtHeader强制转换为PIMAGE_NT_HEADERS64
if (pImageNtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
// 是否有导入表(当然,没有的可能性不大)
if (((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.DataDirectory[1].Size == 0)
return FALSE;
// 导入表起始地址
pImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((LPBYTE)pImageDosHeader + RVAToFOA(pImageNtHeader, ((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.DataDirectory[1].VirtualAddress));
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szImportTableHead);
// 遍历导入表
while (pImageImportDescriptor->OriginalFirstThunk || pImageImportDescriptor->TimeDateStamp || pImageImportDescriptor->ForwarderChain || pImageImportDescriptor->Name || pImageImportDescriptor->FirstThunk) {
// dll名称
MultiByteToWideChar(CP_UTF8, 0, (LPSTR)((LPBYTE)pImageDosHeader + RVAToFOA(pImageNtHeader, pImageImportDescriptor->Name)), -1, szDllName, _countof(szDllName));
// IMAGE_THUNK_DATA64数组起始地址
pImageThunkData64 = (PIMAGE_THUNK_DATA64)((LPBYTE)pImageDosHeader + RVAToFOA(pImageNtHeader, pImageImportDescriptor->FirstThunk));
while (pImageThunkData64->u1.AddressOfData != 0) {
// 按序号导入还是按函数名称导入
// IMAGE_IMPORT_BY_NAME结构指针
pImageImportByName = (PIMAGE_IMPORT_BY_NAME)((LPBYTE)pImageDosHeader + RVAToFOA(pImageNtHeader, pImageThunkData64->u1.AddressOfData));
if (pImageThunkData64->u1.AddressOfData & IMAGE_ORDINAL_FLAG64) {
wsprintf(szFuncName, TEXT("按序号 0x%04X"), pImageThunkData64->u1.AddressOfData & 0xFFFF);
wsprintf(szBuf, TEXT("%-48s%s\r\n"), szDllName, szFuncName);
}
else {
MultiByteToWideChar(CP_UTF8, 0, pImageImportByName->Name, -1, szFuncName, _countof(szFuncName));
wsprintf(szBuf, TEXT("%-48s0x%04X\t\t%s\r\n"), szDllName, pImageImportByName->Hint, szFuncName);
};
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
// 指向下一个IMAGE_THUNK_DATA64结构
pImageThunkData64++;
};
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)TEXT("\r\n"));
// 指向下一个导入表描述符
pImageImportDescriptor++;
};
}
// 如果是PE则把pImageNtHeader强制转换为PIMAGE_NT_HEADERS32
else {
// 是否有导入表(当然,没有的可能性不大)
if (((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.DataDirectory[1].Size == 0)
return FALSE;
// 导入表起始地址
pImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((LPBYTE)pImageDosHeader + RVAToFOA(pImageNtHeader, ((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.DataDirectory[1].VirtualAddress));
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szImportTableHead);
// 遍历导入表
while (pImageImportDescriptor->OriginalFirstThunk || pImageImportDescriptor->TimeDateStamp || pImageImportDescriptor->ForwarderChain || pImageImportDescriptor->Name || pImageImportDescriptor->FirstThunk) {
// dll名称
MultiByteToWideChar(CP_UTF8, 0, (LPSTR)((LPBYTE)pImageDosHeader + RVAToFOA(pImageNtHeader, pImageImportDescriptor->Name)), -1, szDllName, _countof(szDllName));
// IMAGE_THUNK_DATA32数组起始地址
pImageThunkData32 = (PIMAGE_THUNK_DATA32)((LPBYTE)pImageDosHeader + RVAToFOA(pImageNtHeader, pImageImportDescriptor->FirstThunk));
while (pImageThunkData32->u1.AddressOfData != 0) {
// 按序号导入还是按函数名称导入
// IMAGE_IMPORT_BY_NAME结构指针
pImageImportByName = (PIMAGE_IMPORT_BY_NAME)((LPBYTE)pImageDosHeader + RVAToFOA(pImageNtHeader, pImageThunkData32->u1.AddressOfData));
if (pImageThunkData32->u1.AddressOfData & IMAGE_ORDINAL_FLAG32) {
wsprintf(szFuncName, TEXT("按序号 0x%04X"), pImageThunkData32->u1.AddressOfData & 0xFFFF);
wsprintf(szBuf, TEXT("%-48s%s\r\n"), szDllName, szFuncName);
}
else {
MultiByteToWideChar(CP_UTF8, 0, pImageImportByName->Name, -1, szFuncName, _countof(szFuncName));
wsprintf(szBuf, TEXT("%-48s0x%04X\t\t%s\r\n"), szDllName, pImageImportByName->Hint, szFuncName);
};
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
// 指向下一个IMAGE_THUNK_DATA32结构
pImageThunkData32++;
};
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)TEXT("\r\n"));
// 指向下一个导入表描述符
pImageImportDescriptor++;
};
};
return TRUE;
};
BOOL GetExportTable(PIMAGE_DOS_HEADER pImageDosHeader) {
PIMAGE_NT_HEADERS pImageNtHeader; // PE文件头起始地址
PIMAGE_EXPORT_DIRECTORY pImageExportDirectory; // 导出表目录结构的起始地址
PDWORD pAddressOfFunctions; // 导出函数地址表的起始地址
PWORD pAddressOfNameOrdinals; // 函数序数表的起始地址
PDWORD pAddressOfNames; // 函数名称地址表的起始地址
TCHAR szModuleName[128] = { 0 }; // 模块的原始文件名
TCHAR szFuncName[128] = { 0 }; // 函数名称
TCHAR szBuf[512] = { 0 };
TCHAR szExportTableHead[] = TEXT("\r\n导出表信息:\r\n");
TCHAR szExportTableFuncs[] = TEXT("函数序数\t函数地址\t函数名称\r\n");
// PE文件头起始地址
pImageNtHeader = (PIMAGE_NT_HEADERS)((LPBYTE)pImageDosHeader + pImageDosHeader->e_lfanew);
// PE和PE32+的导出表目录结构定位不同
if (pImageNtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
// 是否有导出表
if (((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.DataDirectory[0].Size == 0)
return FALSE;
pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((LPBYTE)pImageDosHeader + RVAToFOA(pImageNtHeader, ((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.DataDirectory[0].VirtualAddress));
}
else {
// 是否有导出表
if (((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.DataDirectory[0].Size == 0)
return FALSE;
pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((LPBYTE)pImageDosHeader + RVAToFOA(pImageNtHeader, ((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.DataDirectory[0].VirtualAddress));
}
// 导出函数地址表的起始地址
pAddressOfFunctions = (PDWORD)((LPBYTE)pImageDosHeader + RVAToFOA(pImageNtHeader, pImageExportDirectory->AddressOfFunctions));
// 函数序数表的起始地址
pAddressOfNameOrdinals = (PWORD)((LPBYTE)pImageDosHeader + RVAToFOA(pImageNtHeader, pImageExportDirectory->AddressOfNameOrdinals));
// 函数名称地址表的起始地址
pAddressOfNames = (PDWORD)((LPBYTE)pImageDosHeader + RVAToFOA(pImageNtHeader, pImageExportDirectory->AddressOfNames));
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szExportTableHead);
// 导出表基本信息
MultiByteToWideChar(CP_UTF8, 0, (LPSTR)((LPBYTE)pImageDosHeader + RVAToFOA(pImageNtHeader, pImageExportDirectory->Name)), -1, szModuleName, _countof(szModuleName));
wsprintf(szBuf, TEXT("模块原始文件名\t\t%s\r\n导出函数的起始序数\t0x%08X\r\n导出函数的总个数\t0x%08X\r\n按名称导出函数的个数\t0x%08X\r\n导出函数地址表的RVA\t0x%08X\r\n函数名称地址表的RVA\t0x%08X\r\n指向函数序数表的RVA\t0x%08X\r\n\r\n"), szModuleName, pImageExportDirectory->Base, pImageExportDirectory->NumberOfFunctions, pImageExportDirectory->NumberOfNames, pImageExportDirectory->AddressOfFunctions, pImageExportDirectory->AddressOfNames, pImageExportDirectory->AddressOfNameOrdinals);
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szExportTableFuncs);
// 遍历导出表中的所有导出函数
for (DWORD i = 0; i < pImageExportDirectory->NumberOfFunctions; i++) {
// 是否是按函数名称导出,遍历函数序数表
DWORD j;
for (j = 0; j < pImageExportDirectory->NumberOfNames; j++)
if (i == pAddressOfNameOrdinals[j]) {
// 获取函数名称
MultiByteToWideChar(CP_UTF8, 0, (LPSTR)((LPBYTE)pImageDosHeader + RVAToFOA(pImageNtHeader, pAddressOfNames[j])), -1, szFuncName, _countof(szFuncName));
break;
};
// 如果遍历完函数序数表也没找到索引i,就是按函数序数导出
if (j == pImageExportDirectory->NumberOfNames)
wsprintf(szFuncName, TEXT("按序数导出"));
if (pAddressOfFunctions[i]) {
wsprintf(szBuf, TEXT("0x%08X\t0x%08X\t%s\r\n"), pImageExportDirectory->Base + i, pAddressOfFunctions[i], szFuncName);
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
};
};
return TRUE;
};
BOOL GetRelocationTable(PIMAGE_DOS_HEADER pImageDosHeader) {
PIMAGE_NT_HEADERS pImageNtHeader; // PE文件头起始地址
PIMAGE_BASE_RELOCATION pImageBaseRelocation; // 重定位表的起始地址
PWORD pRelocationItem; // 重定位项数组的起始地址
DWORD dwRelocationItem; // 重定位项的个数
TCHAR szBuf[64] = { 0 };
TCHAR szRelocationTableHead[] = TEXT("\r\n重定位表信息:\r\n");
TCHAR szRelocationItemInfo[] = TEXT("类型\t重定位地址\t类型\t重定位地址\t类型\t重定位地址\t类型\t重定位地址\t");
// PE文件头起始地址
pImageNtHeader = (PIMAGE_NT_HEADERS)((LPBYTE)pImageDosHeader + pImageDosHeader->e_lfanew);
// PE和PE32+的重定位表的定位不同
if (pImageNtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
// 是否有重定位表
if (((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.DataDirectory[5].Size == 0)
return FALSE;
pImageBaseRelocation = (PIMAGE_BASE_RELOCATION)((LPBYTE)pImageDosHeader + RVAToFOA(pImageNtHeader, ((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.DataDirectory[5].VirtualAddress));
}
else {
// 是否有重定位表
if (((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.DataDirectory[5].Size == 0)
return FALSE;
pImageBaseRelocation = (PIMAGE_BASE_RELOCATION)((LPBYTE)pImageDosHeader + RVAToFOA(pImageNtHeader, ((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.DataDirectory[5].VirtualAddress));
};
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szRelocationTableHead);
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szRelocationItemInfo);
// 遍历重定位表
while (pImageBaseRelocation->VirtualAddress != 0) {
// 重定位项数组的起始地址
pRelocationItem = (PWORD)((LPBYTE)pImageBaseRelocation + sizeof(IMAGE_BASE_RELOCATION));
// 重定位项的个数
dwRelocationItem = (pImageBaseRelocation->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
for (DWORD i = 0; i < dwRelocationItem; i++) {
wsprintf(szBuf, TEXT("0x%X\t0x%08X\t"), pRelocationItem[i] >> 12, pImageBaseRelocation->VirtualAddress + (pRelocationItem[i] & 0x0FFF));
// 4组一行
if (i % 4 == 0) {
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)TEXT("\r\n"));
};
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szBuf);
};
// 页与页之间隔一行
SendMessage(g_hwndEdit, EM_SETSEL, -1, -1);
SendMessage(g_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)TEXT("\r\n"));
// 指向下一个重定位块结构
pImageBaseRelocation = (PIMAGE_BASE_RELOCATION)((LPBYTE)pImageBaseRelocation + pImageBaseRelocation->SizeOfBlock);
};
return TRUE;
};
/*********************************************************************************
* 函数功能:通过指定类型数据(例如导入表、导出表、重定位表等)的RVA得到FOA
* 输入参数的说明:
1. pImageNtHeader参数表示PE内存映射文件对象中PE文件头的起始地址,必须指定
2. dwTargetRVA参数表示目标类型数据的RVA,必须指定
* 返回值: 返回-1表示函数执行失败
**********************************************************************************/
INT RVAToFOA(PIMAGE_NT_HEADERS pImageNtHeader, DWORD dwTargetRVA) {
PIMAGE_SECTION_HEADER pImageSectionHeader;
INT iTargetFOA = -1;
// PE和PE32+的节表定位不同
if (pImageNtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
pImageSectionHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)pImageNtHeader + sizeof(IMAGE_NT_HEADERS32));
else
pImageSectionHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)pImageNtHeader + sizeof(IMAGE_NT_HEADERS64));
// 遍历节表
for (int i = 0; i < pImageNtHeader->FileHeader.NumberOfSections; i++) {
if ((dwTargetRVA >= pImageSectionHeader->VirtualAddress) && (dwTargetRVA <= (pImageSectionHeader->VirtualAddress + pImageSectionHeader->SizeOfRawData))) {
iTargetFOA = dwTargetRVA - pImageSectionHeader->VirtualAddress;
iTargetFOA += pImageSectionHeader->PointerToRawData;
};
// 指向下一个节区信息结构
pImageSectionHeader++;
};
return iTargetFOA;
};
/*********************************************************************************
* 函数功能:通过一个RVA值获取所在节区的名称
* 输入参数的说明:
1. pImageNtHeader参数表示PE内存映射文件对象中PE文件头的起始地址,必须指定
2. dwRVA参数表示一个RVA值,必须指定
* 返回值: 返回NULL表示函数执行失败,请注意返回的节区名称字符串并不一定以零结尾
**********************************************************************************/
LPSTR GetSectionNameByRVA(PIMAGE_NT_HEADERS pImageNtHeader, DWORD dwRVA) {
LPSTR lpSectionName = NULL;
PIMAGE_SECTION_HEADER pImageSectionHeader;
// PE和PE32+的节表定位不同
if (pImageNtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
pImageSectionHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)pImageNtHeader + sizeof(IMAGE_NT_HEADERS32));
else
pImageSectionHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)pImageNtHeader + sizeof(IMAGE_NT_HEADERS64));
// 遍历节表
for (int i = 0; i < pImageNtHeader->FileHeader.NumberOfSections; i++) {
if ((dwRVA >= pImageSectionHeader->VirtualAddress) && (dwRVA <= (pImageSectionHeader->VirtualAddress + pImageSectionHeader->SizeOfRawData)))
lpSectionName = (LPSTR)pImageSectionHeader;
// 指向下一个节区信息结构
pImageSectionHeader++;
};
return lpSectionName;
};
LPTSTR GetDateTime(DWORD dwTimeDateStamp) {
FILETIME ft, ftLocal;
SYSTEMTIME st;
ULARGE_INTEGER uli;
LPTSTR pszDateTime = new TCHAR[64];
st.wYear = 1970;
st.wMonth = 1;
st.wDay = 1;
st.wHour = 0;
st.wMinute = 0;
st.wSecond = 0;
st.wMilliseconds = 0;
// 系统时间转换为文件时间才可以加上已经逝去的时间dwTimeDateStamp
SystemTimeToFileTime(&st, &ft);
// 文件时间单位是1/1000 0000秒,即1000万分之1秒(100-nanosecond)
// 不要将指向FILETIME结构的指针强制转换为ULARGE_INTEGER *或__int64 *值
// 因为这可能导致64位Windows上的对齐错误
uli.HighPart = ft.dwHighDateTime;
uli.LowPart = ft.dwLowDateTime;
uli.QuadPart += (ULONGLONG)10000000 * dwTimeDateStamp;
ft.dwHighDateTime = uli.HighPart;
ft.dwLowDateTime = uli.LowPart;
// 将世界时间转换为本地时间(两个参数都是文件时间)
FileTimeToLocalFileTime(&ft, &ftLocal);
// 再将文件时间转换为系统时间
FileTimeToSystemTime(&ftLocal, &st);
// 转换为日期时间格式字符串
ZeroMemory(pszDateTime, 64);
wsprintf(pszDateTime, TEXT("%d年%0.2d月%0.2d日 %0.2d:%0.2d:%0.2d"), st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wMinute);
return pszDateTime;
};

模拟PE加载器

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
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
#include <windows.h>
#include <tchar.h>
#include "resource.h"
// 全局变量
HWND g_hwndDlg;
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 在dll内存映像中根据函数序数获取函数地址(RVA值)
INT GetFuncRvaByOrdinal(PIMAGE_DOS_HEADER pImageDosHeader, DWORD dwOrdinal);
// 在dll内存映像中根据函数名称获取函数地址(RVA值)
INT GetFuncRvaByName(PIMAGE_DOS_HEADER pImageDosHeader, LPCTSTR lpFuncName);
// 加载内存映射文件中的可执行文件到进程内存中执行
BOOL LoadExecutable(LPVOID lpMemory);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
// 打开文件所用变量
TCHAR szFileName[MAX_PATH] = { 0 };
OPENFILENAME ofn = { 0 };
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwndDlg;
ofn.lpstrFilter = TEXT("exe文件(*.exe)\0*.exe\0dll文件(*.dll)\0*.dll\0All(*.*)\0*.*\0");
ofn.nFilterIndex = 3;
ofn.lpstrFile = szFileName;
ofn.lpstrFile[0] = NULL;
ofn.nMaxFile = _countof(szFileName);
ofn.lpstrTitle = TEXT("请选择要打开的PE文件");
ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
// 内存映射文件所用变量
HANDLE hFile, hFileMap;
LARGE_INTEGER liFileSize;
LPVOID lpMemory;
// DOS头指针和PE文件头指针
PIMAGE_DOS_HEADER pImageDosHeader;
PIMAGE_NT_HEADERS pImageNtHeader;
switch (uMsg) {
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_BROWSE: {
if (GetOpenFileName(&ofn))
SetDlgItemText(hwndDlg, IDC_EDIT_FILENAME, szFileName);
break;
};
case IDC_BTN_LOAD: {
GetDlgItemText(g_hwndDlg, IDC_EDIT_FILENAME, szFileName, _countof(szFileName));
// 打开PE文件
hFile = CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
MessageBox(g_hwndDlg, TEXT("CreateFile函数调用失败"), TEXT("提示"), MB_OK);
return NULL;
}
else {
GetFileSizeEx(hFile, &liFileSize);
if (liFileSize.QuadPart == 0) {
MessageBox(g_hwndDlg, TEXT("文件大小为0"), TEXT("提示"), MB_OK);
return NULL;
};
};
// 为hFile文件对象创建一个文件映射内核对象
hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
if (!hFileMap) {
MessageBox(g_hwndDlg, TEXT("CreateFileMapping调用失败"), TEXT("提示"), MB_OK);
return NULL;
};
// 把文件映射对象hFileMap的全部映射到进程的虚拟地址空间中
lpMemory = MapViewOfFile(hFileMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
if (!lpMemory) {
MessageBox(g_hwndDlg, TEXT("MapViewOfFile调用失败"), TEXT("提示"), MB_OK);
return NULL;
};
// 打开的文件是不是PE文件
pImageDosHeader = (PIMAGE_DOS_HEADER)lpMemory;
if (pImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
MessageBox(hwndDlg, TEXT("打开的不是PE文件"), TEXT("提示"), MB_OK);
return TRUE;
};
pImageNtHeader = (PIMAGE_NT_HEADERS)((LPBYTE)pImageDosHeader + pImageDosHeader->e_lfanew);
if (pImageNtHeader->Signature != IMAGE_NT_SIGNATURE) {
MessageBox(hwndDlg, TEXT("打开的不是PE文件"), TEXT("提示"), MB_OK);
return TRUE;
};
// 加载可执行文件到进程内存中
LoadExecutable(lpMemory);
// 清理工作
UnmapViewOfFile(lpMemory);
CloseHandle(hFileMap);
CloseHandle(hFile);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
INT GetFuncRvaByOrdinal(PIMAGE_DOS_HEADER pImageDosHeader, DWORD dwOrdinal) {
PIMAGE_NT_HEADERS pImageNtHeader; // PE文件头起始地址
PIMAGE_EXPORT_DIRECTORY pImageExportDirectory; // 导出表目录结构的起始地址
PDWORD pAddressOfFunctions; // 导出函数地址表的起始地址
DWORD dwIndexAddressOfFunctions; // 函数在导出函数地址表中的索引
DWORD dwFuncRva; // 函数的RVA
// PE文件头起始地址
pImageNtHeader = (PIMAGE_NT_HEADERS)((LPBYTE)pImageDosHeader + pImageDosHeader->e_lfanew);
// PE和PE32+的导出表目录结构定位不同
if (pImageNtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((LPBYTE)pImageDosHeader + ((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.DataDirectory[0].VirtualAddress);
else
pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((LPBYTE)pImageDosHeader + ((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.DataDirectory[0].VirtualAddress);
// 函数在导出函数地址表中的索引
dwIndexAddressOfFunctions = dwOrdinal - pImageExportDirectory->Base;
if (dwIndexAddressOfFunctions > pImageExportDirectory->NumberOfFunctions)
return -1;
// 导出函数地址表的起始地址
pAddressOfFunctions = (PDWORD)((LPBYTE)pImageDosHeader + pImageExportDirectory->AddressOfFunctions);
dwFuncRva = pAddressOfFunctions[dwIndexAddressOfFunctions];
return dwFuncRva;
};
INT GetFuncRvaByName(PIMAGE_DOS_HEADER pImageDosHeader, LPCTSTR lpFuncName) {
PIMAGE_NT_HEADERS pImageNtHeader; // PE文件头起始地址
PIMAGE_EXPORT_DIRECTORY pImageExportDirectory; // 导出表目录结构的起始地址
PDWORD pAddressOfFunctions; // 导出函数地址表的起始地址
PWORD pAddressOfNameOrdinals; // 函数序数表的起始地址
PDWORD pAddressOfNames; // 函数名称地址表的起始地址
LPSTR lpFuncNameInExport; // 函数名称指针
TCHAR szFuncNameInExport[128]; // 宽字符函数名称
WORD wOrdinal; // 函数在导出函数地址表EAT在的索引
DWORD dwFuncRva; // 函数的RVA
// PE文件头起始地址
pImageNtHeader = (PIMAGE_NT_HEADERS)((LPBYTE)pImageDosHeader + pImageDosHeader->e_lfanew);
// PE和PE32+的导出表目录结构定位不同
if (pImageNtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((LPBYTE)pImageDosHeader + ((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.DataDirectory[0].VirtualAddress);
else
pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((LPBYTE)pImageDosHeader + ((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.DataDirectory[0].VirtualAddress);
// 导出函数地址表的起始地址
pAddressOfFunctions = (PDWORD)((LPBYTE)pImageDosHeader + pImageExportDirectory->AddressOfFunctions);
// 函数序数表的起始地址
pAddressOfNameOrdinals = (PWORD)((LPBYTE)pImageDosHeader + pImageExportDirectory->AddressOfNameOrdinals);
// 函数名称地址表的起始地址
pAddressOfNames = (PDWORD)((LPBYTE)pImageDosHeader + pImageExportDirectory->AddressOfNames);
// 遍历函数名称地址表
for (DWORD i = 0; i < pImageExportDirectory->NumberOfNames; i++) {
lpFuncNameInExport = (LPSTR)((LPBYTE)pImageDosHeader + pAddressOfNames[i]);
MultiByteToWideChar(CP_UTF8, 0, lpFuncNameInExport, -1, szFuncNameInExport, _countof(szFuncNameInExport));
if (_tcsicmp(szFuncNameInExport, lpFuncName) == 0) {
wOrdinal = pAddressOfNameOrdinals[i];
dwFuncRva = pAddressOfFunctions[wOrdinal];
return dwFuncRva;
};
};
return -1;
};
BOOL LoadExecutable(LPVOID lpMemory) { // lpMemory是PE内存映射文件基地址
PIMAGE_DOS_HEADER pImageDosHeader; // 内存映射文件中的DOS头起始地址
PIMAGE_NT_HEADERS pImageNtHeader; // 内存映射文件中的PE文件头起始地址
SIZE_T nSizeOfImage; // PE内存映像大小(基于内存对齐后的大小)
LPVOID lpBaseAddress; // 在本进程中分配内存用于装载可执行文件
DWORD dwSizeOfHeaders; // DOS头+PE头+节表的大小(基于内存对齐后的大小)
WORD wNumberOfSections; // 可执行文件的节区个数
PIMAGE_SECTION_HEADER pImageSectionHeader; // 节表的起始地址
// 获取PE内存映像大小
pImageDosHeader = (PIMAGE_DOS_HEADER)lpMemory;
pImageNtHeader = (PIMAGE_NT_HEADERS)((LPBYTE)pImageDosHeader + pImageDosHeader->e_lfanew);
nSizeOfImage = pImageNtHeader->OptionalHeader.SizeOfImage;
// 在本进程的内存地址空间中分配nSizeOfImage + 20字节大小的可读可写可执行内存
// 多出的20个字节后面会用到
lpBaseAddress = VirtualAlloc(NULL, nSizeOfImage + 20, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
ZeroMemory(lpBaseAddress, nSizeOfImage + 20);
// ***************************************************************************************
// 把可执行文件按pImageNtHeader.OptionalHeader.SectionAlignment对齐粒度映射到分配的内存中
dwSizeOfHeaders = pImageNtHeader->OptionalHeader.SizeOfHeaders;
wNumberOfSections = pImageNtHeader->FileHeader.NumberOfSections;
// 获取节表的起始地址
pImageSectionHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)pImageNtHeader + sizeof(IMAGE_NT_HEADERS));
// 加载DOS头+PE头+节表
memcpy_s(lpBaseAddress, dwSizeOfHeaders, (LPVOID)pImageDosHeader, dwSizeOfHeaders);
// 加载所有节区到节表中指定的RVA处
for (int i = 0; i < wNumberOfSections; i++) {
if (pImageSectionHeader->VirtualAddress == 0 || pImageSectionHeader->SizeOfRawData == 0) {
pImageSectionHeader++;
continue;
};
memcpy_s((LPBYTE)lpBaseAddress + pImageSectionHeader->VirtualAddress, pImageSectionHeader->SizeOfRawData, (LPBYTE)pImageDosHeader + pImageSectionHeader->PointerToRawData, pImageSectionHeader->SizeOfRawData);
pImageSectionHeader++;
};
// ***************************************************************************************
// 映射到进程中的DOS头和PE文件头起始地址
PIMAGE_DOS_HEADER pImageDosHeaderMap; // 映射到进程中的DOS头起始地址
PIMAGE_NT_HEADERS pImageNtHeaderMap; // 映射到进程中的PE文件头起始地址
pImageDosHeaderMap = (PIMAGE_DOS_HEADER)lpBaseAddress;
pImageNtHeaderMap = (PIMAGE_NT_HEADERS)((LPBYTE)pImageDosHeaderMap + pImageDosHeaderMap->e_lfanew);
// ***************************************************************************************
// 修正映射到进程中的PE内存映像的重定位代码
PIMAGE_BASE_RELOCATION pImageBaseRelocationMap; // 映射到进程中的重定位表的起始地址
PWORD pRelocationItem; // 重定位项数组的起始地址
DWORD dwRelocationItem; // 重定位项的个数
PDWORD pdwRelocationAddress; // PE重定位地址
PULONGLONG pullRelocationAddress; // PE32+重定位地址
DWORD dwRelocationDelta; // PE实际装入地址与建议装载地址的差值
ULONGLONG ullRelocationDelta; // PE32+实际装入地址与建议装载地址的差值
// 获取重定位表的起始地址
pImageBaseRelocationMap = (PIMAGE_BASE_RELOCATION)((LPBYTE)pImageDosHeaderMap + pImageNtHeaderMap->OptionalHeader.DataDirectory[5].VirtualAddress);
// 这里就不判断是否存在重定位表,因为通常都会存在
// 遍历重定位表
while (pImageBaseRelocationMap->VirtualAddress != 0) {
// 重定位项数组的起始地址
pRelocationItem = (PWORD)((LPBYTE)pImageBaseRelocationMap + sizeof(IMAGE_BASE_RELOCATION));
// 重定位项的个数
dwRelocationItem = (pImageBaseRelocationMap->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
for (DWORD i = 0; i < dwRelocationItem; i++) {
// 区分PE和PE32+的重定位
if (pRelocationItem[i] >> 12 == 3) {
pdwRelocationAddress = (PDWORD)((LPBYTE)pImageDosHeaderMap + pImageBaseRelocationMap->VirtualAddress + (pRelocationItem[i] & 0x0FFF));
dwRelocationDelta = (DWORD)pImageDosHeaderMap - pImageNtHeaderMap->OptionalHeader.ImageBase;
*pdwRelocationAddress += dwRelocationDelta;
}
else if (pRelocationItem[i] >> 12 == 0xA) {
pullRelocationAddress = (PULONGLONG)((LPBYTE)pImageDosHeaderMap + pImageBaseRelocationMap->VirtualAddress + (pRelocationItem[i] & 0x0FFF));
ullRelocationDelta = (ULONGLONG)pImageDosHeaderMap - pImageNtHeaderMap->OptionalHeader.ImageBase;
*pullRelocationAddress += ullRelocationDelta;
};
};
// 指向下一个重定位块结构
pImageBaseRelocationMap = (PIMAGE_BASE_RELOCATION)((LPBYTE)pImageBaseRelocationMap + pImageBaseRelocationMap->SizeOfBlock);
};
// ***************************************************************************************
// ***************************************************************************************
// 修正映射到进程中的PE内存映像的导入函数地址表IAT
PIMAGE_IMPORT_DESCRIPTOR pImageImportDescriptor;// 映射到进程中的导入表起始地址
PIMAGE_THUNK_DATA pImageThunkData; // IMAGE_THUNK_DATA数组起始地址
PIMAGE_IMPORT_BY_NAME pImageImportByName; // IMAGE_IMPORT_BY_NAME结构指针
TCHAR szDllName[MAX_PATH] = { 0 }; // dll名称
HMODULE hDll; // dll模块句柄
DWORD dwFuncAddress; // 32位函数地址
ULONGLONG ullFuncAddress; // 64位函数地址
// 是否有导入表(当然,没有的可能性不大)
if (pImageNtHeaderMap->OptionalHeader.DataDirectory[1].Size != 0) {
// 导入表起始地址
pImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((LPBYTE)pImageDosHeaderMap + pImageNtHeaderMap->OptionalHeader.DataDirectory[1].VirtualAddress);
// 遍历导入表
while (pImageImportDescriptor->OriginalFirstThunk || pImageImportDescriptor->TimeDateStamp || pImageImportDescriptor->ForwarderChain || pImageImportDescriptor->Name || pImageImportDescriptor->FirstThunk) {
// 在进程中加载dll
MultiByteToWideChar(CP_UTF8, 0, (LPSTR)((LPBYTE)pImageDosHeaderMap + pImageImportDescriptor->Name), -1, szDllName, _countof(szDllName));
hDll = LoadLibrary(szDllName);
// IMAGE_THUNK_DATA数组起始地址
pImageThunkData = (PIMAGE_THUNK_DATA)((LPBYTE)pImageDosHeaderMap + pImageImportDescriptor->FirstThunk);
while (pImageThunkData->u1.AddressOfData != 0) {
// 区分PE和PE32+的IAT
if (pImageNtHeaderMap->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
// 按序号导入还是按函数名称导入
if (pImageThunkData->u1.AddressOfData & IMAGE_ORDINAL_FLAG32)
// 获取加载的dll中函数的地址
dwFuncAddress = (DWORD)GetProcAddress(hDll, (LPSTR)(pImageThunkData->u1.AddressOfData & 0xFFFF));
else {
// IMAGE_IMPORT_BY_NAME结构指针
pImageImportByName = (PIMAGE_IMPORT_BY_NAME)((LPBYTE)pImageDosHeaderMap + pImageThunkData->u1.AddressOfData);
// 获取加载的dll中函数的地址
dwFuncAddress = (DWORD)GetProcAddress(hDll, (LPSTR)pImageImportByName->Name);
};
// 修复IAT项
pImageThunkData->u1.Function = dwFuncAddress;
}
else {
// 按序号导入还是按函数名称导入
if (pImageThunkData->u1.AddressOfData & IMAGE_ORDINAL_FLAG64)
// 获取加载的dll中函数的地址
ullFuncAddress = (ULONGLONG)GetProcAddress(hDll, (LPSTR)(pImageThunkData->u1.AddressOfData & 0xFFFF));
else {
// IMAGE_IMPORT_BY_NAME结构指针
pImageImportByName = (PIMAGE_IMPORT_BY_NAME)((LPBYTE)pImageDosHeaderMap + pImageThunkData->u1.AddressOfData);
// 获取加载的dll中函数的地址
ullFuncAddress = (ULONGLONG)GetProcAddress(hDll, (LPSTR)pImageImportByName->Name);
};
// 修复IAT项
pImageThunkData->u1.Function = ullFuncAddress;
};
// 指向下一个IMAGE_THUNK_DATA结构
pImageThunkData++;
};
// 指向下一个导入表描述符
pImageImportDescriptor++;
};
};
// ***************************************************************************************
// ***************************************************************************************
// 修改建议装载地址,并执行可执行文件
LPVOID lpExeEntry; // 可执行文件入口点
if (pImageNtHeaderMap->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
((PIMAGE_NT_HEADERS32)pImageNtHeaderMap)->OptionalHeader.ImageBase = (DWORD)lpBaseAddress;
lpExeEntry = (LPVOID)((LPBYTE)pImageDosHeaderMap + ((PIMAGE_NT_HEADERS32)pImageNtHeaderMap)->OptionalHeader.AddressOfEntryPoint);
}
else {
((PIMAGE_NT_HEADERS64)pImageNtHeaderMap)->OptionalHeader.ImageBase = (ULONGLONG)lpBaseAddress;
lpExeEntry = (LPVOID)((LPBYTE)pImageDosHeaderMap + ((PIMAGE_NT_HEADERS64)pImageNtHeaderMap)->OptionalHeader.AddressOfEntryPoint);
};
// 如果本程序编译为64位,不支持内联汇编,因此我们采取直接写入可执行机器码的方式执行exe
#ifndef _WIN64
// mov eax, 0x12345678
// jmp eax
BYTE bDataJmp[7] = { 0xB8, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0 };
*(PINT_PTR)(bDataJmp + 1) = (INT_PTR)lpExeEntry;
memcpy_s((LPBYTE)lpBaseAddress + nSizeOfImage, 7, bDataJmp, 7);
#else
// mov rax, 0x1234567812345678
// jmp rax
BYTE bDataJmp[12] = { 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0 };
*(PINT_PTR)(bDataJmp + 2) = (INT_PTR)lpExeEntry;
memcpy_s((LPBYTE)lpBaseAddress + nSizeOfImage, 12, bDataJmp, 12);
#endif
// 可以根据每个节区的属性设置其对应内存页的内存保护属性,此处省略
// 是exe还是dll,如果是exe则执行上面的“jmp 入口地址”指令,否则执行DllMain
if (pImageNtHeaderMap->FileHeader.Characteristics & IMAGE_FILE_DLL) {
// 执行DllMain入口点函数
typedef BOOL(APIENTRY* pfnDllMain)(HMODULE hModule, DWORD ulreason, LPVOID lpReserved);
pfnDllMain fnDllMain = (pfnDllMain)(lpExeEntry);
fnDllMain((HMODULE)lpBaseAddress, DLL_PROCESS_ATTACH, 0);
// 执行一个导出函数试试
typedef VOID(*pfnShowMessage)();
// 如果调用GetProcAddress函数获取ShowMessage函数的地址会提示找不到指定的模块
/*pfnShowMessage fnShowMessage = (pfnShowMessage) GetProcAddress((HMODULE)lpBaseAddress, "ShowMessage");*/
// GetFuncRvaByName是自定义函数,用于获取指定函数的RVA
pfnShowMessage fnShowMessage = (pfnShowMessage)((LPBYTE)lpBaseAddress + GetFuncRvaByName((PIMAGE_DOS_HEADER)lpBaseAddress, TEXT("ShowMessage")));
fnShowMessage();
}
else {
// 跳转到exe入口点执行
typedef VOID(WINAPI* pfnExe)();
pfnExe fnExe = (pfnExe)((LPBYTE)lpBaseAddress + nSizeOfImage);
fnExe();
};
// ***************************************************************************************
return TRUE;
};

线程局部存储表

线程局部存储表是个TLS目录IMAGE_TLS_DIRECTORY64结构,TLS模板时存放所有TLS变量初始化值得数据块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct _IMAGE_TLS_DIRECTORY64 {
ULONGLONG StartAddressOfRawData; //指向TLS模板起始地址 VA
ULONGLONG EndAddressOfRawData; //指向TLS模板结束地址 VA
ULONGLONG AddressOfIndex; //指向TLS索引DWORD数组 VA
ULONGLONG AddressOfCallBacks; //指向TLS回调函数指针的数组 PIMAGE_TLS_CALLBACK类型 VA
DWORD SizeOfZeroFill; //TLS模板之后填充0的个数
union {
DWORD Characteristics; //TLS标志
struct {
DWORD Reserved0 : 20;
DWORD Alignment : 4;
DWORD Reserved1 : 8;
} DUMMYSTRUCTNAME;
} DUMMYUNIONNAME;
} IMAGE_TLS_DIRECTORY64;

AddressOfCallBacks最后以NULL指针结尾。TLS回调函数定义格式如下:

1
2
3
4
5
VOID NTAPI TlsCallback(
PVOID DllHandle,
DWORD Reason, //回调函数被调用原因 同DllMain
PVOID Reserved
);

例如添加TLS回调函数:

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
#include <windows.h>
#include "resource.h"
// 宏定义
#define THREADCOUNT 5
// 全局变量
__declspec(thread) LPVOID gt_lpData = (LPVOID)0x12345678;// 赋个初值是为了分析TLS表的时候方便查看
HWND g_hwndDlg;
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 线程函数
DWORD WINAPI ThreadProc(LPVOID lpParameter);
// TLS回调函数
VOID NTAPI TlsCallback(PVOID DllHandle, DWORD Reason, PVOID Reserved);
// 注册TLS回调函数
#pragma data_seg(".CRT$XLB")
PIMAGE_TLS_CALLBACK pTlsCallback = TlsCallback;
#pragma data_seg()
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
HANDLE hThread[THREADCOUNT] = { 0 };
switch (uMsg) {
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_OK: {
// 创建THREADCOUNT个线程
SetDlgItemText(g_hwndDlg, IDC_EDIT_TLSSLOTS, TEXT(""));
for (int i = 0; i < THREADCOUNT; i++)
if ((hThread[i] = CreateThread(NULL, 0, ThreadProc, (LPVOID)i, 0, NULL)) != NULL)
CloseHandle(hThread[i]);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
TCHAR szBuf[64] = { 0 };
gt_lpData = new BYTE[256];
ZeroMemory(gt_lpData, 256);
// 每个线程的静态TLS数据显示到编辑控件中
wsprintf(szBuf, TEXT("线程%d的gt_lpData值:0x%p\r\n"), (INT)lpParameter, gt_lpData);
SendMessage(GetDlgItem(g_hwndDlg, IDC_EDIT_TLSSLOTS), EM_SETSEL, -1, -1);
SendMessage(GetDlgItem(g_hwndDlg, IDC_EDIT_TLSSLOTS), EM_REPLACESEL, TRUE, (LPARAM)szBuf);
delete[]gt_lpData;
return 0;
};
VOID NTAPI TlsCallback(PVOID DllHandle, DWORD Reason, PVOID Reserved) {
switch (Reason) {
case DLL_PROCESS_ATTACH: {
// 启动了一个新进程(包括第一个线程)
MessageBox(g_hwndDlg, TEXT("我是TLS回调函数"), TEXT("提示"), MB_OK);
break;
};
case DLL_PROCESS_DETACH: {
// 进程将要被终止(包括第一个线程)
};
case DLL_THREAD_ATTACH: {
// 创建了一个新线程,创建所有线程时都会发送这个通知,除了第一个线程
};
case DLL_THREAD_DETACH: {
// 线程将要被终止,终止所有线程时都会发送这个通知,除了第一个线程
break;
};
};
return;
};

其中节区名叫“.CRT$XLB”,CRT表示使用C运行时机制,X为随机标识,L表示TLS Callback Section,B可以是除A和Z外任意一个字母。当编译为Release时,需要项目属性->C/C++->优化->全程序优化设为否,否则TLS回调函数不会被调用。

加载配置信息表

加载配置信息表是一个加载配置目录IMAGE_LOAD_CONFIG_DIRECTORY64。该表用作异常处理,存放了SEH各种异常句柄。程序发生异常时,系统根据异常类别进行分发处理,并根据句柄实施程序流程转向。

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
typedef struct _IMAGE_LOAD_CONFIG_DIRECTORY64 {
DWORD Size; //该结构大小
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD GlobalFlagsClear;
DWORD GlobalFlagsSet;
DWORD CriticalSectionDefaultTimeout;
ULONGLONG DeCommitFreeBlockThreshold;
ULONGLONG DeCommitTotalFreeThreshold;
ULONGLONG LockPrefixTable;
ULONGLONG MaximumAllocationSize;
ULONGLONG VirtualMemoryThreshold;
ULONGLONG ProcessAffinityMask;
DWORD ProcessHeapFlags;
WORD CSDVersion;
WORD DependentLoadFlags;
ULONGLONG EditList;
ULONGLONG SecurityCookie;
ULONGLONG SEHandlerTable; //指向SEH异常处理程序RVA数组 VA
ULONGLONG SEHandlerCount; //SEH异常处理程序个数
ULONGLONG GuardCFCheckFunctionPointer;
ULONGLONG GuardCFDispatchFunctionPointer;
ULONGLONG GuardCFFunctionTable;
ULONGLONG GuardCFFunctionCount;
DWORD GuardFlags;
IMAGE_LOAD_CONFIG_CODE_INTEGRITY CodeIntegrity;
ULONGLONG GuardAddressTakenIatEntryTable;
ULONGLONG GuardAddressTakenIatEntryCount;
ULONGLONG GuardLongJumpTargetTable;
ULONGLONG GuardLongJumpTargetCount;
ULONGLONG DynamicValueRelocTable;
ULONGLONG CHPEMetadataPointer;
ULONGLONG GuardRFFailureRoutine;
ULONGLONG GuardRFFailureRoutineFunctionPointer;
DWORD DynamicValueRelocTableOffset;
WORD DynamicValueRelocTableSection;
WORD Reserved2;
ULONGLONG GuardRFVerifyStackPointerFunctionPointer;
DWORD HotPatchTableOffset;
DWORD Reserved3;
ULONGLONG EnclaveConfigurationPointer;
ULONGLONG VolatileMetadataPointer;
ULONGLONG GuardEHContinuationTable;
ULONGLONG GuardEHContinuationCount;
ULONGLONG GuardXFGCheckFunctionPointer;
ULONGLONG GuardXFGDispatchFunctionPointer;
ULONGLONG GuardXFGTableDispatchFunctionPointer;
ULONGLONG CastGuardOsDeterminedFailureMode;
ULONGLONG GuardMemcpyFunctionPointer;
} IMAGE_LOAD_CONFIG_DIRECTORY64, * PIMAGE_LOAD_CONFIG_DIRECTORY64;

资源表

资源组织方式类似文件系统的目录组织方式。共有3层目录结构,每层下有若干个目录,每个目录下有若干个目录入口,每个入口结构指向下一层的目录。第1层下只有一个目录,该目录按照资源类型划分,如光标、图标和菜单等入口结构。第2层按照资源ID划分,每个目录下有多个该资源类型的不同ID的资源。第3层按照代码页划分,如简体中文、繁体中文和英语等,该层目录下的个目录入口指向相对应资源数据入口。各资源数据入口指向资源数据。

每个资源目录为IMAGE_RESOURCE_DIRECTORY结构,每个目录入口为IMAGE_RESOURCE_DIRECTORY_ENTRY结构,每个资源数据入口为IMAGE_RESOURCE_DATA_ENTRY结构。

资源目录IMAGE_RESOURCE_DIRECTORY结构如下。用于第1层时,标准资源类型被解释为255一下的一个ID数字,自定义资源结构可以是个字符串或255~65535中ID数字,即NumberOfNamedEntries+NumberOfIdEntries为DirectoryEntries数组元素个数。用于第2层时同理,资源ID可以是一个字符串或1~65525之间的ID数字。用于第3层时,每种标准语言都有预定义ID,如简体中文为0x0804、英语为0x0409等。

对于OffsetToData字段,当在第1或2层时,该字段最高位为1,剩余低位值为一个指向资源目录表的相对资源表的偏移量;当在第3层时,该字段最高位为0,指向资源数据入口结构的相对资源表的偏移量。

1
2
3
4
5
6
7
8
9
typedef struct _IMAGE_RESOURCE_DIRECTORY {
DWORD Characteristics; //资源标志 0
DWORD TimeDateStamp; //资源编译器创建资源的时间戳 0
WORD MajorVersion; //主版本号 0
WORD MinorVersion; //次版本号 0
WORD NumberOfNamedEntries; //名称命名的资源目录入口结构个数
WORD NumberOfIdEntries; //ID命名的资源目录入口结构个数
// IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[]; //紧跟资源目录入口结构数组
} IMAGE_RESOURCE_DIRECTORY, * PIMAGE_RESOURCE_DIRECTORY;

资源目录入口IMAGE_RESOURCE_DIRECTORY_ENTRY结构如下。用于第1层时,若为标准资源类型,Name最高位为0,剩余低位表示255以下ID数字;若为自定义资源类型,Name最高位为1,剩余低位为指向相对资源表的IMAGE_RESOURCE_DIR_STRING_U结构偏移量,也可以为255~65535中一个ID数字。用于第二层时,Name表示资源ID,或指向相对资源表的IMAGE_RESOURCE_DIRECTORY_ENTRY结构偏移量。用于第3层时表示代码页ID。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
union {
struct {
DWORD NameOffset : 31;
DWORD NameIsString : 1;
} DUMMYSTRUCTNAME;
DWORD Name; //资源类型或资源ID或代码页ID
WORD Id;
} DUMMYUNIONNAME;
union {
DWORD OffsetToData; //资源数据入口结构或指向下一个资源目录表
struct {
DWORD OffsetToDirectory : 31;
DWORD DataIsDirectory : 1;
} DUMMYSTRUCTNAME2;
} DUMMYUNIONNAME2;
} IMAGE_RESOURCE_DIRECTORY_ENTRY, * PIMAGE_RESOURCE_DIRECTORY_ENTRY;

当为自定义资源类型时,NameString表示资源类型Unicode字符串:

1
2
3
4
typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
WORD Length; //Unicode字符串长度
WCHAR NameString[1]; //Unicode字符串 不以0结尾
} IMAGE_RESOURCE_DIR_STRING_U, * PIMAGE_RESOURCE_DIR_STRING_U;

资源数据入口IMAGE_RESOURCE_DATA_ENTRY结构如下。

1
2
3
4
5
6
typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
DWORD OffsetToData; //资源数据块RVA
DWORD Size; //资源数据块大小 单位字节
DWORD CodePage; //用于解码资源数据中码位值的Unicode代码页 0
DWORD Reserved; //保留
} IMAGE_RESOURCE_DATA_ENTRY, * PIMAGE_RESOURCE_DATA_ENTRY;

预定义资源类型有:

枚举值 含义
RT_CURSOR 光标
RT_BITMAP 位图
RT_ICON 图标
RT_MENU 菜单
RT_DIALOG 对话框
RT_STRING 字符串表
RT_FONTDIR 字体目录
RT_FONT 字体
RT_ACCELERATOR 加速键
RT_RCDATA 应用程序定义的资源
RT_MESSAGETABLE 消息表
RT_GROUP_CURSOR 光标组
RT_GROUP_ICON 图标组
RT_VERSION 程序版本
RT_DLGINCLUDE 提供符号名称的头文件
RT_PLUGPLAY 即插即用资源
RT_VXD VXD
RT_ANICURSOR 动态光标
RT_ANIICON 动态图标
RT_HTML HTML
RT_MANIFEST 清单文件

常见语言代码页ID有:

ID 含义
0x0000 中性语言
0x0400 程序默认语言
0x0404 中文 中国台湾
0x0804 中文 中国
0x0C04 中文 中国香港
0x1004 中文 新加坡
0x0409 英语 美国
0x0809 英语 英国
0x0411 日语
0x412 韩语
0x0412 俄语

获取方式:

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
#include <windows.h>
#include <CommCtrl.h>
#include <strsafe.h>
#include "resource.h"
// 全局变量
HWND g_hwndDlg; // 窗口句柄
HWND g_hwndTree; // 树视图控件窗口句柄
HWND g_hwndEdit; // 多行编辑控件窗口句柄
LPCTSTR arrResType[] = { TEXT("未知类型"), TEXT("光标"), TEXT("位图"), TEXT("图标"),TEXT("菜单"), TEXT("对话框"), TEXT("字符串表"), TEXT("字体目录"), TEXT("字体"),TEXT("加速键"), TEXT("程序自定义资源"), TEXT("消息表"), TEXT("光标组"), TEXT("未知类型"),TEXT("图标组"), TEXT("未知类型"), TEXT("程序版本"), TEXT("提供符号名称的头文件"),TEXT("未知类型"), TEXT("即插即用资源"), TEXT("VXD"), TEXT("动态光标"), TEXT("动态图标"),TEXT("HTML"), TEXT("清单文件") };
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 通过指定类型数据的RVA得到FOA
INT RVAToFOA(PIMAGE_NT_HEADERS pImageNtHeader, DWORD dwTargetRVA);
// 获取资源信息
BOOL GetResourceInfo(PIMAGE_RESOURCE_DIRECTORY pImageRes, PIMAGE_RESOURCE_DIRECTORY pImageResDir,HTREEITEM hTreeParent, DWORD dwLevel);
// 获取选定资源的信息
BOOL GetSelectedResData(PIMAGE_DOS_HEADER pImageDosHeader, LPBYTE lpResData, DWORD dwResSize);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
// 打开文件所用变量
TCHAR szFile[MAX_PATH] = { 0 };
OPENFILENAME ofn = { 0 };
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwndDlg;
ofn.lpstrFilter = TEXT("exe文件(*.exe)\0*.exe\0dll文件(*.dll)\0*.dll\0All(*.*)\0*.*\0");
ofn.nFilterIndex = 3;
ofn.lpstrFile = szFile;
ofn.lpstrFile[0] = NULL;
ofn.nMaxFile = _countof(szFile);
ofn.lpstrTitle = TEXT("请选择要打开的PE文件");
ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
// 内存映射文件所用变量
static HANDLE hFile, hFileMap;
static LPVOID lpMemory;
LARGE_INTEGER liFileSize;
// DOS头指针和PE文件头指针
static PIMAGE_DOS_HEADER pImageDosHeader;
static PIMAGE_NT_HEADERS pImageNtHeader;
// 第1层目录中的资源目录结构起始地址,也就是资源表的起始地址
PIMAGE_RESOURCE_DIRECTORY pImageRes;
// 多行编辑控件字体
HFONT hFont;
// 调整树视图控件和多行编辑控件窗口大小之用
static RECT rectWindow;
RECT rectTree, rectEdit;
static int nWidthTree, nHeightTree, nWidthEdit, nHeightEdit;
int cx, cy;
switch (uMsg) {
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;
g_hwndTree = GetDlgItem(hwndDlg, IDC_TREE_RESOURCE);
g_hwndEdit = GetDlgItem(hwndDlg, IDC_EDIT_RESOURCE);
// 设置多行编辑控件为宋体
hFont = CreateFont(12, 0, 0, 0, 0, 0, 0, 0, GB2312_CHARSET, 0, 0, 0, 0, TEXT("宋体"));
SendMessage(g_hwndEdit, WM_SETFONT, (WPARAM)hFont, FALSE);
DeleteObject(hFont);
// 默认情况下编辑控件最大缓冲区大小约为32KB个字符,设为不限大小
SendMessage(g_hwndEdit, EM_SETLIMITTEXT, 0, 0);
// 保存程序窗口大小
GetClientRect(hwndDlg, &rectWindow);
// 保存树视图控件和多行编辑控件的宽度高度
GetWindowRect(g_hwndTree, &rectTree);
nWidthTree = rectTree.right - rectTree.left;
nHeightTree = rectTree.bottom - rectTree.top;
GetWindowRect(g_hwndEdit, &rectEdit);
nWidthEdit = rectEdit.right - rectEdit.left;
nHeightEdit = rectEdit.bottom - rectEdit.top;
return TRUE;
};
case WM_SIZE: {
cx = LOWORD(lParam) - rectWindow.right;
cy = HIWORD(lParam) - rectWindow.bottom;
SetWindowPos(g_hwndTree, NULL, 0, 0, nWidthTree, nHeightEdit + cy, SWP_NOZORDER | SWP_NOMOVE);
SetWindowPos(g_hwndEdit, NULL, 0, 0, nWidthEdit + cx, nHeightEdit + cy, SWP_NOZORDER | SWP_NOMOVE);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_BROWSE: {
if (GetOpenFileName(&ofn))
SetDlgItemText(hwndDlg, IDC_EDIT_FILENAME, szFile);
break;
};
case IDC_BTN_GET: {
// 打开一个PE文件
GetDlgItemText(hwndDlg, IDC_EDIT_FILENAME, szFile, _countof(szFile));
hFile = CreateFile(szFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
MessageBox(hwndDlg, TEXT("CreateFile函数调用失败"), TEXT("提示"), MB_OK);
return TRUE;
}
else {
GetFileSizeEx(hFile, &liFileSize);
if (liFileSize.QuadPart == 0) {
MessageBox(hwndDlg, TEXT("文件大小为0"), TEXT("提示"), MB_OK);
return TRUE;
};
};
// 为hFile文件对象创建一个文件映射内核对象
hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (!hFileMap) {
MessageBox(hwndDlg, TEXT("CreateFileMapping调用失败"), TEXT("提示"), MB_OK);
return TRUE;
};
// 把文件映射对象hFileMap的全部映射到进程的虚拟地址空间中
lpMemory = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
if (!lpMemory) {
MessageBox(hwndDlg, TEXT("MapViewOfFile调用失败"), TEXT("提示"), MB_OK);
return TRUE;
};
// 打开的文件是不是PE文件
pImageDosHeader = (PIMAGE_DOS_HEADER)lpMemory;
if (pImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
MessageBox(hwndDlg, TEXT("打开的不是PE文件"), TEXT("提示"), MB_OK);
return TRUE;
};
pImageNtHeader = (PIMAGE_NT_HEADERS)((LPBYTE)pImageDosHeader + pImageDosHeader->e_lfanew);
if (pImageNtHeader->Signature != IMAGE_NT_SIGNATURE) {
MessageBox(hwndDlg, TEXT("打开的不是PE文件"), TEXT("提示"), MB_OK);
return TRUE;
};
// 第1层目录中的资源目录结构起始地址,区分PE和PE32+
if (pImageNtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
pImageRes = (PIMAGE_RESOURCE_DIRECTORY)((LPBYTE)pImageDosHeader + RVAToFOA(pImageNtHeader, ((PIMAGE_NT_HEADERS32)pImageNtHeader)->OptionalHeader.DataDirectory[2].VirtualAddress));
else
pImageRes = (PIMAGE_RESOURCE_DIRECTORY)((LPBYTE)pImageDosHeader + RVAToFOA(pImageNtHeader, ((PIMAGE_NT_HEADERS64)pImageNtHeader)->OptionalHeader.DataDirectory[2].VirtualAddress));
// 清空编辑控件
SetDlgItemText(hwndDlg, IDC_EDIT_RESOURCE, TEXT(""));
// 获取资源信息
GetResourceInfo(pImageRes, pImageRes, TVI_ROOT, 1);
break;
};
case IDCANCEL: {
// 清理工作
UnmapViewOfFile(lpMemory);
CloseHandle(hFileMap);
CloseHandle(hFile);
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
case WM_NOTIFY: {
switch (((LPNMHDR)lParam)->code) {
case TVN_SELCHANGED: {
LPNMTREEVIEW lpnmTreeView;
LPBYTE lpResData; // 所选择资源项的资源数据起始地址
DWORD dwResSize; // 所选择资源项的资源数据大小
lpnmTreeView = (LPNMTREEVIEW)lParam;
if (lpnmTreeView->itemNew.lParam != 0) {
lpResData = (LPBYTE)pImageDosHeader + RVAToFOA(pImageNtHeader, ((PIMAGE_RESOURCE_DATA_ENTRY)(lpnmTreeView->itemNew.lParam))->OffsetToData);
dwResSize = ((PIMAGE_RESOURCE_DATA_ENTRY)(lpnmTreeView->itemNew.lParam))->Size;
GetSelectedResData(pImageDosHeader, lpResData, dwResSize);
};
break;
};
};
return TRUE;
};
};
return FALSE;
};
INT RVAToFOA(PIMAGE_NT_HEADERS pImageNtHeader, DWORD dwTargetRVA) {
PIMAGE_SECTION_HEADER pImageSectionHeader;
INT iTargetFOA = -1;
// PE和PE32+的节表定位不同
if (pImageNtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
pImageSectionHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)pImageNtHeader + sizeof(IMAGE_NT_HEADERS32));
else
pImageSectionHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)pImageNtHeader + sizeof(IMAGE_NT_HEADERS64));
// 遍历节表
for (int i = 0; i < pImageNtHeader->FileHeader.NumberOfSections; i++) {
if ((dwTargetRVA >= pImageSectionHeader->VirtualAddress) && (dwTargetRVA <= (pImageSectionHeader->VirtualAddress + pImageSectionHeader->SizeOfRawData))) {
iTargetFOA = dwTargetRVA - pImageSectionHeader->VirtualAddress;
iTargetFOA += pImageSectionHeader->PointerToRawData;
};
// 指向下一个节区信息结构
pImageSectionHeader++;
};
return iTargetFOA;
};
/*********************************************************************************
* 函数功能: 获取资源信息
* 输入参数的说明:
1. pImageRes参数表示第1层目录中的资源目录结构起始地址,也就是资源表的起始地址,必须指定
2. pImageResDir参数表示第1~3层目录中的资源目录结构起始地址,必须指定
3. hTreeParent参数表示树视图控件中父节点的句柄,必须指定
4. dwLevel参数指定为数值1~3,表示当前调用本函数是为了获取第几层目录的信息,必须指定
* 该函数为递归函数
**********************************************************************************/
BOOL GetResourceInfo(PIMAGE_RESOURCE_DIRECTORY pImageRes, PIMAGE_RESOURCE_DIRECTORY pImageResDir, HTREEITEM hTreeParent, DWORD dwLevel) {
PIMAGE_RESOURCE_DIRECTORY pImageResDirSub; // 下一层资源目录结构起始地址
PIMAGE_RESOURCE_DIRECTORY_ENTRY pImageResDirEntry; // 资源目录入口结构数组起始地址
WORD wNums; // 资源目录入口结构数组个数
PIMAGE_RESOURCE_DATA_ENTRY pImageResDataEntry; // 资源数据入口结构起始地址
PIMAGE_RESOURCE_DIR_STRING_U pString;
HTREEITEM hTree;
TVINSERTSTRUCT tvi = { 0 };
TCHAR szResType[128] = { 0 }, szResID[128] = { 0 }, szLanguageID[128] = { 0 };
TCHAR szBuf[256] = { 0 };
// 资源目录入口结构数组起始地址
pImageResDirEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)((LPBYTE)pImageResDir + sizeof(IMAGE_RESOURCE_DIRECTORY));
// 资源目录入口结构数组个数
wNums = pImageResDir->NumberOfNamedEntries + pImageResDir->NumberOfIdEntries;
tvi.item.mask = TVIF_TEXT;
tvi.hInsertAfter = TVI_LAST;
tvi.hParent = hTreeParent;
if (dwLevel == 1) {
// 遍历资源目录入口结构数组
for (WORD i = 0; i < wNums; i++) {
// 资源类型
if (pImageResDirEntry[i].Name & 0x80000000) {
pString = (PIMAGE_RESOURCE_DIR_STRING_U)((LPBYTE)pImageRes + (pImageResDirEntry[i].Name & 0x7FFFFFFF));
StringCchCopy(szResType, pString->Length + 1, pString->NameString);
}
else {
if (LOWORD(pImageResDirEntry[i].Name) <= 24)
wsprintf(szResType, TEXT("%s"), arrResType[LOWORD(pImageResDirEntry[i].Name)]);
else
wsprintf(szResType, TEXT("%d(自定义ID)"), LOWORD(pImageResDirEntry[i].Name));
};
tvi.item.pszText = szResType;
hTree = (HTREEITEM)SendMessage(g_hwndTree, TVM_INSERTITEM, 0, (LPARAM)&tvi);
// 递归进入第2层
pImageResDirSub = (PIMAGE_RESOURCE_DIRECTORY)((LPBYTE)pImageRes + (pImageResDirEntry[i].OffsetToData & 0x7FFFFFFF));
GetResourceInfo(pImageRes, pImageResDirSub, hTree, 2);
};
}
else if (dwLevel == 2) {
// 遍历资源目录入口结构数组
for (WORD i = 0; i < wNums; i++) {
// 资源ID
if (pImageResDirEntry[i].Name & 0x80000000) {
pString = (PIMAGE_RESOURCE_DIR_STRING_U)((LPBYTE)pImageRes + (pImageResDirEntry[i].Name & 0x7FFFFFFF));
StringCchCopy(szResID, pString->Length + 1, pString->NameString);
}
else
wsprintf(szResID, TEXT("%d"), LOWORD(pImageResDirEntry[i].Name));
tvi.item.pszText = szResID;
hTree = (HTREEITEM)SendMessage(g_hwndTree, TVM_INSERTITEM, 0, (LPARAM)&tvi);
// 递归进入第3层
pImageResDirSub = (PIMAGE_RESOURCE_DIRECTORY)((LPBYTE)pImageRes + (pImageResDirEntry[i].OffsetToData & 0x7FFFFFFF));
GetResourceInfo(pImageRes, pImageResDirSub, hTree, 3);
};
}
else {
// 遍历资源目录入口结构数组
for (WORD i = 0; i < wNums; i++) {
// 语言ID
if (pImageResDirEntry[i].Name & 0x80000000) {
pString = (PIMAGE_RESOURCE_DIR_STRING_U)((LPBYTE)pImageRes + (pImageResDirEntry[i].Name & 0x7FFFFFFF));
StringCchCopy(szLanguageID, pString->Length + 1, pString->NameString);
}
else
wsprintf(szLanguageID, TEXT("0x%04X"), LOWORD(pImageResDirEntry[i].Name));
// 资源数据入口结构起始地址
pImageResDataEntry = (PIMAGE_RESOURCE_DATA_ENTRY)((LPBYTE)pImageRes + (pImageResDirEntry[i].OffsetToData));
wsprintf(szBuf, TEXT("%s RVA:0x%08X 大小:0x%X"), szLanguageID, pImageResDataEntry->OffsetToData, pImageResDataEntry->Size);
tvi.item.mask = TVIF_TEXT | TVIF_PARAM;
tvi.item.pszText = szBuf;
tvi.item.lParam = (LPARAM)pImageResDataEntry;// 保存资源数据入口结构起始地址到项目数据
SendMessage(g_hwndTree, TVM_INSERTITEM, 0, (LPARAM)&tvi);
// 递归出口
return TRUE;
};
};
return TRUE;
};
BOOL GetSelectedResData(PIMAGE_DOS_HEADER pImageDosHeader, LPBYTE lpResData, DWORD dwResSize) {
DWORD dwFileOffset; // 文件偏移
dwFileOffset = lpResData - (LPBYTE)pImageDosHeader;
// 没实现自己去搞
return TRUE;
};

延迟加载导入表

延迟加载指的是通过隐式链接的DLL。可执行模块开始运行时并不加载延时加载的DLL,也不会检查该DLL是否存在,只有当代码中调用延迟加载DLL中函数时,系统才实际载入该DLL。设置延迟加载DLL后,编译器在PE文件中创建一个延迟加载导入表,记录可执行模块要导入的DLL以及相关函数信息。

延迟加载导入表是一个延迟加载描述IMAGE_DELAYLOAD_DESCRIPTOR结构数组,结构个数取决要延迟加载的DLL数,每个结构对应一个DLL,以全0该结构结束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct _IMAGE_DELAYLOAD_DESCRIPTOR {
union {
DWORD AllAttributes; //最高位为1表示延迟加载版本2
struct {
DWORD RvaBased : 1;
DWORD ReservedAttributes : 31;
} DUMMYSTRUCTNAME;
} Attributes;
DWORD DllNameRVA; //延迟加载DLL名称字符串 UTF-8
DWORD ModuleHandleRVA; //延迟加载DLL模块句柄RVA
DWORD ImportAddressTableRVA; //延迟加载DLL的IAT起始地址 RVA IMAGE_THUNK_DATA
DWORD ImportNameTableRVA; //延迟加载DLL的INT起始地址 RVA IMAGE_THUNK_DATA
DWORD BoundImportAddressTableRVA; //可选延迟加载DLL的绑定IAT起始地址 RVA
DWORD UnloadInformationTableRVA; //可选延迟加载DLL的卸载IAT起始地址 RVA
DWORD TimeDateStamp; //未绑定为0 绑定为绑定的时间戳
} IMAGE_DELAYLOAD_DESCRIPTOR, * PIMAGE_DELAYLOAD_DESCRIPTOR;

每个IMAGE_THUNK_DATA表示一个导入函数信息,最后以一个全0该结构结束。

1
2
3
4
5
6
7
8
9
typedef struct _IMAGE_THUNK_DATA64 {
union {
ULONGLONG ForwarderString;
ULONGLONG Function;
ULONGLONG Ordinal;
ULONGLONG AddressOfData;
} u1;
} IMAGE_THUNK_DATA64;
typedef IMAGE_THUNK_DATA64* PIMAGE_THUNK_DATA64;

校验和与CRC

校验和

计算校验和的过程为:将IMAGE_NT_HEADERS.OptionalHeader.CheckSum字段清零,以WORD为单位对数据块进行带进位的累加,大于WORD部分自动溢出,将累加和与文件长度相加得PE文件校验和。ImageHlp.dll用于操作PE文件,

MapFileAndCheckSum

计算指定文件校验和:

1
2
3
4
5
DWORD MapFileAndCheckSum(
_In_ PCTSTR FileName, //要计算校验和的文件名
_Out_ PDWORD HeaderSum, //接收原始校验和变量的指针
_Out_ PDWORD CheckSum //接收计算的校验和变量的指针
); //成功返回CHECKSUM_SUCCESS

例如:

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
#include <windows.h>
#include <imagehlp.h>
#include "resource.h"
#pragma comment(lib, "Imagehlp.lib")
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
// 打开文件所用变量
TCHAR szFile[MAX_PATH] = { 0 };
OPENFILENAME ofn = { 0 };
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwndDlg;
ofn.lpstrFilter = TEXT("exe文件(*.exe)\0*.exe\0dll文件(*.dll)\0*.dll\0All(*.*)\0*.*\0");
ofn.nFilterIndex = 3;
ofn.lpstrFile = szFile;
ofn.lpstrFile[0] = NULL;
ofn.nMaxFile = _countof(szFile);
ofn.lpstrTitle = TEXT("请选择要打开的PE文件");
ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
HANDLE hFile;
DWORD dwFileSize;
LPVOID lpMemory;
PIMAGE_DOS_HEADER pImageDosHeader;
PIMAGE_NT_HEADERS pImageNtHeader;
DWORD dwCheckSum = 0;
DWORD dwCheckSumImageHlp = 0;
DWORD dwCheckSumImageHlpOrigin = 0;
TCHAR szBuf[16] = { 0 };
switch (uMsg) {
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_BROWSE: {
if (GetOpenFileName(&ofn))
SetDlgItemText(hwndDlg, IDC_EDIT_FILENAME, szFile);
break;
};
case IDC_BTN_CALC: {
// 打开一个PE文件,并获取文件大小
GetDlgItemText(hwndDlg, IDC_EDIT_FILENAME, szFile, _countof(szFile));
hFile = CreateFile(szFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
dwFileSize = GetFileSize(hFile, NULL);
// 分配内存,复制PE文件
lpMemory = VirtualAlloc(NULL, dwFileSize, MEM_COMMIT, PAGE_READWRITE);
ReadFile(hFile, lpMemory, dwFileSize, NULL, NULL);
CloseHandle(hFile);
// 打开的文件是不是PE文件
pImageDosHeader = (PIMAGE_DOS_HEADER)lpMemory;
if (pImageDosHeader->e_magic == IMAGE_DOS_SIGNATURE) {
pImageNtHeader = (PIMAGE_NT_HEADERS)((LPBYTE)pImageDosHeader + pImageDosHeader->e_lfanew);
if (pImageNtHeader->Signature == IMAGE_NT_SIGNATURE)
// IMAGE_NT_HEADERS.OptionalHeader.CheckSum字段清0
pImageNtHeader->OptionalHeader.CheckSum = 0;
};
// 计算校验和
DWORD i;
for (i = 0; i < dwFileSize - 1; i += 2) {
dwCheckSum += *((LPWORD)((LPBYTE)lpMemory + i));
dwCheckSum = (dwCheckSum >> 16) + (dwCheckSum & 0xFFFF);
};
if (i == dwFileSize - 1)
dwCheckSum += *((LPBYTE)lpMemory + i);
dwCheckSum += dwFileSize;
VirtualFree(lpMemory, 0, MEM_RELEASE);
// 显示自定义算法计算的结果
wsprintf(szBuf, TEXT("0x%08X"), (DWORD)dwCheckSum);
SetDlgItemText(hwndDlg, IDC_EDIT_CUSTOMFUNC, szBuf);
MapFileAndCheckSum(szFile, &dwCheckSumImageHlpOrigin, &dwCheckSumImageHlp);
// 显示MapFileAndCheckSum函数计算的结果
wsprintf(szBuf, TEXT("0x%08X"), dwCheckSumImageHlp);
SetDlgItemText(hwndDlg, IDC_EDIT_MAPFILEANDCHECKSUM, szBuf);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};

CheckSumMappedFile

计算指定PE文件校验和:

1
2
3
4
5
6
PIMAGE_NT_HEADERS CheckSumMappedFile(
_In_ PVOID BaseAddress, //PE内存映射文件基地址
_In_ DWORD FileLength, //文件大小 单位字节
_Out_ PDWORD HeaderSum, //接收原始校验和变量的指针
_Out_ PDWORD CheckSum //接收计算的校验和变量的指针
); //成功返回PE内存映射文件IMAGE_NT_HEADERS结构的指针 失败NULL

调用者可自行通过返回的指针修改IMAGE_NT_HEADERS.OptionalHeader.CheckSum。

循环冗余校验CRC

常用版本有CRC-8、CRC-12、CRC-16、CRC-CCITT、CRC-32、CRC-32C等。WinRAR、NERO、ARJ、LHA等压缩文件采用CRC-32,磁盘驱动器读写采用CRC-16,通用图像存储格式如GIF、TIFF等也采用CRC。这里实现一个文件或一段数据的CRC-32,可以实现一个自定义函数,也可用NtDll.dll中RtlComputeCrc32

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
#include <windows.h>
#include "resource.h"
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 常量定义
//#define Poly 0xEDB88320L // CRC-32标准
// 生成CRC-32查询表
VOID GenerateCRC32Table(PUINT pCRC32Table);
// 计算CRC-32
UINT CRC32(LPBYTE lpData, UINT nSize);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
// 打开文件所用变量
TCHAR szFile[MAX_PATH] = { 0 };
OPENFILENAME ofn = { 0 };
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwndDlg;
ofn.lpstrFilter = TEXT("exe文件(*.exe)\0*.exe\0dll文件(*.dll)\0*.dll\0All(*.*)\0*.*\0");
ofn.nFilterIndex = 3;
ofn.lpstrFile = szFile;
ofn.lpstrFile[0] = NULL;
ofn.nMaxFile = _countof(szFile);
ofn.lpstrTitle = TEXT("请选择要打开的PE文件");
ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
// 内存映射文件所用变量
HANDLE hFile, hFileMap;
LPVOID lpMemory;
LARGE_INTEGER liFileSize;
UINT nCRC;
TCHAR szBuf[16] = { 0 };
switch (uMsg) {
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_BROWSE: {
if (GetOpenFileName(&ofn))
SetDlgItemText(hwndDlg, IDC_EDIT_FILENAME, szFile);
break;
};
case IDC_BTN_CALC: {
// 打开文件
GetDlgItemText(hwndDlg, IDC_EDIT_FILENAME, szFile, _countof(szFile));
hFile = CreateFile(szFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
MessageBox(hwndDlg, TEXT("CreateFile函数调用失败"), TEXT("提示"), MB_OK);
return TRUE;
}
else {
GetFileSizeEx(hFile, &liFileSize);
if (liFileSize.QuadPart == 0) {
MessageBox(hwndDlg, TEXT("文件大小为0"), TEXT("提示"), MB_OK);
return TRUE;
};
};
// 为hFile文件对象创建一个文件映射内核对象
hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (!hFileMap) {
MessageBox(hwndDlg, TEXT("CreateFileMapping调用失败"), TEXT("提示"), MB_OK);
return TRUE;
};
// 把文件映射对象hFileMap的全部映射到进程的虚拟地址空间中
lpMemory = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
if (!lpMemory) {
MessageBox(hwndDlg, TEXT("MapViewOfFile调用失败"), TEXT("提示"), MB_OK);
return TRUE;
};
// 自定义函数计算CRC-32
nCRC = CRC32((LPBYTE)lpMemory, liFileSize.LowPart);
wsprintf(szBuf, TEXT("0x%08X"), nCRC);
SetDlgItemText(hwndDlg, IDC_EDIT_RESULT, szBuf);
// RtlComputeCrc32计算CRC-32
typedef UINT(WINAPI* pfnRtlComputeCrc32)(INT dwInitial, LPVOID lpData, INT nLen);
pfnRtlComputeCrc32 fnRtlComputeCrc32;
fnRtlComputeCrc32 = (pfnRtlComputeCrc32)GetProcAddress(GetModuleHandle(TEXT("NtDll.dll")), "RtlComputeCrc32");
nCRC = fnRtlComputeCrc32(0, lpMemory, liFileSize.LowPart); // 第一个参数指定为0
wsprintf(szBuf, TEXT("0x%08X"), nCRC);
SetDlgItemText(hwndDlg, IDC_EDIT_RESULT2, szBuf);
// 清理工作
UnmapViewOfFile(lpMemory);
CloseHandle(hFileMap);
CloseHandle(hFile);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
#define Poly 0xEDB88320 // CRC-32标准
VOID GenerateCRC32Table(PUINT pCRC32Table) {
UINT nCrc;
for (UINT i = 0; i < 256; i++) {
nCrc = i;
for (int j = 0; j < 8; j++)
if (nCrc & 0x00000001)
nCrc = (nCrc >> 1) ^ Poly;
else
nCrc = nCrc >> 1;
pCRC32Table[i] = nCrc;
};
return;
};
UINT CRC32(LPBYTE lpData, UINT nSize) {
UINT CRC32Table[256] = { 0 }; // CRC-32查询表
UINT nCrc = 0xFFFFFFFF;
// 生成CRC-32查询表
GenerateCRC32Table(CRC32Table);
// 计算CRC-32
for (UINT i = 0; i < nSize; i++)
nCrc = CRC32Table[(nCrc ^ lpData[i]) & 0xFF] ^ (nCrc >> 8);
return nCrc ^ 0xFFFFFFFF; // 按位取反
};

x64下书写汇编

x64不支持内联汇编,但intrin.h中提供了一系列intrinsic函数,这里就介绍俩。

__cpuid/__cpuidex

获取CPU信息,如CPU星号和家族等;获取CPU支持的功能,如是否支持MMX、SSE、FPU指令等。

1
2
3
4
5
6
7
8
9
VOID __cpuuid(
_Out_ INT cpuInfo[4], //返回CPU信息及支持的功能
_In_ INT function_id //指定要获取的基本信息 功能号 0~3
);
VOID __cpuidex(
_Out_ INT cpuInfo[4],
_In_ INT function_id,
_In_ INT subfunction_id //指定要获取的扩展信息
);

该指令原用法:

1
2
mov eax, 功能号
cpuid

返回后通过eax、ebx、ecx和ebx返回所需信息,这里分别放入cpuInfo数组。获取基本信息时,功能号最高位为0;获取扩展信息时,最高位为1。

法一

不让内联汇编就外联,添加Test.asm汇编源文件。该文件右键打开属性,配置自定义生成工具,命令行选项写以下,表示用ml64.exe编译.asm为.obj到生成配置目录下,用/Fo指定输出.obj文件名,用/c仅编译不自动链接。生成配置目录即“\项目名\x64\Release”等。

1
ml64 /Fo $(IntDir)%(fileName).obj /c %(fileName).asm

输出选项写以下,表示告诉链接器到哪找.obj文件以完成链接。

1
$(IntDir)%(fileName).obj

这里汇编文件为:

1
2
3
4
5
6
7
8
9
10
.code
GetCPUID proc
mov r8, rcx
mov eax, edx
cpuid
mov dword ptr [r8], eax
mov dword ptr [r8 + 0Ch], edx
ret
GetCPUID endp
end

源代码这样写:

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
#include <windows.h>
#include <intrin.h>
#include "resource.h"
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 声明引用外部函数
EXTERN_C VOID GetCPUID(int cpuInfo[4], int function_id);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
int arrCpuInfo[4] = { 0 };
TCHAR szBuf[32] = { 0 };
switch (uMsg) {
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_GET: {
// intrinsic函数
__cpuid(arrCpuInfo, 1);
wsprintf(szBuf, TEXT("%08X%08X"), arrCpuInfo[3], arrCpuInfo[0]);
SetDlgItemText(hwndDlg, IDC_EDIT_INTRINSIC, szBuf);
// 自定义汇编函数
ZeroMemory(arrCpuInfo, sizeof(arrCpuInfo));
GetCPUID(arrCpuInfo, 1);
wsprintf(szBuf, TEXT("%08X%08X"), arrCpuInfo[3], arrCpuInfo[0]);
SetDlgItemText(hwndDlg, IDC_EDIT_ASM, szBuf);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};

法二

写成ShellCode。

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
#include <windows.h>
#include <intrin.h>
#include "resource.h"
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
int arrCpuInfo[4] = { 0 };
TCHAR szBuf[32] = { 0 };
// GetCPUID函数字节码
BYTE bGetCPUID[] = {
0x4C, 0x8B, 0xC1, // mov r8, rcx
0x8B, 0xC2, // mov eax, edx
0x0F, 0xA2, // cpuid
0x41, 0x89, 0x00, // mov dword ptr [r8], eax
0x41, 0x89, 0x50, 0x0C, // mov dword ptr [r8 + 0xC], edx
0xC3 }; // ret
// 函数指针变量
VOID(*GetCPUID)(int cpuInfo[4], int function_id) = (VOID(*)(int*, int))(ULONG_PTR)bGetCPUID;
DWORD dwOldProtect;
switch (uMsg) {
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_GET: {
// intrinsic函数
__cpuid(arrCpuInfo, 1);
wsprintf(szBuf, TEXT("%08X%08X"), arrCpuInfo[3], arrCpuInfo[0]);
SetDlgItemText(hwndDlg, IDC_EDIT_INTRINSIC, szBuf);
// 自定义汇编函数
ZeroMemory(arrCpuInfo, sizeof(arrCpuInfo));
VirtualProtect(GetCPUID, sizeof(bGetCPUID), PAGE_EXECUTE_READWRITE, &dwOldProtect);
GetCPUID(arrCpuInfo, 1);
VirtualProtect(GetCPUID, sizeof(bGetCPUID), dwOldProtect, &dwOldProtect);
wsprintf(szBuf, TEXT("%08X%08X"), arrCpuInfo[3], arrCpuInfo[0]);
SetDlgItemText(hwndDlg, IDC_EDIT_ASM, szBuf);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};

Detour

Detour库用于拦截API函数,原理就是将目标函数前几条指令替换为无条件跳转到用户提供的自定义函数的jmp指令。自定义函数完成拦截处理后,恢复执行目标函数被覆盖的前几条指令,并继续执行目标函数后续部分。目标函数执行完后自定义函数返回。

自定义函数在内部实现层次上分为两个函数。执行拦截处理的为Detour绕行函数,其函数参数、函数调用约定和返回值类型必须与目标函数完全一致。Trampoline跳板函数执行目标函数被覆盖的前几条指令并跳转到目标函数后续部分的功能。Trampoline在Detour函数中被调用。

Detour库除了可以对API函数拦截,还可修改可执行文件导入表、项可执行文件添加数据、将DLL加载到目标进程等。该库托管在Github上,自己下载是源码,用x64 Native Tools Command Prompt for VS 2022命令行在Makefile所在目录下用命令:

1
nmake

运行后目录下生成include、lib.X64、bin.X64目录。

DLL注入

DetourIsHelperProcess

检查被注入DLL所属当前线程是辅助进程还是目标进程:

1
BOOL DetourIsHelperProcess(VOID);

是辅助进程返回TRUE,是目标进程返回FALSE。

DetourRestoreAfterWith

恢复目标进程导入表:

1
BOOL DetourRestoreAfterWith(VOID);

DetourTransactionBegin

开启事务:

1
BOOL DetourTransactionBegin(VOID);

DetourUpdateThread

指定更新线程:

1
2
3
4
LONG DetourAttach(
_Inout_ PVOID* ppPointer, //目标函数指针地址
_In_ PVOID pDetour //自定义函数地址
);

DetourDetach

执行Unhook处理:

1
2
3
4
LONG DetourDetach(
_Inout_ PVOID* ppPointer,
_In_ PVOID pDetour
);

DetourTransactionCommit

提交事务:

1
LONG DetourTransactionCommit(VOID);

例子

可执行模块用DetourCreateProcessWithDllExDetourCreateProcessWithDlls将被注入DLL加载到目标进程中。内部实现方法是修改目标进程导入表,将被注入DLL对应导入表描述符结构放在导入表最前部,即可在目标进程气都后及执行代码前加载被注入DLL,在被注入DLL中用DetourAttach执行Hook处理。

Detour支持x86与x64之间跨架构创建目标进程,但此时DetourCreateProcessWithDllExDetourCreateProcessWithDlls必须创建临时辅助进程,方法是将被注入DLL加载到rundll32.exe进程并用DetourFinishHelperProcess创建一个辅助进程,辅助进程加载被注入DLL副本,保证使用正确的架构代码来修改目标进程IAT。此时要注意:被注入DLL必须导出DetourFinishHelperProcess且导出函数序数为1,否则目标进程无法启动;被注入DLL应在其DllMain中用DetourIsHelperProcess检查其所属当前进程是辅助进程还是目标进程,辅助进程则不应Hook。

执行DetourAttack后DetourExtTextOutW中OriginalExtTextOutW被改为指向Trampoline函数。

本节中,事务是一系列原子性、独占性的操作。操作前要开启事务,最后提交事务,提交后所做操作才会生效。用DetourAttach执行Hook和用DetourDetach执行Unhook前要先用DetourTransactionBegin开启事务,之后用DetourTransactionCommit提交事务,有时用DetourUpdateThread指定受事务影响的需更新的线程。

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
#include <windows.h>
#include <tchar.h>
#include "..\..\..\Detours-master\include\detours.h"
// 编译为x86时需要使用的.lib
#pragma comment(lib, "..\\..\\..\\Detours-master\\lib.X86\\detours.lib")
// 编译为x64时需要使用的.lib
//#pragma comment(lib, "..\\..\\..\\Detours-master\\lib.X64\\detours.lib")
// 目标函数指针(加static关键字说明仅用于本文件)
static BOOL(WINAPI* OriginalExtTextOutW)(HDC hdc, INT x, INT y, UINT options,CONST PRECT lprect, LPCWSTR lpString, UINT c, CONST PINT lpDx) = ExtTextOutW;
// 自定义函数
BOOL WINAPI DetourExtTextOutW(HDC hdc, int x, int y, UINT options, RECT* lprect, LPCWSTR lpString, UINT c, INT* lpDx) {
TCHAR szText1[] = TEXT("屏幕");
TCHAR szText2[] = TEXT("用户名");
TCHAR szText3[] = TEXT("购买者");
TCHAR szTextReplace[] = TEXT(" ");
LPCTSTR lpStr;
if ((lpStr = _tcsstr(lpString, szText1)) || (lpStr = _tcsstr(lpString, szText2)) || (lpStr = _tcsstr(lpString, szText3)))
memcpy((LPVOID)lpStr, szTextReplace, _tcslen(lpStr) * sizeof(TCHAR));
OriginalExtTextOutW(hdc, x, y, options, lprect, lpString, c, lpDx);
return TRUE;
};
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
// 如果当前进程是辅助进程则不执行任何处理
if (DetourIsHelperProcess())
return TRUE;
if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
// 恢复当前进程的导入表
DetourRestoreAfterWith();
// 开启(开始)事务
DetourTransactionBegin();
// 指定更新线程
DetourUpdateThread(GetCurrentThread());
// 执行Hook处理
DetourAttach(&(PVOID&)OriginalExtTextOutW, DetourExtTextOutW);
// 提交事务
DetourTransactionCommit();
}
else if (ul_reason_for_call == DLL_PROCESS_DETACH) {
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
// 执行Unhook处理
DetourDetach(&(PVOID&)OriginalExtTextOutW, DetourExtTextOutW);
DetourTransactionCommit();
};
return TRUE;
};

将DLL加载到目标进程

DetourCreateProcessWithDllEx

创建一个新进程并将指定DLL加载到该进程中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
BOOL DetourCreateProcessWithDllEx(
_In_opt_ LPCTSTR lpApplication,
_Inout_opt_ LPTSTR lpCommandLine,
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ BOOL bInheritHandles,
_In_ DWORD dwCreationFlags,
_In_opt_ LPVOID lpEnvironment,
_In_opt_ LPCTSTR lpCurrentDirectory,
_In_ LPSTARTUPINFOW lpStartupInfo,
_Out_ LPPROCESS_INFORMATION lpProcessInformation,
_In_ LPCSTR lpDllName, //要注入DLL名称
_In_opt_ PDETOUR_CREATE_PROCESS_ROUTINE pfCreateProcessW //替换CreateProcessW的新函数指针
);

使用标准CreateProcessW时,pfnCreateProcessW可设为NULL。该函数在内部用CreateProcessW,且dwCreationFlags设为CREATE_SUSPENDED以挂起模式创建目标进程。该函数修改目标进程导入表,将指定DLL对应导入表描述符结构放在导入表最前部,然后恢复目标进程执行,系统先加载指定DLL。

DetourCreateProcessWithDlls

创建一个新线程并将指定的若干个DLL加载到该线程中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BOOL DetourCreateProcessWithDlls(
_In_opt_ LPCTSTR lpApplication,
_Inout_opt_ LPTSTR lpCommandLine,
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ BOOL bInheritHandles,
_In_ DWORD dwCreationFlags,
_In_opt_ LPVOID lpEnvironment,
_In_opt_ LPCTSTR lpCurrentDirectory,
_In_ LPSTARTUPINFOW lpStartupInfo,
_Out_ LPPROCESS_INFORMATION lpProcessInformation,
_In_ DWORD nDlls, //rlpDll数组元素个数
_In_ LPCSTR* rlpDlls, //要注入的DLL名称数组
_In_opt_ PDETOUR_CREATE_PROCESS_ROUTINE pfCreateProcessW
);

描述同DetourCreateProcessWithDllEx

DetourAttachEx

DetourAttach,还可获取Detour函数、Trampoline函数和目标函数地址:

1
2
3
4
5
6
7
LONG DetourAttachEx(
_Inout_ PVOID* ppPointer, //目标函数指针地址
_In_ PVOID pDetour, //自定义函数地址
_Out_opt_ PDETOUR_TRAMPOLINE ppRealTrampoline, //返回Trampoline函数地址
_Out_opt_ PVOID* ppRealTarget, //返回目标函数地址
_Out_opt_ PVOID* ppRealDetour //返回Detour函数地址
);

DetourFindFunction

从指定模块中查找指定函数地址:

1
2
3
4
PVOID DetourFindFunction(
_In_ LPCSTR pszModule, //模块名
_In_ LPCSTR pszFunction //函数名
); //成功返回指定函数内存地址 失败NULL

DetourCodeFromPointer

获取指定函数的代码实现地址:

1
2
3
4
PVOID DetourCodeFromPointer(
_In_ PVOID pPointer, //目标函数指针
_Out_opt_ PVOID* ppGlobals //返回目标函数全局/静态数据地址
); //成功返回目标函数代码实现地址

DetourFindFunction不同的是,可能指定的函数只有个jmp,DetourCodeFromPointer会继续跟进并找到代码实现地址。如:

1
2
3
4
5
6
LPVOID lpGetCurrentProcess = NULL, lpRealGetCurrentProcess = NULL, lpGlobals = NULL;
TCHAR szBuf[512] = { 0 };
lpGetCurrentProcess = DetourFindFunction("kernel32", "GetCurrentProcess");
lpRealGetCurrentProcess = DetourCodeFromPointer(lpGetCurrentProcess, &lpGlobals);
wsprintf(szBuf, TEXT("函数指针:0x%p\n 函数代码实现地址:0x%p\n 全局(或静态)数据地址:0x%p\n"), lpGetCurrentProcess, lpRealGetCurrentProcess, lpGlobals);
MessageBox(NULL, szBuf, TEXT("提示"), NULL);

DetourEnumerateModules

枚举进程中模块:

1
2
3
HMODULE DetourEnumerateModules(
_In_opt_ HMODULE hModuleLast //模块句柄
);

第一次hModuelLast为NULL,函数返回下一个模块句柄,以返回的模块句柄循环调用该函数,直到返回NULL。

DetourGetEntryPoint

获取模块入口点:

1
2
3
PVOID DetourGetEntryPoint(
_In_opt_ HMODULE hModule //模块句柄
);

hModule为NULL则返回调用进程可执行模块入口点。

DetourEnumerateExports

枚举模块导出函数:

1
2
3
4
5
BOOL DetourEnumerateExports(
_In_ HMODULE hModuel, //模块句柄
_In_opt_ PVOID pContext, //传递给回调函数的参数
_In_ PF_DETOUR_ENUMERATE_EXPORT_CALLBACK pfExport //回调函数
);

每当枚举到一个导出函数,都调用一次回调函数,格式为:

1
2
3
4
5
6
BOOL CALLBACK ExportFunc(
_In_opt_ PVOID pContext, //传递来的参数
_In_ ULONG nOrdinal, //函数序数
_In_opt_ LPCSTR pszName, //函数名
_In_opt_ PVOID pCode //函数代码实现地址
);

需要继续枚举时,回调函数返回TRUE,终止则返回FALSE。

DetourEnumerateImports

枚举模块导入表:

1
2
3
4
5
6
BOOL DetourEnumerateImports(
_In_opt_ HMODULE hModule,//模块句柄
_In_opt_ PVOID pContext, //传递给pfImportFile和pfImportFunc的参数
_In_opt_ PF_DETOUR_IMPORT_FILE_CALLBACK pfImportFile, //每枚举到一个DLL时调用
_In_opt_ PF_DETOUR_IMPORT_FUNC_CALLBACK pfImportFunc //每枚举到一个导入函数时调用
);

还有个DetourEnumerateImportsEx自己去学。

例子

为了保证DLL中导出函数DetourFinishHelperProcess的导出函数为1,应添加模块定义文件xxx.def:

1
2
EXPORTS
DetourFinishHelperProcess @1

然后分别编译x86和x64两个版本为xxx32.dll和xxx64.dll。DetourCreateProcessWithDllExDetourCreateProcessWithDlls发现选错架构的DLL时,会自动改为正确的DLL,但不建议依赖该纠错特性。

注入器:

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
#include <windows.h>
#include <tchar.h>
#include <CommCtrl.h>
#include "..\..\..\Detours-master\include\detours.h"
#include "resource.h"
// 编译为x86时需要使用的.lib
#pragma comment(lib, "..\\..\\..\\Detours-master\\lib.X86\\detours.lib")
// 编译为x64时需要使用的.lib
//#pragma comment(lib, "..\\..\\..\\Detours-master\\lib.X64\\detours.lib")
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
HWND hwndComboDllPath;
HWND hwndComboTarget;
CHAR szInjectDll[MAX_PATH] = { 0 }; // 注入dll路径
TCHAR szTargetProcess[MAX_PATH] = { 0 }; // 目标程序路径
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = { 0 };
BOOL bRet = FALSE;
switch (uMsg) {
case WM_INITDIALOG: {
hwndComboDllPath = GetDlgItem(hwndDlg, IDC_COMBO_DLLPATH);
hwndComboTarget = GetDlgItem(hwndDlg, IDC_COMBO_TARGET);
// 注入dll组合框添加一些列表项
SendMessage(hwndComboDllPath, CB_ADDSTRING, 0, (LPARAM)TEXT("InjectDll32.dll"));
SendMessage(hwndComboDllPath, CB_ADDSTRING, 0, (LPARAM)TEXT("InjectDll64.dll"));
SendMessage(hwndComboDllPath, CB_SETCURSEL, 0, 0);
// 目标程序组合框添加一些列表项
SendMessage(hwndComboTarget, CB_ADDSTRING, 0, (LPARAM)TEXT("FloatingWaterMark32.exe"));
SendMessage(hwndComboTarget, CB_ADDSTRING, 0, (LPARAM)TEXT("FloatingWaterMark64.exe"));
SendMessage(hwndComboTarget, CB_SETCURSEL, 0, 0);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_CREATE: {
GetDlgItemTextA(hwndDlg, IDC_COMBO_DLLPATH, szInjectDll, _countof(szInjectDll));
GetDlgItemText(hwndDlg, IDC_COMBO_TARGET, szTargetProcess, _countof(szTargetProcess));
GetStartupInfo(&si);
bRet = DetourCreateProcessWithDllEx(NULL, szTargetProcess, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi, szInjectDll, NULL);
if (!bRet)
MessageBox(hwndDlg, TEXT("创建目标进程失败!"), TEXT("错误提示"), MB_OK);
break;
};
};
return TRUE;
};
case WM_CLOSE: {
EndDialog(hwndDlg, 0);
return TRUE;
};
};
return FALSE;
};

编辑可执行文件

DetourBinaryOpen

将可执行文件内容读入内存:

1
2
3
PDETOUR_BINARY DetourBinaryOpen(
_In_ HANDLE hFile //文件句柄
); //成功返回Detour二进制文件对象指针 失败NULL

DetourBinarySetPayload

项Detour二进制文件对象中添加数据:

1
2
3
4
5
6
PVOID DetourBinarySetPayload(
_In_ PDETOUR_BINARY pBinary, //Detour二进制文件对象
_In_ REFGUID rguid, //要添加的数据自定义GUID
_In_ PVOID pData, //要添加的数据指针
_In_ DWORD cbData //要添加的数据大小 单位字节
); //成功返回数据实际写入内存地址 失败NULL

DetourBinaryFindPayload

从Detour二进制文件对象中查找指定GUID的数据:

1
2
3
4
5
PVOID DetourBinaryFindPayload(
_In_ PDETOUR_BINARY pBinary,
_In_ REFGUID rguid,
_Out_ PDWORD pcbData //返回查找到的数据大小 单位字节
); //成功返回指定数据内存地址 失败NULL

DetourBinaryDeletePayload/DetourBinaryPurgePayloads

从Detour二进制文件对象中删除指定/所有数据:

1
2
3
4
5
6
7
BOOL DetourBinaryDeletePayload(
_In_ PDETOUR_BINARY pBinary,
_In_ REFGUID rguid
);
BOOL DetourBinaryPurgePayloads(
_In_ PDETOUR_BINARY pBinary
);

DetourBinaryEditImports

修改Detour二进制文件对象导入表:

1
2
3
4
5
6
7
8
BOOL DetourBinaryEditImports(
_In_ PDETOUR_BINARY pBinary,
_In_opt_ PVOID pContext, //传递给各个回调函数的参数
_In_opt_ PF_DETOUR_BINARY_BYWAY_CALLBACK pfByway,
_In_opt_ PF_DETOUR_BINARY_FILE_CALLBACK pfFile,
_In_opt_ PF_DETOUR_BINARY_SYMBOL_CALLBACK pfSymbol,
_In_opt_ PF_DETOUR_COMMIT_CALLBACK pfFinal
);

DetourBinaryResetImports

重置Detour二进制文件对象导入表:

1
2
3
BOOL DetourBinaryResetImports(
_In_ PDETOUR_BINARY pBinary
);

DetourBinaryWrite

将更新写入文件:

1
2
3
4
BOOL DetourBinaryWrite(
_In_ PDETOUR_BINARY pBinary,
_In_ HANDLE hFile
);

DetourBinaryClose

关闭 打开的Detour二进制文件对象:

1
2
3
BOOL DetourBinaryClose(
_In_ PDETOUR_BINARY pBinary
);

IAT Hook

实现方式为修改API对应IAT项内存地址为自定义函数内存地址。为了栈平衡,自定义函数的参数、调用约定和返回值类型等必须与目标函数一致。

除了修改进程所有模块IAT中目标函数对应IAT项,为了能够在加载模块时加载其他依赖模块,还需Hook掉LoadLibrary*GetProcAddress等函数。

ImageDirectoryEntryToDataEx

从普通PE文件或PE内存映像中查找指定数据目录内存地址:

1
2
3
4
5
6
7
PVOID IMAGEAPI ImageDirectoryEntryToDataEx(
_In_ PVOID pBase, //PE或PE内存映像基地址
_In_ BOOLEAN bMappedAsImage, //是否是PE内存映像
_In_ USHORT nDirectoryEntry, //数据目录索引
_Out_ PULONG pSize, //返回数据目录大小
_Out_opt_ PIMAGE_SECTION_HEADER* pFoundHeader //返回数据目录所在节区信息
); //成功返回数据目录内存地址 失败NULL

nDirectoryEntry如IMAGE_DIRECTORY_ENTRY_EXPORT为导出表等。

例子

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
#include <windows.h>
#include <tchar.h>
#include <dbghelp.h>
#include <TlHelp32.h>
#pragma comment(lib, "Dbghelp.lib")
// 目标函数原函数指针(ExtTextOutW; LoadLibrary*; GetProcAddress)
static BOOL(WINAPI* OrigExtTextOutW)(HDC hdc, int x, int y, UINT options,const RECT* lprect, LPCWSTR lpString, UINT c, const INT* lpDx) = ExtTextOutW;
static HMODULE(WINAPI* OrigLoadLibraryA)(LPCSTR lpLibFileName) = LoadLibraryA;
static HMODULE(WINAPI* OrigLoadLibraryW)(LPCWSTR lpLibFileName) = LoadLibraryW;
static HMODULE(WINAPI* OrigLoadLibraryExA)(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) = LoadLibraryExA;
static HMODULE(WINAPI* OrigLoadLibraryExW)(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) = LoadLibraryExW;
static FARPROC(WINAPI* OrigGetProcAddress)(HMODULE hModule, LPCSTR lpProcName) = GetProcAddress;
// 自定义函数(替换掉ExtTextOutW; LoadLibrary*; GetProcAddress)
BOOL WINAPI HookExtTextOutW(HDC hdc, int x, int y, UINT options,RECT* lprect, LPCWSTR lpString, UINT c, INT* lpDx);
HMODULE WINAPI HookLoadLibraryA(LPCSTR lpLibFileName);
HMODULE WINAPI HookLoadLibraryW(LPCWSTR lpLibFileName);
HMODULE WINAPI HookLoadLibraryExA(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
HMODULE WINAPI HookLoadLibraryExW(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
FARPROC WINAPI HookGetProcAddress(HMODULE hModule, LPCSTR lpProcName);
// 替换进程的指定模块导入表中的一个IAT项(导入函数地址)
BOOL ReplaceIATInOneMod(HMODULE hModule, LPCSTR pszDllName, PROC pfnTarget, PROC pfnNew);
// 替换进程的所有模块导入表中的一个IAT项(导入函数地址)
VOID ReplaceIATInAllMod(LPCSTR pszDllName, PROC pfnTarget, PROC pfnNew);
// 替换指定模块导出表中的一个EAT项(导出函数地址)
BOOL ReplaceEATInOneMod(HMODULE hModule, LPCSTR pszFuncName, PROC pfnNew);
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {
ReplaceIATInAllMod("gdi32.dll", (PROC)OrigExtTextOutW, (PROC)HookExtTextOutW);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryA, (PROC)HookLoadLibraryA);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryW, (PROC)HookLoadLibraryW);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryExA, (PROC)HookLoadLibraryExA);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryExW, (PROC)HookLoadLibraryExW);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigGetProcAddress, (PROC)HookGetProcAddress);
};
case DLL_THREAD_ATTACH: {};
case DLL_THREAD_DETACH: {};
case DLL_PROCESS_DETACH: {
break;
};
};
return TRUE;
};
//////////////////////////////////////////////////////////////////////////
// 替换进程的指定模块导入表中的一个IAT项(导入函数地址)
BOOL ReplaceIATInOneMod(HMODULE hModule, LPCSTR pszDllName, PROC pfnTarget, PROC pfnNew) {
ULONG ulSize; // 导入表的大小
PIMAGE_IMPORT_DESCRIPTOR pImageImportDesc = NULL; // 导入表起始地址
PIMAGE_THUNK_DATA pImageThunkData = NULL; // IMAGE_THUNK_DATA数组起始地址
// 获取导入表起始地址
pImageImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToDataEx(hModule, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize, NULL);
if (!pImageImportDesc)
return FALSE;
// 遍历导入表,查找目标函数
while (pImageImportDesc->OriginalFirstThunk || pImageImportDesc->TimeDateStamp || pImageImportDesc->ForwarderChain || pImageImportDesc->Name || pImageImportDesc->FirstThunk) {
if (_stricmp(pszDllName, (LPSTR)((LPBYTE)hModule + pImageImportDesc->Name)) == 0) {
pImageThunkData = (PIMAGE_THUNK_DATA)((LPBYTE)hModule + pImageImportDesc->FirstThunk);
while (pImageThunkData->u1.AddressOfData != 0) {
PROC* ppfn = (PROC*)&pImageThunkData->u1.Function;
if (*ppfn == pfnTarget) {
DWORD dwOldProtect;
BOOL bRet = FALSE;
// 替换目标IAT项的值为pfnNew
VirtualProtect(ppfn, sizeof(pfnNew), PAGE_READWRITE, &dwOldProtect);
bRet = WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew, sizeof(pfnNew), NULL);
VirtualProtect(ppfn, sizeof(pfnNew), dwOldProtect, &dwOldProtect);
return bRet;
};
// 指向下一个IMAGE_THUNK_DATA结构
pImageThunkData++;
};
};
// 指向下一个导入表描述符
pImageImportDesc++;
};
return FALSE;
};
// 替换进程的所有模块导入表中的一个IAT项(导入函数地址)
VOID ReplaceIATInAllMod(LPCSTR pszDllName, PROC pfnTarget, PROC pfnNew) {
MEMORY_BASIC_INFORMATION mbi = { 0 };
HMODULE hModuleThis; // 当前代码所处的模块
HANDLE hSnapshot = INVALID_HANDLE_VALUE;
MODULEENTRY32 me = { sizeof(MODULEENTRY32) };
BOOL bRet = FALSE;
VirtualQuery(ReplaceIATInAllMod, &mbi, sizeof(mbi));
hModuleThis = (HMODULE)mbi.AllocationBase;
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());
if (hSnapshot == INVALID_HANDLE_VALUE)
return;
bRet = Module32First(hSnapshot, &me);
while (bRet) {
if (me.hModule != hModuleThis) // 排除当前代码所处的模块
ReplaceIATInOneMod(me.hModule, pszDllName, pfnTarget, pfnNew);
bRet = Module32Next(hSnapshot, &me);
};
CloseHandle(hSnapshot);
};
// 自定义函数
BOOL WINAPI HookExtTextOutW(HDC hdc, int x, int y, UINT options, RECT* lprect, LPCWSTR lpString, UINT c, INT* lpDx) {
TCHAR szText1[] = TEXT("屏幕");
TCHAR szText2[] = TEXT("用户名");
TCHAR szText3[] = TEXT("购买者");
TCHAR szTextReplace[] = TEXT(" ");
LPCTSTR lpStr;
if ((lpStr = _tcsstr(lpString, szText1)) || (lpStr = _tcsstr(lpString, szText2)) || (lpStr = _tcsstr(lpString, szText3)))
memcpy((LPVOID)lpStr, szTextReplace, _tcslen(lpStr) * sizeof(TCHAR));
return OrigExtTextOutW(hdc, x, y, options, lprect, lpString, c, lpDx);
};
HMODULE WINAPI HookLoadLibraryA(LPCSTR lpLibFileName) {
// 调用原LoadLibraryA函数
HMODULE hModule = OrigLoadLibraryA(lpLibFileName);
// 原LoadLibraryA函数执行完毕,进程所有模块的导入表中的相关IAT项再替换一次
if (hModule != NULL) {
ReplaceIATInAllMod("gdi32.dll", (PROC)OrigExtTextOutW, (PROC)HookExtTextOutW);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryA, (PROC)HookLoadLibraryA);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryW, (PROC)HookLoadLibraryW);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryExA, (PROC)HookLoadLibraryExA);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryExW, (PROC)HookLoadLibraryExW);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigGetProcAddress, (PROC)HookGetProcAddress);
};
return hModule;
};
HMODULE WINAPI HookLoadLibraryW(LPCWSTR lpLibFileName) {
HMODULE hModule = OrigLoadLibraryW(lpLibFileName);
if (hModule != NULL) {
ReplaceIATInAllMod("gdi32.dll", (PROC)OrigExtTextOutW, (PROC)HookExtTextOutW);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryA, (PROC)HookLoadLibraryA);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryW, (PROC)HookLoadLibraryW);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryExA, (PROC)HookLoadLibraryExA);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryExW, (PROC)HookLoadLibraryExW);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigGetProcAddress, (PROC)HookGetProcAddress);
};
return hModule;
};
HMODULE WINAPI HookLoadLibraryExA(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) {
HMODULE hModule = OrigLoadLibraryExA(lpLibFileName, hFile, dwFlags);
if ((hModule != NULL) && ((dwFlags & LOAD_LIBRARY_AS_DATAFILE) == 0) && ((dwFlags & LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE) == 0) && ((dwFlags & LOAD_LIBRARY_AS_IMAGE_RESOURCE) == 0)) {
ReplaceIATInAllMod("gdi32.dll", (PROC)OrigExtTextOutW, (PROC)HookExtTextOutW);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryA, (PROC)HookLoadLibraryA);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryW, (PROC)HookLoadLibraryW);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryExA, (PROC)HookLoadLibraryExA);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryExW, (PROC)HookLoadLibraryExW);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigGetProcAddress, (PROC)HookGetProcAddress);
};
return hModule;
};
HMODULE WINAPI HookLoadLibraryExW(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) {
HMODULE hModule = OrigLoadLibraryExW(lpLibFileName, hFile, dwFlags);
if ((hModule != NULL) && ((dwFlags & LOAD_LIBRARY_AS_DATAFILE) == 0) && ((dwFlags & LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE) == 0) && ((dwFlags & LOAD_LIBRARY_AS_IMAGE_RESOURCE) == 0)) {
ReplaceIATInAllMod("gdi32.dll", (PROC)OrigExtTextOutW, (PROC)HookExtTextOutW);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryA, (PROC)HookLoadLibraryA);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryW, (PROC)HookLoadLibraryW);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryExA, (PROC)HookLoadLibraryExA);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryExW, (PROC)HookLoadLibraryExW);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigGetProcAddress, (PROC)HookGetProcAddress);
};
return hModule;
};
FARPROC WINAPI HookGetProcAddress(HMODULE hModule, LPCSTR lpProcName) {
// 调用原GetProcAddress函数
FARPROC pfn = OrigGetProcAddress(hModule, lpProcName);
// 如果原GetProcAddress函数获取的是ExtTextOutW函数的地址,则替换
if (pfn == (FARPROC)OrigExtTextOutW)
pfn = (FARPROC)HookExtTextOutW;
return pfn;
};
// 替换指定模块导出表中的一个EAT项(导出函数地址)
BOOL ReplaceEATInOneMod(HMODULE hModule, LPCSTR pszFuncName, PROC pfnNew) {
ULONG ulSize; // 导出表的大小
PIMAGE_EXPORT_DIRECTORY pImageExportDir = NULL; // 导出表起始地址
PDWORD pAddressOfFunctions = NULL; // 导出函数地址表的起始地址
PWORD pAddressOfNameOrdinals = NULL; // 函数序数表的起始地址
PDWORD pAddressOfNames = NULL; // 函数名称地址表的起始地址
// 获取导出表起始地址
pImageExportDir = (PIMAGE_EXPORT_DIRECTORY)ImageDirectoryEntryToDataEx(hModule, TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &ulSize, NULL);
if (!pImageExportDir)
return FALSE;
// 导出函数地址表、函数序数表、函数名称地址表的起始地址
pAddressOfFunctions = (PDWORD)((LPBYTE)hModule + pImageExportDir->AddressOfFunctions);
pAddressOfNameOrdinals = (PWORD)((LPBYTE)hModule + pImageExportDir->AddressOfNameOrdinals);
pAddressOfNames = (PDWORD)((LPBYTE)hModule + pImageExportDir->AddressOfNames);
// 遍历函数名称地址表
for (DWORD i = 0; i < pImageExportDir->NumberOfNames; i++) {
if (_stricmp(pszFuncName, (LPSTR)((LPBYTE)hModule + pAddressOfNames[i])) != 0)
continue;
// 已经找到目标函数,获取导出函数地址
PROC* ppfn = (PROC*)&pAddressOfFunctions[pAddressOfNameOrdinals[i]];
pfnNew = (PROC)((LPBYTE)pfnNew - (LPBYTE)hModule); // To RVA
DWORD dwOldProtect;
BOOL bRet = FALSE;
// 替换目标EAT项的值为pfnNew
VirtualProtect(ppfn, sizeof(pfnNew), PAGE_READWRITE, &dwOldProtect);
bRet = WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew, sizeof(pfnNew), NULL);
VirtualProtect(ppfn, sizeof(pfnNew), dwOldProtect, &dwOldProtect);
return bRet;
};
return FALSE;
};

对于延迟加载DLL的情况,录入延迟加载GetMd5.dll中的GetMd5函数,则需要:

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
#include <windows.h>
#include <dbghelp.h>
#include <TlHelp32.h>
#pragma comment(lib, "Dbghelp.lib")
// 目标函数原函数指针
static HMODULE(WINAPI* OrigLoadLibraryA)(LPCSTR lpLibFileName) = LoadLibraryA;
static HMODULE(WINAPI* OrigLoadLibraryW)(LPCWSTR lpLibFileName) = LoadLibraryW;
static HMODULE(WINAPI* OrigLoadLibraryExA)(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) = LoadLibraryExA;
static HMODULE(WINAPI* OrigLoadLibraryExW)(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) = LoadLibraryExW;
// 自定义函数
BOOL HookGetMd5(LPCTSTR lpFileName, LPTSTR lpMd5);
HMODULE WINAPI HookLoadLibraryA(LPCSTR lpLibFileName);
HMODULE WINAPI HookLoadLibraryW(LPCWSTR lpLibFileName);
HMODULE WINAPI HookLoadLibraryExA(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
HMODULE WINAPI HookLoadLibraryExW(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags);
// 替换进程的指定模块导入表中的一个IAT项(导入函数地址)
BOOL ReplaceIATInOneMod(HMODULE hModule, LPCSTR pszDllName, PROC pfnTarget, PROC pfnNew);
// 替换进程的所有模块导入表中的一个IAT项(导入函数地址)
VOID ReplaceIATInAllMod(LPCSTR pszDllName, PROC pfnTarget, PROC pfnNew);
// 替换指定模块导出表中的一个EAT项(导出函数地址)
BOOL ReplaceEATInOneMod(HMODULE hModule, LPCSTR pszFuncName, PROC pfnNew);
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryA, (PROC)HookLoadLibraryA);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryW, (PROC)HookLoadLibraryW);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryExA, (PROC)HookLoadLibraryExA);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryExW, (PROC)HookLoadLibraryExW);
};
case DLL_THREAD_ATTACH: {};
case DLL_THREAD_DETACH: {};
case DLL_PROCESS_DETACH: {
break;
};
};
return TRUE;
};
//////////////////////////////////////////////////////////////////////////
// 替换进程的指定模块导入表中的一个IAT项(导入函数地址)
BOOL ReplaceIATInOneMod(HMODULE hModule, LPCSTR pszDllName, PROC pfnTarget, PROC pfnNew) {
ULONG ulSize; // 导入表的大小
PIMAGE_IMPORT_DESCRIPTOR pImageImportDesc = NULL; // 导入表起始地址
PIMAGE_THUNK_DATA pImageThunkData = NULL; // IMAGE_THUNK_DATA数组起始地址
// 获取导入表起始地址
pImageImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToDataEx(hModule, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize, NULL);
if (!pImageImportDesc)
return FALSE;
// 遍历导入表,查找目标函数
while (pImageImportDesc->OriginalFirstThunk || pImageImportDesc->TimeDateStamp || pImageImportDesc->ForwarderChain || pImageImportDesc->Name || pImageImportDesc->FirstThunk) {
if (_stricmp(pszDllName, (LPSTR)((LPBYTE)hModule + pImageImportDesc->Name)) == 0) {
pImageThunkData = (PIMAGE_THUNK_DATA)((LPBYTE)hModule + pImageImportDesc->FirstThunk);
while (pImageThunkData->u1.AddressOfData != 0) {
PROC* ppfn = (PROC*)&pImageThunkData->u1.Function;
if (*ppfn == pfnTarget) {
DWORD dwOldProtect;
BOOL bRet = FALSE;
// 替换目标IAT项的值为pfnNew
VirtualProtect(ppfn, sizeof(pfnNew), PAGE_READWRITE, &dwOldProtect);
bRet = WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew, sizeof(pfnNew), NULL);
VirtualProtect(ppfn, sizeof(pfnNew), dwOldProtect, &dwOldProtect);
return bRet;
};
// 指向下一个IMAGE_THUNK_DATA结构
pImageThunkData++;
};
};
// 指向下一个导入表描述符
pImageImportDesc++;
};
return FALSE;
};
// 替换进程的所有模块导入表中的一个IAT项(导入函数地址)
VOID ReplaceIATInAllMod(LPCSTR pszDllName, PROC pfnTarget, PROC pfnNew) {
MEMORY_BASIC_INFORMATION mbi = { 0 };
HMODULE hModuleThis; // 当前代码所处的模块
HANDLE hSnapshot = INVALID_HANDLE_VALUE;
MODULEENTRY32 me = { sizeof(MODULEENTRY32) };
BOOL bRet = FALSE;
VirtualQuery(ReplaceIATInAllMod, &mbi, sizeof(mbi));
hModuleThis = (HMODULE)mbi.AllocationBase;
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());
if (hSnapshot == INVALID_HANDLE_VALUE)
return;
bRet = Module32First(hSnapshot, &me);
while (bRet) {
if (me.hModule != hModuleThis) // 排除当前代码所处的模块
ReplaceIATInOneMod(me.hModule, pszDllName, pfnTarget, pfnNew);
bRet = Module32Next(hSnapshot, &me);
};
CloseHandle(hSnapshot);
};
// 自定义函数
BOOL HookGetMd5(LPCTSTR lpFileName, LPTSTR lpMd5) {
MessageBox(NULL, TEXT("延迟加载dll中的GetMd5函数已被Hook"), TEXT("提示"), MB_OK);
return TRUE;
};
HMODULE WINAPI HookLoadLibraryA(LPCSTR lpLibFileName) {
// 调用原LoadLibraryA函数
HMODULE hModule = OrigLoadLibraryA(lpLibFileName);
// 原LoadLibraryA函数执行完毕,进程所有模块的导入表中的相关IAT项再替换一次
if (hModule != NULL) {
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryA, (PROC)HookLoadLibraryA);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryW, (PROC)HookLoadLibraryW);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryExA, (PROC)HookLoadLibraryExA);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryExW, (PROC)HookLoadLibraryExW);
if (strstr(lpLibFileName, "GetMd5.dll"))
ReplaceEATInOneMod(hModule, "GetMd5", (PROC)HookGetMd5);
};
return hModule;
};
HMODULE WINAPI HookLoadLibraryW(LPCWSTR lpLibFileName) {
HMODULE hModule = OrigLoadLibraryW(lpLibFileName);
if (hModule != NULL) {
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryA, (PROC)HookLoadLibraryA);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryW, (PROC)HookLoadLibraryW);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryExA, (PROC)HookLoadLibraryExA);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryExW, (PROC)HookLoadLibraryExW);
if (wcsstr(lpLibFileName, L"GetMd5.dll"))
ReplaceEATInOneMod(hModule, "GetMd5", (PROC)HookGetMd5);
};
return hModule;
};
HMODULE WINAPI HookLoadLibraryExA(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) {
HMODULE hModule = OrigLoadLibraryExA(lpLibFileName, hFile, dwFlags);
if ((hModule != NULL) && ((dwFlags & LOAD_LIBRARY_AS_DATAFILE) == 0) && ((dwFlags & LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE) == 0) && ((dwFlags & LOAD_LIBRARY_AS_IMAGE_RESOURCE) == 0)) {
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryA, (PROC)HookLoadLibraryA);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryW, (PROC)HookLoadLibraryW);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryExA, (PROC)HookLoadLibraryExA);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryExW, (PROC)HookLoadLibraryExW);
if (strstr(lpLibFileName, "GetMd5.dll"))
ReplaceEATInOneMod(hModule, "GetMd5", (PROC)HookGetMd5);
};
return hModule;
};
HMODULE WINAPI HookLoadLibraryExW(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) {
HMODULE hModule = OrigLoadLibraryExW(lpLibFileName, hFile, dwFlags);
if ((hModule != NULL) && ((dwFlags & LOAD_LIBRARY_AS_DATAFILE) == 0) && ((dwFlags & LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE) == 0) && ((dwFlags & LOAD_LIBRARY_AS_IMAGE_RESOURCE) == 0)) {
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryA, (PROC)HookLoadLibraryA);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryW, (PROC)HookLoadLibraryW);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryExA, (PROC)HookLoadLibraryExA);
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigLoadLibraryExW, (PROC)HookLoadLibraryExW);
if (wcsstr(lpLibFileName, L"GetMd5.dll"))
ReplaceEATInOneMod(hModule, "GetMd5", (PROC)HookGetMd5);
};
return hModule;
};
// 替换指定模块导出表中的一个EAT项(导出函数地址)
BOOL ReplaceEATInOneMod(HMODULE hModule, LPCSTR pszFuncName, PROC pfnNew) {
ULONG ulSize; // 导出表的大小
PIMAGE_EXPORT_DIRECTORY pImageExportDir = NULL; // 导出表起始地址
PDWORD pAddressOfFunctions = NULL; // 导出函数地址表的起始地址
PWORD pAddressOfNameOrdinals = NULL; // 函数序数表的起始地址
PDWORD pAddressOfNames = NULL; // 函数名称地址表的起始地址
// 获取导出表起始地址
pImageExportDir = (PIMAGE_EXPORT_DIRECTORY)ImageDirectoryEntryToDataEx(hModule, TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &ulSize, NULL);
if (!pImageExportDir)
return FALSE;
// 导出函数地址表、函数序数表、函数名称地址表的起始地址
pAddressOfFunctions = (PDWORD)((LPBYTE)hModule + pImageExportDir->AddressOfFunctions);
pAddressOfNameOrdinals = (PWORD)((LPBYTE)hModule + pImageExportDir->AddressOfNameOrdinals);
pAddressOfNames = (PDWORD)((LPBYTE)hModule + pImageExportDir->AddressOfNames);
// 遍历函数名称地址表
for (DWORD i = 0; i < pImageExportDir->NumberOfNames; i++) {
if (_stricmp(pszFuncName, (LPSTR)((LPBYTE)hModule + pAddressOfNames[i])) != 0)
continue;
// 已经找到目标函数,获取导出函数地址
PROC* ppfn = (PROC*)&pAddressOfFunctions[pAddressOfNameOrdinals[i]];
pfnNew = (PROC)((LPBYTE)pfnNew - (LPBYTE)hModule); // To RVA
DWORD dwOldProtect;
BOOL bRet = FALSE;
// 替换目标EAT项的值为pfnNew
VirtualProtect(ppfn, sizeof(pfnNew), PAGE_READWRITE, &dwOldProtect);
bRet = WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew, sizeof(pfnNew), NULL);
VirtualProtect(ppfn, sizeof(pfnNew), dwOldProtect, &dwOldProtect);
return bRet;
};
return FALSE;
};

此外因延迟加载使用GetProcAddress获取函数地址,所以只Hook掉该函数也可以:

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
#include <windows.h>
#include <dbghelp.h>
#include <TlHelp32.h>
#pragma comment(lib, "Dbghelp.lib")
// 目标函数原函数指针
static FARPROC(WINAPI* OrigGetProcAddress)(HMODULE hModule, LPCSTR lpProcName) = GetProcAddress;
// 自定义函数
BOOL HookGetMd5(LPCTSTR lpFileName, LPTSTR lpMd5);
FARPROC WINAPI HookGetProcAddress(HMODULE hModule, LPCSTR lpProcName);
// 替换进程的指定模块导入表中的一个IAT项(导入函数地址)
BOOL ReplaceIATInOneMod(HMODULE hModule, LPCSTR pszDllName, PROC pfnTarget, PROC pfnNew);
// 替换进程的所有模块导入表中的一个IAT项(导入函数地址)
VOID ReplaceIATInAllMod(LPCSTR pszDllName, PROC pfnTarget, PROC pfnNew);
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {
ReplaceIATInAllMod("kernel32.dll", (PROC)OrigGetProcAddress, (PROC)HookGetProcAddress);
};
case DLL_THREAD_ATTACH: {};
case DLL_THREAD_DETACH: {};
case DLL_PROCESS_DETACH: {
break;
};
};
return TRUE;
};
//////////////////////////////////////////////////////////////////////////
// 替换进程的指定模块导入表中的一个IAT项(导入函数地址)
BOOL ReplaceIATInOneMod(HMODULE hModule, LPCSTR pszDllName, PROC pfnTarget, PROC pfnNew) {
ULONG ulSize; // 导入表的大小
PIMAGE_IMPORT_DESCRIPTOR pImageImportDesc = NULL; // 导入表起始地址
PIMAGE_THUNK_DATA pImageThunkData = NULL; // IMAGE_THUNK_DATA数组起始地址
// 获取导入表起始地址
pImageImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToDataEx(hModule, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize, NULL);
if (!pImageImportDesc)
return FALSE;
// 遍历导入表,查找目标函数
while (pImageImportDesc->OriginalFirstThunk || pImageImportDesc->TimeDateStamp || pImageImportDesc->ForwarderChain || pImageImportDesc->Name || pImageImportDesc->FirstThunk) {
if (_stricmp(pszDllName, (LPSTR)((LPBYTE)hModule + pImageImportDesc->Name)) == 0) {
pImageThunkData = (PIMAGE_THUNK_DATA)((LPBYTE)hModule + pImageImportDesc->FirstThunk);
while (pImageThunkData->u1.AddressOfData != 0) {
PROC* ppfn = (PROC*)&pImageThunkData->u1.Function;
if (*ppfn == pfnTarget) {
DWORD dwOldProtect;
BOOL bRet = FALSE;
// 替换目标IAT项的值为pfnNew
VirtualProtect(ppfn, sizeof(pfnNew), PAGE_READWRITE, &dwOldProtect);
bRet = WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew, sizeof(pfnNew), NULL);
VirtualProtect(ppfn, sizeof(pfnNew), dwOldProtect, &dwOldProtect);
return bRet;
};
// 指向下一个IMAGE_THUNK_DATA结构
pImageThunkData++;
};
};
// 指向下一个导入表描述符
pImageImportDesc++;
};
return FALSE;
};
// 替换进程的所有模块导入表中的一个IAT项(导入函数地址)
VOID ReplaceIATInAllMod(LPCSTR pszDllName, PROC pfnTarget, PROC pfnNew) {
MEMORY_BASIC_INFORMATION mbi = { 0 };
HMODULE hModuleThis; // 当前代码所处的模块
HANDLE hSnapshot = INVALID_HANDLE_VALUE;
MODULEENTRY32 me = { sizeof(MODULEENTRY32) };
BOOL bRet = FALSE;
VirtualQuery(ReplaceIATInAllMod, &mbi, sizeof(mbi));
hModuleThis = (HMODULE)mbi.AllocationBase;
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());
if (hSnapshot == INVALID_HANDLE_VALUE)
return;
bRet = Module32First(hSnapshot, &me);
while (bRet) {
if (me.hModule != hModuleThis) // 排除当前代码所处的模块
ReplaceIATInOneMod(me.hModule, pszDllName, pfnTarget, pfnNew);
bRet = Module32Next(hSnapshot, &me);
};
CloseHandle(hSnapshot);
};
// 自定义函数
BOOL HookGetMd5(LPCTSTR lpFileName, LPTSTR lpMd5) {
MessageBox(NULL, TEXT("延迟加载dll中的GetMd5函数已被Hook"), TEXT("提示"), MB_OK);
return TRUE;
};
FARPROC WINAPI HookGetProcAddress(HMODULE hModule, LPCSTR lpProcName) {
// 调用原GetProcAddress函数
FARPROC pfn = OrigGetProcAddress(hModule, lpProcName);
// 如果原GetProcAddress函数获取的是GetMd5函数的地址,则替换
if (_stricmp(lpProcName, "GetMd5") == 0)
pfn = (FARPROC)HookGetMd5;
return pfn;
};