0%

PE文件简要解析器

PE文件解析器

检测是否为PE文件

首先先检测该文件是不是PE文件,根据PE文件特性

PE文件的第一个字节位于一个传统的MS-DOS头部,称作IMAGE_DOS_HEADER,该结构体第一个成员的值可用作判断文件是否为PE文件。其值应被设置为4D5A(但是实际存储在文件中,是以小端序形式存储的,如下所示),

image-20220412211923140

这才是PE文件的标识。

在《逆向工程核心原理》中,我们可以知道PE文件类型大概有以下几种

image-20220412211706773

那么,如何访问该成员呢?

利用结构体访问成员的方式即可

1
2
3
4
5
PIMAGE_DOS_HEADER P_DosHeader;////定义指向_IMAGE_DOS_HEADER结构体的指针
P_DosHeader = (PIMAGE_DOS_HEADER)buffer;//在编写这一步的时候,我忽视了buffer的字节大小和指针的字节大小不对等(因为我刚开始定义buffer疏忽了将其作为指针来定义),从而触发warning: cast to pointer from integer of different size [-Wint-to-pointer-cast],buffer改为指针类型即可解决该warning
printf("Info as follow:\n");
printf("The Flag bit of this file is %X\n", P_DosHeader->e_magic);
printf("The offset of the DOS header is:%X\n", P_DosHeader->e_lfanew);

输出如下图所示

image-20220412231706106

可以看到,正确的。

当然我们也可以为MZ加个判断

1
2
3
4
5
if(P_DosHeader->e_magic != IMAGE_DOS_SIGNATURE)//5A4D-"ZM",4D5A-"MZ"
{
printf("This file is not a PE file!\n");
return 0;
}

输入的路径格式可以是如下图所示的

image-20220412234003731

顺带一提上述被检测的文件就是记事本这个应用程序。

NT头偏移量(上图的0x000000E0)根据程序不同,其值也不同

接下来可以正式开始解析PE文件

NT_HEADER

首先是NT头包含哪些信息

  1. signature
  2. FILE_HEADER
  3. OPTIONAL_HEADER

后面两个其实都包含大量数据。我们以PEview工具查看做示例,如下图所示

image-20220413073714649

image-20220413073726016

东西很多,但写起来难度不是很大

FILE_HEADER

在签名里,一个PE有效文件其字段应为0x00004550,ASCII码为PE00

访问PE文件头的方式为:

P_NtHeader=Imagebase+dosHeader->e_lfanew;

然后是机器码和节区数

image-20220413102414066

因为COFF符号表在PE文件中比较少见,所以这里有关COFF符号表的两个成员就不输出信息了

Characteristics,普通的.exe文件这个字段的值一般是10F,DLL文件则一般是2102

1
2
3
4
5
6
7
//解析PE文件头
PIMAGE_NT_HEADERS32 P_NtHeader; //定义指向_IMAGE_NT_HEADERS32结构体的指针
P_NtHeader = (PIMAGE_NT_HEADERS32)(buffer + P_DosHeader->e_lfanew);
printf("The Flag bit of this file is %X\n", P_NtHeader->Signature);
printf("Machine: %X\n", P_NtHeader->FileHeader.Machine);
printf("Number of Sections: %d\n", P_NtHeader->FileHeader.NumberOfSections);
printf("Characteristics: %X\n", P_NtHeader->FileHeader.Characteristics);

OPTIONAL_HEADER

由于成员众多,我仅列出了在逆向分析时,我经常用到的一些成员

printf即可,没什么技术含量

稍微提一下Subsystem,它的值用来区分系统文件和普通的可执行文件。

