一、【作业描述】
1、编写程序读取一个exe文件,输出所有的PE头信息;
2、使用第三方的PE工具,对比如下信息,看是否一致;
二、【PE头字段解析】
注:海哥发的PE结构 PDF也不是特别全的,可以去<winNT.h>头文件自己查看相关结构,这样能更清晰一些。
PE头字段解析说明:标黄色的为海哥画的重要要背的,个人认为了解即可弄清偏移量是咋回事就好。其中的重点,后期主要操作的资源都在这个存放各种表的结构里面: _IMAGE_DATA_DIRECTORY DataDirectory[16];
struct _IMAGE_FILE_HEADER { | 描述一个可执行文件的基本属性和结构 | |
0x00 WORD Machine; | 目标机器类型 | |
0x02 WORD NumberOfSections; | PE文件中包含的节的数量 | |
0x04 DWORD TimeDateStamp; | 文件的编译时间戳 | |
0x08 DWORD PointerToSymbolTable; | 符号表信息不包含在最终的可执行文件中。如果是.OBJ文件,则指向符号表的RVA(相对虚拟信息);如果是可执行文件则值为0。 | |
0x0c DWORD NumberOfSymbols; | 如果是.obj文件,表示符号表中的符号数量 | |
0x10 WORD SizeOfOptionalHeader; | 可选头结构体的大小(字节) | |
0x12 WORD Characteristics; | 是否只读 | |
}; | ||
struct _IMAGE_OPTIONAL_HEADER { | 提供可执行文件的详细信息:文件布局、内存需求、入口点、数据目录等信息。 | |
0x00 WORD Magic; | 区分32位还是64位文件 | |
0x02 BYTE MajorLinkerVersion; | 链接器主要版本 | |
0x03 BYTE MinorLinkerVersion; | 链接器次要版本 | |
0x04 DWORD SizeOfCode; | 代码节的总大小,无影响 | |
0x08 DWORD SizeOfInitializedData; | 已初始化数据节的总大小,无影响 | |
0x0c DWORD SizeOfUninitializedData; | 未初始化数据节的总大小,无影响 | |
0x10 DWORD AddressOfEntryPoint; | 程序入口点、相对于映像基地址的RVA | |
0x14 DWORD BaseOfCode; | 代码节相对于映像基地址的RVA 无影响 | |
64位没有此项; | 0x18 DWORD BaseOfData; | 数据节相对于映像基地址的RVA 无影响 |
64位——ULONGLONG类型 | 0x1c DWORD ImageBase; | 映像在内存中的首选基地址 |
0x20 DWORD SectionAlignment; | 内存中节的对齐大小 | |
0x24 DWORD FileAlignment; | 文件中节的对齐大小 | |
0x28 WORD MajorOperatingSystemVersion; | 目标操作系统的版本 | |
0x2a WORD MinorOperatingSystemVersion; | 目标操作系统的版本 | |
0x2c WORD MajorImageVersion; | 映像的版本 | |
0x2e WORD MinorImageVersion; | 映像的版本 | |
0x30 WORD MajorSubsystemVersion; | 目标子系统的版本 | |
0x32 WORD MinorSubsystemVersion; | 目标子系统的版本 | |
0x34 DWORD Win32VersionValue; | 通常保留为0 | |
0x38 DWORD SizeOfImage; | 映像在内存中占用的大小,必须是SectionAlignment整数倍。 | |
0x3c DWORD SizeOfHeaders; | 文件头部+节表按照FileAlignment对齐后的总大小 | |
0x40 DWORD CheckSum; | 文件的校验和 ,一些系统文件、驱动程序有要求,用来判断文件是否被修改,或NTdll,系统会检查。可逆 | |
0x44 WORD Subsystem; | 映像的子系统类型(如控制台程序还是GUI程序) | |
0x46 WORD DllCharacteristics; | DLL的特性。如是否支持搞地质映射或是否使用NX兼容性 | |
64位——ULONGLONG类型 | 0x48 DWORD SizeOfStackReserve; | 栈的预留大小 |
64位——ULONGLONG类型 | 0x4c DWORD SizeOfStackCommit; | 栈的提交大小 |
64位——ULONGLONG类型 | 0x50 DWORD SizeOfHeapReserve; | 堆的预留大小 |
64位——ULONGLONG类型 | 0x54 DWORD SizeOfHeapCommit; | 堆的提交大小 |
0x58 DWORD LoaderFlags; | 保留使用,目前总是0 | |
0x5c DWORD NumberOfRvaAndSizes; | 后面的数据目录数组中的条目数量 | |
0x60 _IMAGE_DATA_DIRECTORY DataDirectory[16]; | 用于存储导入表、导出表、资源、异常处理等的RVA和大小 |
三、代码如下:
#include <windows.h> #include <winNT.h> #include <stdio.h> //这里我之前犯了一个错误,windows.h 包含winNT.h 头,所以windows.h必须要在winNT.h头前面,否则会引起冲突。单独使用windows.h头有些无法输出出来 // //#include <stdio.h> //#include <windows.h> //#include <time.h> //#include <winNT.h> //#include <pshpack1.h> //#include <wintrust.h> //读取一个exe文件,输出所有PE头信息。 /* * 1、以二进制模式打开文件 * 2、存储到一个buffer中 * 3、判断前两个字符是否为5A 4D * 4、如果是,则是PE文件,否则输出不是标准可执行文件 * 5、顺着往下走 */ int main(){ FILE* pfile = NULL; //测试64位、32位程序 ;1:64 、2:32 //fopen_s(&pfile, "C:\/Users\/Administrator\/source\/repos\/文件内存读写\/x64\/Debug\\notepad_fileRW.exe", "rb"); fopen_s(&pfile, "C:\/Windows\/syswow64\/odbcad32.exe", "rb"); if (!pfile) { printf("Failed to open file.\n"); return 1; } fseek(pfile,0,SEEK_END); size_t file_size=ftell(pfile); rewind(pfile); char* file_buffer; file_buffer=(char*)calloc(file_size,1); PIMAGE_DOS_HEADER dos_header; int check_fread=fread_s(file_buffer, file_size, 1, file_size,pfile); if (check_fread!= file_size) { printf("file read erro"); fclose(pfile); free(file_buffer); } dos_header = (PIMAGE_DOS_HEADER)file_buffer; fclose(pfile); if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) { printf("Not a valid PE file.\n"); free(file_buffer); return 1; } //在打印16进制数字时,%x 默认输出小写字母,%X默认输出大写字母。 //dos_header printf("e_magic: 【%04X】\n", dos_header->e_magic); printf("e_lfanew: 【%04X】\n", dos_header->e_lfanew); //NT_header PIMAGE_NT_HEADERS nt_header; file_buffer =file_buffer + dos_header->e_lfanew; nt_header = (PIMAGE_NT_HEADERS)file_buffer; printf("Signature:【%04X】\n",nt_header->Signature); //file_header PIMAGE_FILE_HEADER file_header; file_header = (PIMAGE_FILE_HEADER)(file_buffer + sizeof(nt_header->Signature)); printf("Machine:【%04X】\n", file_header->Machine); printf("NumberOfSections:【%04X】\n", file_header->NumberOfSections); printf("TimeDateStamp:【%08X】\n", file_header->TimeDateStamp); printf("PointerToSymbolTable:【%08X】\n", file_header->PointerToSymbolTable); printf("NumberOfSymbols:【%08X】\n", file_header->NumberOfSymbols); printf("SizeOfOptionalHeader:【%04X】\n", file_header->SizeOfOptionalHeader); //还可以继续分解Characteristics printf("Characteristics:【%04X】\n", file_header->Characteristics); //optional_header PIMAGE_OPTIONAL_HEADER optional_header; PIMAGE_OPTIONAL_HEADER64 optional_header_64; //file_header++ ;这里的file_header 是IMAGE_FILE_HEADER型的,占14H个字节,后面的运算也是按照这个类型算的,所以只需向后加1即可。卡了30分钟才解决。 optional_header = (PIMAGE_OPTIONAL_HEADER)(++file_header);// sizeof(IMAGE_FILE_HEADER); printf("Magic:【%02X】\n", optional_header->Magic); int magic=sizeof(optional_header->Magic); printf("SizeOfCode:【%04X】\n", optional_header->SizeOfCode); printf(":SizeOfInitializedData【%04X】\n", optional_header->SizeOfInitializedData); printf("SizeOfUninitializedData:【%04X】\n", optional_header->SizeOfUninitializedData); printf("AddressOfEntryPoint:【%04X】\n", optional_header->AddressOfEntryPoint); printf("BaseOfCode:【%04X】\n", optional_header->BaseOfCode); //64位PE的imageBase是 Ulonglong型的,这里需要处理一下;%I64X是打印输出ulonglong型的16位数据的格式化符 if (optional_header->Magic ==0x20B) { optional_header_64 = (PIMAGE_OPTIONAL_HEADER64)(file_header); printf("=============================64位PE文件=========================================\n"); printf("ImageBase:【%0I64X】\n", optional_header_64->ImageBase); printf("SectionAlignment:【%08X】\n", optional_header_64->SectionAlignment); printf("FileAlignment:【%08X】\n", optional_header_64->FileAlignment); printf("SizeOfImage:【%08X】\n", optional_header_64->SizeOfImage); printf("SizeOfHeaders:【%08X】\n", optional_header_64->SizeOfHeaders); printf("CheckSum:【%08X】\n", optional_header_64->CheckSum); printf("SizeOfStackReserve:【%0I64X】\n", optional_header_64->SizeOfStackReserve); printf("SizeOfStackCommit:【%0I64X】\n", optional_header_64->SizeOfStackCommit); printf("SizeOfHeapReserve:【%0I64X】\n", optional_header_64->SizeOfHeapReserve); printf("SizeOfHeapCommit:【%0I64X】\n", optional_header_64->SizeOfHeapCommit); printf("NumberOfRvaAndSizes:【%08X】\n", optional_header_64->NumberOfRvaAndSizes); printf("=============================64位PE文件=========================================\n"); } else { //32位PE printf("=============================32位PE文件=========================================\n"); printf("BaseOfData:【%04X】\n", optional_header->BaseOfData); printf("ImageBase:【%04X】\n", optional_header->ImageBase); printf("SectionAlignment:【%04X】\n", optional_header->SectionAlignment); printf("FileAlignment:【%04X】\n", optional_header->FileAlignment); printf("SizeOfImage:【%04X】\n", optional_header->SizeOfImage); printf("SizeOfHeaders:【%04X】\n", optional_header->SizeOfHeaders); printf("CheckSum:【%04X】\n", optional_header->CheckSum); printf("SizeOfStackReserve:【%04X】\n", optional_header->SizeOfStackReserve); printf("SizeOfStackCommit:【%04X】\n", optional_header->SizeOfStackCommit); printf("SizeOfHeapReserve:【%04X】\n", optional_header->SizeOfHeapReserve); printf("SizeOfHeapCommit:【%04X】\n", optional_header->SizeOfHeapCommit); printf("NumberOfRvaAndSizes:【%04X】\n", optional_header->NumberOfRvaAndSizes); printf("=============================32位PE文件=========================================\n"); } // 在释放free(file_buffer); 之前,应先将file_buffer = NULL; 否则会报“Expression:is_block_type_valid(header->_block_use)”错误 file_buffer = NULL; free(file_buffer); return 0; }
四、010editor 介绍
本人使用的是010 editor当做第三方PE工具的,因为确实可以这么做到,真的很强大,强烈推荐,不过官网下载是英文版的,其中熟悉软件用了不少时间。效果如下图:
本人在编辑的时候可能会受到自身逻辑限制,无法想到一些重要的情况,欢迎各位一起学习的小伙伴多多批评指正。