记录一下C语言写的简单的PE文件分析器。
边学边做的,有些地方有疏漏了还请指点。
我按照《逆向工程核心原理》这本书给出的大体结构对各部分内容进行输出,下面记录一下我遇到的难题和一些值得注意的点。
一、
分析一个pe文件要做的第一件事就是把这个文件加载到内存中,也就是文件映射。
这里我用的代码是:
-
char FilePath[] = “E:\\test\\notepad.exe”;
-
HANDLE hFile = CreateFileA(FilePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
-
HANDLE hMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
-
PVOID pbFile = MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
把FilePath[]内容改成需要分析的pe文件的路径即可。
然后我们准备分析一个文件的DOS头、NT头、输入表输出表,其他内容暂且不分析。
一个DOS头的内容如下:
作为一个结构体,每个PE文件的DOS头长度不定,但是e_lfanew的值应该都是3cH。
二、
如何找到并输出DOS头。
在网络上搜集各种资料观察各种大牛的代码后我发现,当我把文件映射之后,可以直接定义DOS头的入口:
这也让我对文件的本质和C语言编程多了一些理解,事实上这也是我第一次接触这种很像windows编程的小程序。
直接用系统已经给出的结构体定义去声明一个DOS头,然后我们就可以以指针的形式去访问内部成员。
这里我又遇见了第三个问题,实际上这个问题应该是在映射文件前就该遇到了:
三、
刚开始的时候我并不明白这些DWORD、HANDLE都是些什么东西,查过资料后明白了,不过是和int、char这些东西一样,只是微软把他们typedef成了其他名称,本质还是一些基础的数据定义。
搞清楚这个之后我们对于DOS头甚至是之后的整体文件就能有个大概的认识了。这些支离破碎的二进制被我们观测为十六进制,再加以联系组成一块一块的结构,最后一起拼接成了一个PE文件。
访问DOS头的成员:
-
printf("==================================PE DOS HEADER===================================");
-
printf("\ne_magic: 0x%04X", pDosHeader->e_magic);
-
printf("\ne_cblp: 0x%04X", pDosHeader->e_cblp);
-
printf("\ne_cp: 0x%04X", pDosHeader->e_cp);
-
printf("\ne_crlc: 0x%04X", pDosHeader->e_crlc);
-
printf("\ne_cparhdr: 0x%04X", pDosHeader->e_cparhdr);
-
printf("\ne_minalloc: 0x%04X", pDosHeader->e_minalloc);
-
printf("\ne_maxalloc: 0x%04X", pDosHeader->e_maxalloc);
-
printf("\ne_ss: 0x%04X", pDosHeader->e_ss);
-
printf("\ne_sp: 0x%04X", pDosHeader->e_sp);
-
printf("\ne_csum: 0x%04X", pDosHeader->e_csum);
-
printf("\ne_ip: 0x%04X", pDosHeader->e_ip);
-
printf("\ne_cs: 0x%04X", pDosHeader->e_cs);
-
printf("\ne_lfarlc: 0x%04X", pDosHeader->e_lfarlc);
-
printf("\ne_ovno: 0x%04X\n", pDosHeader->e_ovno);
-
for (int i = 0; i <= 3; i++) {
-
printf(“e_res[%d]: 0x%04X “,i, pDosHeader->e_res[i]);
-
}
-
printf("\ne_oemid: 0x%04X”, pDosHeader->e_oemid);
-
printf("\ne_oeminfo: 0x%04X\n”, pDosHeader->e_oeminfo);
-
for (int i = 0; i <= 9; i++) {
-
if (!(i % 4) && i) printf("\n");
-
printf(“e_res[%d]: 0x%04X “, i, pDosHeader->e_res2[i]);
-
}
-
printf("\ne_lfanew: 0x%08X\n”, pDosHeader->e_lfanew);
-
- printf("\n==================================PE NT HEADER===================================”);//大小为F8
-
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS32)((DWORD)pbFile + pDosHeader->e_lfanew);
-
printf("\nSignature: 0x%08X", pNtHeader->Signature);
大部分以十六进制的方法输出,小部分需要看一下表示的内容于是便输出为%s或者是%d。
注意,在文件映射的开始我们定义了一个PVOID的pbFile,这个代表文件的入口,之后我们想要定位NT头或者其他什么内容时需要用到。
e_lfanew的值就是指从文件开头到NT头的距离,中间可能夹着DOS存根,但是无伤大雅,存根是只有在DOS环境下启动程序才会运行的内容,现在一般用来告诉用户这个软件应该在windows下打开。
把注意力放在NT头上:
通过强制类型转化可以找到NT头的起始位置,下面是NT头的内容:
NT头看着没什么东西,里面其实包含了两个结构体,一个文件头一个可选头。
NT头的第一个signature是签名,作为PE文件其值为0x50450000,也就是"PE"00
-
printf("\n==================================PE NT HEADER===================================");//大小为F8
-
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS32)((DWORD)pbFile + pDosHeader->e_lfanew);
-
printf("\nSignature: 0x%08X", pNtHeader->Signature);
文件头的结构如下:
-
//NT头的file header
-
printf("\n==================================PE FILE HEADER===================================\n");
-
printf(“Machine: 0x%04X\n”, pNtHeader->FileHeader.Machine);
-
printf(“NumberOfSections: 0x%04X\n”, pNtHeader->FileHeader.NumberOfSections); //文件中存在的节区数量
-
printf(“TimeDateStamp: 0x%08X\n”, pNtHeader->FileHeader.TimeDateStamp);
-
printf(“PointerToSymbolTable: 0x%08X\n”, pNtHeader->FileHeader.PointerToSymbolTable);
-
printf(“NumberOfSymbols: 0x%08X\n”, pNtHeader->FileHeader.NumberOfSymbols);
-
printf(“SizeOfOptionalHeader: 0x%04X\n”, pNtHeader->FileHeader.SizeOfOptionalHeader); //指出optional header 的大小
-
printf(“Characteristics: 0x%04X\n”, pNtHeader->FileHeader.Characteristics); //标识文件的属性
可选头结构如下:
-
//NT头的optional header
-
printf("\n===================================PE OPTIONAL HEADER====================================\n");
-
- printf(“Machine:%04X\n”, pNtHeader->OptionalHeader.Magic);
-
printf(“MajorLinkerVersion:%02X\n”, pNtHeader->OptionalHeader.MajorLinkerVersion);
-
printf(“MinorLinkerVersion:%02X\n”, pNtHeader->OptionalHeader.MinorLinkerVersion);
-
printf(“SizeOfCode:%08X\n”, pNtHeader->OptionalHeader.SizeOfCode);
-
printf(“SizeOfInitializedData:%08X\n”, pNtHeader->OptionalHeader.SizeOfInitializedData);
-
printf(“SizeOfUninitializedData:%08X\n”, pNtHeader->OptionalHeader.SizeOfUninitializedData);
-
printf(“AddressOfEntryPoint:%08X\n”, pNtHeader->OptionalHeader.AddressOfEntryPoint); //代码起始位置
-
printf(“BaseOfCode:%08X\n”, pNtHeader->OptionalHeader.BaseOfCode);
-
printf(“BaseOfData:%08X\n”, pNtHeader->OptionalHeader.BaseOfData);
-
printf(“ImageBase:%08X\n”, pNtHeader->OptionalHeader.ImageBase);
-
printf(“SectionAlignment:%08X\n”, pNtHeader->OptionalHeader.SectionAlignment);
-
printf(“FileAlignment:%08X\n”, pNtHeader->OptionalHeader.FileAlignment);
-
printf(“MajorOperatingSystemVersion:%04X\n”, pNtHeader->OptionalHeader.MajorOperatingSystemVersion);
-
printf(“MinorOperatingSystemVersion:%04X\n”, pNtHeader->OptionalHeader.MinorOperatingSystemVersion);
-
printf(“MajorImageVersion:%04X\n”, pNtHeader->OptionalHeader.MajorImageVersion);
-
printf(“MinorImageVersion:%04X\n”, pNtHeader->OptionalHeader.MinorImageVersion);
-
printf(“MajorSubsystemVersion:%04X\n”, pNtHeader->OptionalHeader.MajorSubsystemVersion);
-
printf(“MinorSubsystemVersion:%04X\n”, pNtHeader->OptionalHeader.MinorSubsystemVersion);
-
printf(“Win32VersionValue:%08X\n”, pNtHeader->OptionalHeader.Win32VersionValue);
-
printf(“SizeOfImage:%08X\n”, pNtHeader->OptionalHeader.SizeOfImage);
-
printf(“SizeOfHeaders:%08X\n”, pNtHeader->OptionalHeader.SizeOfHeaders); //整个PE头大小
-
printf(“CheckSum:%08X\n”, pNtHeader->OptionalHeader.CheckSum);
-
printf(“Subsystem:%04X\n”, pNtHeader->OptionalHeader.Subsystem);
-
printf(“DllCharacteristics:%04X\n”, pNtHeader->OptionalHeader.DllCharacteristics);
-
printf(“SizeOfStackReserve:%08X\n”, pNtHeader->OptionalHeader.SizeOfStackReserve);
-
printf(“SizeOfStackCommit:%08X\n”, pNtHeader->OptionalHeader.SizeOfStackCommit);
-
printf(“SizeOfHeapReserve:%08X\n”, pNtHeader->OptionalHeader.SizeOfHeapReserve);
-
printf(“SizeOfHeapCommit:%08X\n”, pNtHeader->OptionalHeader.SizeOfHeapCommit);
-
printf(“LoaderFlags:%08X\n”, pNtHeader->OptionalHeader.LoaderFlags);
-
printf(“NumberOfRvaAndSizes:%08X\n”, pNtHeader->OptionalHeader.NumberOfRvaAndSizes); //用来指定最后数组的大小
-
char DataDirectoryName[][50] = { “EXPORT Directory”,“IMPORT Directory”,“RESOURCE Directory”,“EXCEPTION Directory”,“SECURITY Directory”,“BASERELOC Directory”,
-
“DEBUG Directory”,“COPYRIGHT Directory”,“GLOBALPTR Directory”,“TLS Directory”,“LOAD_CONFIG Directory”,“BOUND_IMPORT Directory”,“IAT Directory”,“DELAY_IMPORT Directory”,
-
“COM_DESCRIPTOR Directory”,“Reserved Directory” };
-
for (int i = 0; i < pNtHeader->OptionalHeader.NumberOfRvaAndSizes; i++) {
-
printf("%s : 0x%08X 0x%08X\n", DataDirectoryName[i], pNtHeader->OptionalHeader.DataDirectory[i].VirtualAddress,pNtHeader->OptionalHeader.DataDirectory[i].Size);
-
}
节区表:
-
printf("\n===================================PE SECTION HEADER====================================\n\n");
-
//PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)IMAGE_FIRST_SECTION(pNtHeader);
-
PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pNtHeader + sizeof(IMAGE_NT_HEADERS));
-
for (int i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++) {
-
printf("————–stction %d————–\n\n",i+1);
-
for (int j = 0; j < IMAGE_SIZEOF_SHORT_NAME; j++) {
-
printf("%c", pSectionHeader->Name[j]);
-
}//name的名称可能和实际作用没什么联系
-
printf(" 0x");
-
for (int j = 0; j < IMAGE_SIZEOF_SHORT_NAME; j++) {
-
printf("%X", pSectionHeader->Name[j]);
-
}
-
printf("\nVirtualSize : 0x%04X", pSectionHeader->Misc.VirtualSize); //内存中节区所占大小
-
printf("\nVirtualAddress : 0x%08X", pSectionHeader->VirtualAddress); //内存中节区起始地址
-
printf("\nSizeOfRawData : 0x%08X", pSectionHeader->SizeOfRawData); //磁盘文件节区所占大小
-
printf("\nPointerToRawData : 0x%08X", pSectionHeader->PointerToRawData); //磁盘文件中节区起始位置
-
printf("\nPointerToRelocations : 0x%08X", pSectionHeader->PointerToRelocations);
-
printf("\nPointerToLinenumbers : 0x%08X", pSectionHeader->PointerToLinenumbers);
-
printf("\nNumberOfRelocations : 0x%04X", pSectionHeader->NumberOfRelocations);
-
printf("\nNumberOfLinenumbers : 0x%04X", pSectionHeader->NumberOfLinenumbers);
-
printf("\nCharacteristics : 0x%08X", pSectionHeader->Characteristics);
-
pSectionHeader++;
-
printf("\n\n");
-
}
输入表:
-
printf("\n===================================PE IMPORT====================================\n");
-
DWORD pImportOffset = RVA_to_RAW(pNtHeader,pNtHeader->OptionalHeader.DataDirectory[1].VirtualAddress);
-
PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pbFile + pImportOffset);
-
while (1) {
-
if (pImport->FirstThunk == 0 && pImport->ForwarderChain == 0 && pImport->Name == 0 && pImport->OriginalFirstThunk == 0 && pImport->TimeDateStamp == 0) {
-
break ;
-
}
-
DWORD dwINT = (DWORD)pbFile + RVA_to_RAW(pNtHeader, pImport->OriginalFirstThunk);
-
DWORD dwTimeDateStamp = (DWORD)pbFile + RVA_to_RAW(pNtHeader, pImport->TimeDateStamp);
-
DWORD dwForwarderChain = (DWORD)pbFile + RVA_to_RAW(pNtHeader, pImport->ForwarderChain);
-
DWORD dwName = (DWORD)pbFile + RVA_to_RAW(pNtHeader, pImport->Name);
-
DWORD dwFirstThunk = (DWORD)pbFile + RVA_to_RAW(pNtHeader, pImport->FirstThunk);
-
printf("——————- %s ——————-\n",dwName);
-
printf(“TimeDateStamp: 0x%08X\n”, pImport->TimeDateStamp);
-
printf(“ForwarderChain: 0x%08X\n”, pImport->ForwarderChain);
-
printf(“pImport->FirstThunk: 0x%X\n”, pImport->FirstThunk);
-
DWORD * ImportByName = (DWORD *)dwINT;
-
DWORD * pFirstThunk = (DWORD *)dwFirstThunk;
-
int i = 0;
-
printf("\nAddress\t\tHint\tName\n");
-
while ((ImportByName[i])) {
-
PIMAGE_IMPORT_BY_NAME pImpoetByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)pbFile + RVA_to_RAW(pNtHeader, ImportByName[i]));
-
printf(“0x%04X\t”, pFirstThunk[i]);
-
printf(“0x%04X\t”,pImpoetByName->Hint);
-
printf("%s\n", pImpoetByName->Name);
-
i++;
-
}
-
dwINT++;
-
pImport++;
-
}
输出表:
-
printf("\n===================================PE EXPORT====================================\n");
-
PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pbFile + RVA_to_RAW(pNtHeader, pNtHeader->OptionalHeader.DataDirectory[0].VirtualAddress));
-
printf(“Characteristics : 0x%X\n”, pExport->Characteristics);
-
printf(“TimeDateStamp : 0x%X\n”, pExport->TimeDateStamp);
-
printf(“MajorVersion : 0x%X\n”, pExport->MajorVersion);
-
printf(“MinorVersion : 0x%X\n”, pExport->MinorVersion);
-
printf(“Base : 0x%X\n”, pExport->Base);
-
printf(“NumberOfNames: %d\n”, pExport->NumberOfNames);
-
printf(“NumberOfFunctions: %d\n”, pExport->NumberOfFunctions);
-
DWORD * AddressOfFunctions = (DWORD *)((DWORD)pbFile + RVA_to_RAW(pNtHeader, pExport->AddressOfFunctions));
-
DWORD * AddressOfNameOrdinals = (DWORD *)((DWORD)pbFile + RVA_to_RAW(pNtHeader, pExport->AddressOfNameOrdinals));
-
DWORD * AddressOfNames = (DWORD *)((DWORD)pbFile + RVA_to_RAW(pNtHeader, pExport->AddressOfNames));
-
DWORD * Name = (DWORD *)((DWORD)pbFile + RVA_to_RAW(pNtHeader, pExport->Name));
-
WORD * pwOrdinals = (WORD *)((DWORD)pbFile + RVA_to_RAW(pNtHeader, pExport->AddressOfNameOrdinals));
-
- if (pExport->NumberOfFunctions == 0) {
-
printf("\n\t———- No Export Tabel! ———-\n");
-
if (NULL != pbFile)
-
{
-
UnmapViewOfFile(pbFile);
-
}
-
- if (NULL != hMapping)
-
{
-
CloseHandle(hMapping);
-
}
-
- if (INVALID_HANDLE_VALUE != hFile)
-
{
-
CloseHandle(hFile);
-
}
-
- return 0;
-
}
-
- for (int i = 0; i < pExport->NumberOfNames; i++) {
-
DWORD dwName = (DWORD)pbFile + RVA_to_RAW(pNtHeader, AddressOfNames[i]);
-
DWORD VA = pNtHeader->OptionalHeader.ImageBase + AddressOfFunctions[i];
-
printf(“Ordinals: %d\tName: %-30s\tRVA: 0x%08X\tVA: 0x%08X\n”, pwOrdinals[i], dwName, AddressOfFunctions[i], VA);
-
}
在输入输出表这里遇到了新的问题:在知晓如何把RVA转化成RAW之后,要再次以新的RAW和起始地址找新的结构,这个时候利用了指针指向把数据输出,我一直输出的是指针存放的地址,所以非常难受。
整个代码已经上传至github和gitee:
Github:https://github.com/DorinXL/Easy_PEInfo