image-20220413111626984

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
printf("The follow is the info of PE file optional header:\n");
printf("Magic: %X\n", P_NtHeader->OptionalHeader.Magic); //这个值是0x10B,表示PE32,0x20B表示PE32+(64位)
printf("AddressOfEntryPoint: %X\n", P_NtHeader->OptionalHeader.AddressOfEntryPoint);//这项可以告诉我们程序的入口地址
printf("ImageBase: %X\n", P_NtHeader->OptionalHeader.ImageBase);//这个值是文件的起始地址,也就是我们要解析的文件的起始地址
printf("SectionAlignment: %X\n", P_NtHeader->OptionalHeader.SectionAlignment);//节区对齐值
printf("FileAlignment: %X\n", P_NtHeader->OptionalHeader.FileAlignment);//文件对齐值
printf("SizeOfImage: %X\n", P_NtHeader->OptionalHeader.SizeOfImage);//文件大小
printf("SizeOfHeaders: %X\n", P_NtHeader->OptionalHeader.SizeOfHeaders);//头部大小
printf("CheckSum: %X\n", P_NtHeader->OptionalHeader.CheckSum);//映像校验和,用处就是用来检验文件是否被修改过。
printf("Subsystem: %X\n", P_NtHeader->OptionalHeader.Subsystem);//子系统
printf("NumberOfRvaAndSizes: %X\n", P_NtHeader->OptionalHeader.NumberOfRvaAndSizes);//RVA和Sizes的数量
printf("Number Of Data Directories: %X\n", P_NtHeader->OptionalHeader.NumberOfRvaAndSizes);//数据目录的数量
printf("------------------------Thats's basic info of OPTIONAL_HEADER----------------------------\n");
printf("\n");
printf("------------------------Next are some tables----------------------------\n");
printf("\n");
//解析DATA_DIRECTORY
printf("The follow is the info of PE file data directory:\n");
printf("Address of Export Table: %X\n", P_NtHeader->OptionalHeader.DataDirectory[0].VirtualAddress);
printf("Size of Export Table: %X\n", P_NtHeader->OptionalHeader.DataDirectory[0].Size);
printf("Address of Import Table: %X\n", P_NtHeader->OptionalHeader.DataDirectory[1].VirtualAddress);
printf("Size of Import Table: %X\n", P_NtHeader->OptionalHeader.DataDirectory[1].Size);
printf("Address of Resource Table: %X\n", P_NtHeader->OptionalHeader.DataDirectory[2].VirtualAddress);
printf("Size of Resource Table: %X\n", P_NtHeader->OptionalHeader.DataDirectory[2].Size);
printf("Address of TLS Table: %X\n", P_NtHeader->OptionalHeader.DataDirectory[9].VirtualAddress);
printf("Size of TLS Table: %X\n", P_NtHeader->OptionalHeader.DataDirectory[9].Size);
printf("Address of IAT: %X\n", P_NtHeader->OptionalHeader.DataDirectory[12].VirtualAddress);
printf("Size of IAT: %X\n", P_NtHeader->OptionalHeader.DataDirectory[12].Size);
printf("------------------------Thats's basic info of DATA_DIRECTORY----------------------------\n");
printf("\n");

在上面的各种表中,需要特别关注IMPORT和EXPORT这两个,反调试相关的话,TLS的地址其实是保存在FS寄存器的,实际访问也是通过FS寄存器。

至此,NT头的解析算是完成了。

节区头

