PEviewer

​ 为了深入理解PE文件的结构,又参考多方资料,代码,思路,图片,讲解等等,用C语言写了一个简陋,不完善的PEviewer,代码如下,vscode运行不会报错

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

IMAGE_DOS_HEADER dosheader;//Dos头
IMAGE_FILE_HEADER fileheader;//文件头
IMAGE_SECTION_HEADER SectionHeader;//节区头
IMAGE_OPTIONAL_HEADER32 optionalheader;//NT头里的可选头
IMAGE_IMPORT_DESCRIPTOR iid;//IID
DWORD PointerToPeHeader = 0;
DWORD Size_Of_File = 0;
DWORD Pe_Signature = 0;
WORD Size_Of_Opt_Header = 0;
WORD subsys = 0;
DWORD Addr_of_EP = 0;
DWORD PtrIdata = 0;
DWORD ImageBase = 0;
DWORD ImportsVA = 0;
DWORD ImportsSize = 0;
DWORD ImportDirAddr = 0;
int NrOfSections = 0;

struct SectionInfo
{
char name[10];
int VA;
int VirtualSize;
int SizeOfRAW;
int Ptr2RAW;//是一个文件偏移地址(FOA),该节区在硬盘文件中的地址
};

void print_section(struct SectionInfo *section_info)
{
printf("%s节区:\n", section_info->name);
printf("\t装如内存虚拟空间后的地址(RVA) : 0x%x\n", section_info->VA);
printf("\t该节区装入内存后的总大小: %d byte\n", section_info->VirtualSize);
printf("\t该节区在硬盘文件中的地址(FOA) : 0x%x\n", section_info->Ptr2RAW);
printf("\t该节区在硬盘上初始化数据的大小 : %d byte\n", section_info->SizeOfRAW);
}