这个直接构造循环输出其相关信息即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
printf("The follow is the info of PE file section header:\n");
PIMAGE_SECTION_HEADER P_SectionHeader;
P_SectionHeader = (PIMAGE_SECTION_HEADER)(buffer + P_DosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS32));
for(int i = 0; i < P_NtHeader->FileHeader.NumberOfSections; i++)
{
printf("Section Name: %s\n", P_SectionHeader->Name);
printf("Virtual Address: %X\n", P_SectionHeader->VirtualAddress);
printf("Size of Raw Data: %X\n", P_SectionHeader->SizeOfRawData);
printf("Pointer to Raw Data: %X\n", P_SectionHeader->PointerToRawData);
printf("Pointer to Relocations: %X\n", P_SectionHeader->PointerToRelocations);
printf("Pointer to Linenumbers: %X\n", P_SectionHeader->PointerToLinenumbers);
printf("Number of Relocations: %X\n", P_SectionHeader->NumberOfRelocations);
printf("Number of Linenumbers: %X\n", P_SectionHeader->NumberOfLinenumbers);
printf("Characteristics: %X\n", P_SectionHeader->Characteristics);
printf("------------------------Thats's basic info of SECTION----------------------------\n");
printf("\n");

image-20220413115002184

至此,一些基本的信息得以展现,现在main函数已经非常臃肿了,我们将还没实现的导入/出表,重定位表,和TLS表分别用对应的函数来输出相关信息。

IMPORT_TABLE

首先是导入表的一些基本信息

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
typedef struct _IMAGE_IMPORT_DESCRIPTOR{
union{
DWORD Characteristics;
DWORD OriginalFirstThunk;//导入名称表INT
};
DWORD TimeDateStamp; //时间戳
DWORD ForwarderChain;
DWORD Name; //dll名称
DWORD FirstThunk; //导入地址表IAT
}IMAGE_IMPORT_DESCRIPTOR;
//OriginalFirstThunk和FirstThunk都指向的是_IMAGE_THUNK_DATA32结构体
//导入名称表最高位是0,就是名称导入
//最高位是1,就是序号导入
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD,导入函数的地址,在加载到内存后,这里才起作用
DWORD Ordinal; // 假如是序号导入的,会用到这里
DWORD AddressOfData; //PIMAGE_IMPORT_BY_NAME,假如是函数名导入的,用到这 里 ,它指向另外一个结构体:PIMGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;

typedef struct _IMAGE_IMPORT_BY_NAME{
WORD Hint; //序号
BYTE NAME[1]; //函数名
}IMAGE_IMPORT_BY_NAME,*PIMAGE_IMPORT_BY_NAME;

如下是关于函数以何种方式输入的更详细的说明

image-20220413185242749

还要提一嘴,exe文件的默认加载及地址是0x004000000

dll文件的则是0x10000000

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
void ImportTable(char* buffer)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer);
PIMAGE_DATA_DIRECTORY pDataDirectory = (PIMAGE_DATA_DIRECTORY)(pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_IMPORT);//
PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(RvaToOffset(pDataDirectory->VirtualAddress, buffer) + buffer);//指向导入表的第一个描述符
//遍历导入表
while(pImportDescriptor->Name)
{
char * szDllname = (char*)(RvaToOffset(pImportDescriptor->Name, buffer) + buffer);//指向DLL名字
printf("DLL Name: %s\n", szDllname);
PIMAGE_THUNK_DATA32 PIAT = (PIMAGE_THUNK_DATA32)(RvaToOffset(pImportDescriptor->FirstThunk, buffer) + buffer);//指向IAT
PIMAGE_THUNK_DATA32 PINT = (PIMAGE_THUNK_DATA32)(RvaToOffset(pImportDescriptor->OriginalFirstThunk, buffer) + buffer);//指向INT
//解析IAT,IAT是一个结构体数组,其结尾为0
while(PIAT->u1.Ordinal)
{
if(PIAT->u1.Ordinal & 0x80000000)//如果是序号
{
printf("Ordinal: %2d\n", PIAT->u1.Ordinal & 0xFFFF);
printf("\n");
}
else//如果是函数名
{
PIMAGE_IMPORT_BY_NAME szFuncName = (PIMAGE_IMPORT_BY_NAME)(RvaToOffset(PIAT->u1.AddressOfData, buffer) + buffer);
printf("Function Name: %s\n", szFuncName->Name);
printf("Hint: %X\n", szFuncName->Hint);
printf("\n");
}
PIAT++;
}
pImportDescriptor++;
}
}

效果图

image-20220414191127255

image-20220414191140997

有些DLL函数名称会乱码,就很离谱

EXPORT_TABLE

image-20220414130435339

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct _IMAGE_EXPORT_DIRECTORY { 
DWORD Characteristics; // (1) 保留,恒为0x00000000
DWORD TimeDateStamp; // (2) 时间戳
WORD MajorVersion; // (3) 主版本号,一般不赋值
WORD MinorVersion; // (4) 子版本号,一般不赋值
DWORD Name; // (5) 模块名称*
DWORD Base; // (6) 索引基数*
DWORD NumberOfFunctions; // (7) 导出地址表中成员个数*
DWORD NumberOfNames; // (8) 导出名称表中成员个数*
DWORD AddressOfFunctions; // (9) 导出地址表(EAT)*
DWORD AddressOfNames; // (10) 导出名称表(ENT)*
DWORD AddressOfNameOrdinals; // (11) 指向导出序号数组*
}IMAGE_EXPORT_DIRECTORY,*PIMAGE_EXPORT_DIRECTORY;

其结构如上。

image-20220414131910912

image-20220414132204351

EAT: 导出地址表 ENT: 导出名称表(与EOT一一对应)

EOT:导出序号表

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
void ExportTable(char* buffer)
{
//Dos头
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;
//PE
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer);
//定位数据目录表中的导出表
PIMAGE_DATA_DIRECTORY pExportDir = pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT;
//填充导出表结构
PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(RvaToOffset(pExportDir->VirtualAddress, buffer) + buffer);
char* szName = (char*)(RvaToOffset(pExport->Name, buffer) + buffer);
if (pExport->AddressOfFunctions == 0)
{
printf("Without EXPORT_TABLE!\n");
return;
}
printf("Name:%s\n", szName);
printf("Number of Functions:%08X\n", pExport->NumberOfFunctions);
printf("Number of Names:%08X\n", pExport->NumberOfNames);
printf("Func_addr:%08X\n", pExport->AddressOfFunctions);
printf("\n");
//获取函数数量
DWORD dwNumOfFUN = pExport->NumberOfFunctions;
//函数名数量
DWORD dwNumOfNames = pExport->NumberOfNames;
//基
DWORD dwBase = pExport->Base;
//导出地址表
PDWORD pEat32 = (PDWORD)(RvaToOffset(pExport->AddressOfFunctions, buffer) + buffer);
//导出名称表
PDWORD pEnt32 = (PDWORD)(RvaToOffset(pExport->AddressOfNames, buffer) + buffer);
//导出序号表
PWORD pId = (PWORD)(RvaToOffset(pExport->AddressOfNameOrdinals, buffer) + buffer);
for (int i = 0; i < dwNumOfFUN; i++)
{
if (pEat32[i] == 0)
continue;
DWORD Id = 0;
for (; Id < dwNumOfNames; Id++)
{
if (pId[Id] == i)
break;
}
if (Id == dwNumOfNames)
{
printf("Name:%X Address:0x%08X Name[NuLL]\n", i + dwBase, pEat32[i]);
printf("\n");
}
else
{
char* szFunName = (char*)(RvaToOffset(pEnt32[Id], buffer) + buffer);
printf("Name:%X Address:0x%08X Name[%s]\n", i + dwBase, pEat32[i],szFunName);
printf("\n");
}
printf("-------------------------------------\n");
}
printf("----------------------------------------------------------\n");
}

image-20220414191329512

这个解析的其实是老头环的导出表 :)

TLS_TABLE

image-20220414190838236

1
2
3
4
5
6
7
8
9
10
11
12
void TlsTable(char* buffer)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer);
PIMAGE_DATA_DIRECTORY pTLSDir = (pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_TLS);//定位数据目录表中的TLS表
PIMAGE_TLS_DIRECTORY pTLS = (PIMAGE_TLS_DIRECTORY)(RvaToOffset(pTLSDir->VirtualAddress, buffer) + buffer);//填充TLS结构
printf("StartAddressOfRawData: %08X\n", pTLS->StartAddressOfRawData);
printf("EndAddressOfRawData: %08X\n", pTLS->EndAddressOfRawData);
printf("TLS_Callback: %08X\n", pTLS->AddressOfCallBacks);//tls回调函数
printf("TLS_Size: %08X\n", pTLS->SizeOfZeroFill);
printf("TLS_Characteristics: %08X\n", pTLS->Characteristics);
}

全篇

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
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>


DWORD RvaToOffset(DWORD dwRva, char* buffer); //计算数据目录表起始位置到文件头的偏移(RVAtoRAW)
void ImportTable(char* buffer); //解析导入表函数
void ExportTable(char* buffer); //解析导出表函数
void TlsTable(char* buffer); //解析TLS表的函数