int main()
{
char name[] = "ty.exe";
printf("请将测试文件与本程序放在同一目录下\n");
printf("请输入文件名:");
scanf("%s",name);

printf("测试文件%s\n",name);
printf("-----------文件信息-----------\n");

//打开文件
FILE* fp;

fp=fopen(name , "rb");
if(fp == NULL)
{
printf("文件打开失败\n");
return 0;
}
//读取DOS头"MZ"
fseek(fp,0,SEEK_SET);
fread(&dosheader , 1 , sizeof(dosheader) , fp);//从fp流里面读出"MZ"
WORD MZ=dosheader.e_magic;
if(MZ != 0x5a4d)
{
printf("这不是PE文件,或者你把DOS头删了\nfuck you\n");
fclose(fp);
return 0;
}
printf("PE文件的Dos头是:%x\n",MZ);
printf("DOS结构体里面有两个重要成员:e_magic(MZ) e_lfanew\n");
WORD NToffset=dosheader.e_lfanew;
printf("NT头的偏移 = 0x%x\n",NToffset);

//寻找NT头
fseek(fp,NToffset,SEEK_SET);
fread(&Pe_Signature,1,sizeof(Pe_Signature),fp);
WORD signature=Pe_Signature;
printf("PE签名是:%x\n",signature);
if(signature != 0x4550)
{
printf("你PE头呢?\nFuck you");
fclose(fp);
return 0;
}
//文件头结构体
fread(&fileheader,1,sizeof(fileheader),fp);
WORD m_code=fileheader.Machine;
if (m_code==0x014c)
{
printf("该CPU是intel-386(X32)\n");
}
else if(m_code==0x8664)
{
printf("该CPU是(X64)\n");
}
else
{
printf("谁知道你这是么cpu,自己百度去\n");
}
//节区数
if (fileheader.NumberOfSections==0)
{
printf("不可能有0个节区\n");
fclose(fp);
return 0;
}
WORD section_num=fileheader.NumberOfSections;
printf("文件一共有%x个节区\n",fileheader.NumberOfSections);
//可选头大小
WORD optional_header_size=fileheader.SizeOfOptionalHeader;
int xbit=0;
if (optional_header_size==0xE0)
{
xbit=32;
// pritnf("这是一个32位程序\n");
}
else
{
xbit=64;
// printf("这是一个64位程序\n");
}
printf("可选头结构体的大小是:0x%x\n这是一个X%d程序\n",optional_header_size,xbit);
//找到可选头
fread(&optionalheader,1,optional_header_size,fp);

subsys=optionalheader.Subsystem;
if (subsys==2)
{
printf("这是一个Windows GUI 子系统\n");
}
else if(subsys==3)
{
printf("这是一个Windows 控制台子系统\n");
}
else
{
printf("俺也不知道了,恁自己查吧\n");
}

DWORD iid_addr=optionalheader.DataDirectory[1].VirtualAddress;
DWORD iid_size=optionalheader.DataDirectory[1].Size;

DWORD ope_addr=optionalheader.AddressOfEntryPoint;
printf("程序最先执行的代码的地址(RVA)是:0x%x",ope_addr);
printf("如果想要在一个可执行文件中附加了一段代码并且要让这段代码首先被执行,就可以通过更改入口地址到目标代码上,然后再跳转回原有的入口地址。\n");
printf("就像upx压缩那样\n");
printf("代码节的起始地址:0x%x, ",optionalheader.BaseOfCode);
printf("数据节的起始地址:0x%x\n",optionalheader.BaseOfData);

//打印出所有的节区
struct SectionInfo section_info[section_num];
for(int i=0;i<section_num;i++)
{
fread(&SectionHeader,1,sizeof(SectionHeader),fp);
memcpy(section_info[i].name,SectionHeader.Name,10);
section_info[i].VA=SectionHeader.VirtualAddress;
section_info[i].VirtualSize=SectionHeader.Misc.VirtualSize;
//这个成员指定了该节区装入内存后的总大小,以字节为单位
section_info[i].Ptr2RAW=SectionHeader.PointerToRawData;
print_section(&section_info[i]);
}


//RVA--->RAW
int RVA2RAW(int rva)
{
int i,raw;
for(i=0;i<section_num;i++)
{
if ((rva>=section_info[i].VA) && (rva<=(section_info[i].VA + section_info[i].VirtualSize)))
{
//RAW = RVA - VA + PointerToRawData
raw = rva - section_info[i].VA + section_info[i].VirtualSize;
return raw;
}
}
return -1;
}


//尝试打印出dll的名称
printf("IID结构体的大小:%d\n",iid_size);
int IID_addr=RVA2RAW(iid_addr);
fseek(fp,IID_addr,SEEK_SET);
fread(&iid,1,20,fp); //read the first Import
int count = 1;

while(iid.Name!=0)
{
fread(&iid,1,20,fp);
count++;
}

IMAGE_IMPORT_DESCRIPTOR Imported[count-1];
fseek(fp, iid_addr, SEEK_SET);

// char *dll_name;
for(int i=0;i<(count-1);i++)
{
fread(&Imported[i],1,20,fp);
printf("[%d] DLL文件的名称在地址 0x%lx 处。\n", i, Imported[i].Name);

//尝试获取dll的名称,失败
// if (i==3)
// {
// fseek(fp,Imported[i].Name,SEEK_SET);
// fread(&name,1,10,fp);
// printf("--%s--\n",dll_name);
// }

}

printf("文件用到的DLL:\n");
// //尝试获取dll的名称,失败
// DWORD DLL_NAME;
// for(int i=0;i<(count-1);i++)
// {
// DLL_NAME = RVA2RAW(Imported[i].Name);//找出name的RAW
// // DLL_NAME = Imported[i].Name;
// fseek(fp , DLL_NAME , SEEK_SET);//定位文件
// do
// {
// char c;
// c = fgetc(fp);//按字节获取地址里的内容
// if (c==0)//ascii字符串用0结尾
// {
// printf("\n");
// break;
// }
// printf("%c",c);
// } while (1);

// }

fclose(fp);
return 0;
}

该PEviewer能够准确的识别PE文件的位数,节区数等等,有些也不能准确识别,后期会不定期改进,先发上来,有意见和建议或者指出错误或者告诉我怎么识别dll名称的,欢迎联系我(QQ:MTYyMTA0Mzk4Ng==)