int main()
{
int nFileLength = 0;
FILE* pFile = NULL;
char path[100];
char *buffer;

printf("Please input the path of the file you want to look by my parser of PE:\n");
gets(path);
fopen_s(&pFile,path, "rb");
if(pFile == NULL)
{
printf("Open file failed,maybe it's doesn't exist?\n");
return 0;
}
fseek(pFile, 0, SEEK_END); //允许文件读写位置移动到文件尾,返回文件长度
nFileLength = ftell(pFile); //获取文件长度
if(nFileLength == 0)
{
printf("The file is empty!\n");
return 0;
}
rewind(pFile); //将文件指针重新指向文件开头

//接下来为文件分配空间
int ImageBase = nFileLength * sizeof(char) + 1;
buffer = (char*)malloc(ImageBase);
memset(buffer, 0, nFileLength * sizeof(char) + 1);
//读取文件内容
fread(buffer, 1, ImageBase, pFile);
//至此,文件读取操作完成

PIMAGE_DOS_HEADER P_DosHeader; //定义指向_IMAGE_DOS_HEADER结构体的指针
P_DosHeader = (PIMAGE_DOS_HEADER)buffer;
printf("Basic Info is as follow:\n");
printf("The Flag bit of this file is %X\n", P_DosHeader->e_magic);
printf("The offset of the DOS header is:%X\n", P_DosHeader->e_lfanew);
printf("------------------------Thats's basic info of DosHeaderStruct----------------------------\n");
printf("\n");
if(P_DosHeader->e_magic != IMAGE_DOS_SIGNATURE)//判断是否为PE文件
{
printf("This file is not a PE file!\n");
return 0;
}
//解析PE文件头
//解析FILE_HEADER
PIMAGE_NT_HEADERS32 P_NtHeader; //定义指向_IMAGE_NT_HEADERS32结构体的指针
P_NtHeader = (PIMAGE_NT_HEADERS32)(buffer + P_DosHeader->e_lfanew);
printf("The follow is the info of PE file header:\n");
printf("The Flag bit of this file is %X\n", P_NtHeader->Signature);
printf("Machine: %X\n", P_NtHeader->FileHeader.Machine);
printf("Number of Sections: %d\n", P_NtHeader->FileHeader.NumberOfSections);
printf("Characteristics: %X\n", P_NtHeader->FileHeader.Characteristics);
/*printf("TimeDateStamp: %X\n", P_NtHeader->FileHeader.TimeDateStamp);//这项可以告诉我们编译器创建文件的时间,注释掉吧,感觉没用过*/
printf("------------------------Thats's basic info of FILE_HEADER----------------------------\n");
printf("\n");
//解析OPTIONAL_HEADER
printf("The follow is the info of PE file optional header:\n");
printf("Magic: %X\n", P_NtHeader->OptionalHeader.Magic); //这个值是0x10B,表示PE32,0x20B表示PE32+(64位)
printf("AddressOfEntryPoint: %X\n", P_NtHeader->OptionalHeader.AddressOfEntryPoint);//这项可以告诉我们程序的入口地址
printf("ImageBase: %X\n", P_NtHeader->OptionalHeader.ImageBase);//这个值是文件的起始地址,也就是我们要解析的文件的起始地址
printf("SectionAlignment: %X\n", P_NtHeader->OptionalHeader.SectionAlignment);//节区对齐值
printf("FileAlignment: %X\n", P_NtHeader->OptionalHeader.FileAlignment);//文件对齐值
printf("SizeOfImage: %X\n", P_NtHeader->OptionalHeader.SizeOfImage);//文件大小
printf("SizeOfHeaders: %X\n", P_NtHeader->OptionalHeader.SizeOfHeaders);//头部大小
printf("CheckSum: %X\n", P_NtHeader->OptionalHeader.CheckSum);//映像校验和,用处就是用来检验文件是否被修改过。
printf("Subsystem: %X\n", P_NtHeader->OptionalHeader.Subsystem);//子系统
printf("NumberOfRvaAndSizes: %X\n", P_NtHeader->OptionalHeader.NumberOfRvaAndSizes);//RVA和Sizes的数量
printf("Number Of Data Directories: %X\n", P_NtHeader->OptionalHeader.NumberOfRvaAndSizes);//数据目录的数量
printf("------------------------Thats's basic info of OPTIONAL_HEADER----------------------------\n");
printf("\n");
printf("------------------------Next are some tables----------------------------\n");
printf("\n");
//解析DATA_DIRECTORY
printf("The follow is the info of PE file data directory:\n");
printf("Address of Export Table: %X\n", P_NtHeader->OptionalHeader.DataDirectory[0].VirtualAddress);
printf("Size of Export Table: %X\n", P_NtHeader->OptionalHeader.DataDirectory[0].Size);
printf("Address of Import Table: %X\n", P_NtHeader->OptionalHeader.DataDirectory[1].VirtualAddress);
printf("Size of Import Table: %X\n", P_NtHeader->OptionalHeader.DataDirectory[1].Size);
printf("Address of Resource Table: %X\n", P_NtHeader->OptionalHeader.DataDirectory[2].VirtualAddress);
printf("Size of Resource Table: %X\n", P_NtHeader->OptionalHeader.DataDirectory[2].Size);
printf("Address of TLS Table: %X\n", P_NtHeader->OptionalHeader.DataDirectory[9].VirtualAddress);
printf("Size of TLS Table: %X\n", P_NtHeader->OptionalHeader.DataDirectory[9].Size);
printf("Address of IAT: %X\n", P_NtHeader->OptionalHeader.DataDirectory[12].VirtualAddress);
printf("Size of IAT: %X\n", P_NtHeader->OptionalHeader.DataDirectory[12].Size);
printf("------------------------Thats's basic info of DATA_DIRECTORY----------------------------\n");
printf("\n");
//解析SECTION_HEADER
printf("The follow is the info of PE file section header:\n");
PIMAGE_SECTION_HEADER P_SectionHeader;
P_SectionHeader = (PIMAGE_SECTION_HEADER)(buffer + P_DosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS32));
for(int i = 0; i < P_NtHeader->FileHeader.NumberOfSections; i++)
{
printf("Section Name: %s\n", P_SectionHeader->Name);
printf("Virtual Address: %X\n", P_SectionHeader->VirtualAddress);
printf("Size of Raw Data: %X\n", P_SectionHeader->SizeOfRawData);
printf("Pointer to Raw Data: %X\n", P_SectionHeader->PointerToRawData);
printf("Pointer to Relocations: %X\n", P_SectionHeader->PointerToRelocations);
printf("Pointer to Linenumbers: %X\n", P_SectionHeader->PointerToLinenumbers);
printf("Number of Relocations: %X\n", P_SectionHeader->NumberOfRelocations);
printf("Number of Linenumbers: %X\n", P_SectionHeader->NumberOfLinenumbers);
printf("Characteristics: %X\n", P_SectionHeader->Characteristics);
printf("------------------------Thats's basic info of SECTION----------------------------\n");
printf("\n");
P_SectionHeader++;
}
printf("Please input which information you want to look :\n");
printf("1.Export Table\n");
printf("2.Import Table\n");
printf("3.TLS Table\n");
printf("4.exit\n");
int choice;
while(1)
{
printf("Input a number: ");
scanf("%d", &choice);
if (choice == 1)
ExportTable(buffer);
else if (choice == 2)
ImportTable(buffer);
else if (choice == 3)
TlsTable(buffer);
else if (choice == 4)
return 0;
else
printf("Please input again and make sur that's valid\n");
}
system("pause");
free(buffer);//走之前别忘了清空缓存区
return 0;
}

DWORD RvaToOffset(DWORD dwRva, char* buffer)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;//Dos头
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer);//PE头
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);//区段表
//判断是否落在头部当中
if (dwRva < pSection[0].VirtualAddress)
{
return dwRva;
}
for (int i = 0; i < pNt->FileHeader.NumberOfSections; i++)
{
if (dwRva >= pSection[i].VirtualAddress && dwRva <= pSection[i].VirtualAddress + pSection[i].Misc.VirtualSize)
{
//dwRva-pSection[i].VirtualAddress是数据目录表到区段起始地址的偏移(OFFSET)
// pSection[i].PointerToRawData区段到文件头的偏移(OFFSET)
return dwRva - pSection[i].VirtualAddress + pSection[i].PointerToRawData;//返回虚拟地址对应的文件偏移地址(RAW),这个公式一定得掌握
}
}
}
void ImportTable(char* buffer)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer);
PIMAGE_DATA_DIRECTORY pDataDirectory = (PIMAGE_DATA_DIRECTORY)(pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_IMPORT);//
PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(RvaToOffset(pDataDirectory->VirtualAddress, buffer) + buffer);//指向导入表的第一个描述符
//遍历导入表
while(pImportDescriptor->Name)
{
char * szDllname = (char*)(RvaToOffset(pImportDescriptor->Name, buffer) + buffer);//指向DLL名字
printf("DLL Name: %s\n", szDllname);
PIMAGE_THUNK_DATA32 PIAT = (PIMAGE_THUNK_DATA32)(RvaToOffset(pImportDescriptor->FirstThunk, buffer) + buffer);//指向IAT
PIMAGE_THUNK_DATA32 PINT = (PIMAGE_THUNK_DATA32)(RvaToOffset(pImportDescriptor->OriginalFirstThunk, buffer) + buffer);//指向INT
//解析IAT,IAT是一个结构体数组,其结尾为0
while(PIAT->u1.Ordinal)
{
if(PIAT->u1.Ordinal & 0x80000000)//如果是序号
{
printf("Ordinal: %2d\n", PIAT->u1.Ordinal & 0xFFFF);
printf("\n");
}
else//如果是函数名
{
PIMAGE_IMPORT_BY_NAME szFuncName = (PIMAGE_IMPORT_BY_NAME)(RvaToOffset(PIAT->u1.AddressOfData, buffer) + buffer);
printf("Function Name: %s\n", szFuncName->Name);
printf("Hint: %X\n", szFuncName->Hint);
printf("\n");
}
PIAT++;
}
pImportDescriptor++;
}
}
void ExportTable(char* buffer)
{
//Dos头
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;
//PE
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer);
//定位数据目录表中的导出表
PIMAGE_DATA_DIRECTORY pExportDir = pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT;
//填充导出表结构
PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(RvaToOffset(pExportDir->VirtualAddress, buffer) + buffer);
char* szName = (char*)(RvaToOffset(pExport->Name, buffer) + buffer);
if (pExport->AddressOfFunctions == 0)
{
printf("Without EXPORT_TABLE!\n");
return;
}
printf("Name:%s\n", szName);
printf("Number of Functions:%08X\n", pExport->NumberOfFunctions);
printf("Number of Names:%08X\n", pExport->NumberOfNames);
printf("Func_addr:%08X\n", pExport->AddressOfFunctions);
printf("\n");
//获取函数数量
DWORD dwNumOfFUN = pExport->NumberOfFunctions;
//函数名数量
DWORD dwNumOfNames = pExport->NumberOfNames;
//基
DWORD dwBase = pExport->Base;
//导出地址表
PDWORD pEat32 = (PDWORD)(RvaToOffset(pExport->AddressOfFunctions, buffer) + buffer);
//导出名称表
PDWORD pEnt32 = (PDWORD)(RvaToOffset(pExport->AddressOfNames, buffer) + buffer);
//导出序号表
PWORD pId = (PWORD)(RvaToOffset(pExport->AddressOfNameOrdinals, buffer) + buffer);
for (int i = 0; i < dwNumOfFUN; i++)
{
if (pEat32[i] == 0)
continue;
DWORD Id = 0;
for (; Id < dwNumOfNames; Id++)
{
if (pId[Id] == i)
break;
}
if (Id == dwNumOfNames)
{
printf("Name:%X Address:0x%08X Name[NuLL]\n", i + dwBase, pEat32[i]);
printf("\n");
}
else
{
char* szFunName = (char*)(RvaToOffset(pEnt32[Id], buffer) + buffer);
printf("Name:%X Address:0x%08X Name[%s]\n", i + dwBase, pEat32[i],szFunName);
printf("\n");
}
printf("-------------------------------------\n");
}
printf("----------------------------------------------------------\n");
}
void TlsTable(char* buffer)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer);
PIMAGE_DATA_DIRECTORY pTLSDir = (pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_TLS);//定位数据目录表中的TLS表
PIMAGE_TLS_DIRECTORY pTLS = (PIMAGE_TLS_DIRECTORY)(RvaToOffset(pTLSDir->VirtualAddress, buffer) + buffer);//填充TLS结构
printf("StartAddressOfRawData: %08X\n", pTLS->StartAddressOfRawData);
printf("EndAddressOfRawData: %08X\n", pTLS->EndAddressOfRawData);
printf("TLS_Callback: %08X\n", pTLS->AddressOfCallBacks);//tls回调函数
printf("TLS_Size: %08X\n", pTLS->SizeOfZeroFill);
printf("TLS_Characteristics: %08X\n", pTLS->Characteristics);
}

小结

写这玩意儿还是花费了很长时间的,当初学PE的时候对导入表和导出表没有仔细学习,写的时候还去补遗了,但其实对这些知识点熟悉,写起来还是很容易的,只要能正确访问目标就行了

写完能对PE文件的结构有更深刻的了解和体会。

(边看代码边阅读助于理解,:) ,还有就是尽量解析32文件,64位可能会有显示不出节区名之类的错误)