过个年回来带大家温习一下——PE结构

admin 2026-03-05 20:28:10 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细介绍了WindowsPE文件结构,涵盖DOS部分、PE文件头、节表、导入导出表及重定位表等核心组件的结构与功能。文章结合WinNT.h头文件定义与十六进制视图,解析了PE指纹判定、入口点计算及内存对齐机制,并提供了C语言编写PE格式解析器的初步实现思路,适合初学者系统掌握逆向工程基础知识。 综合评分: 78 文章分类: 逆向分析,二进制安全,安全开发


cover_image

过个年回来带大家温习一下——PE结构

chika chika

痴卡

2026年3月4日 16:03 中国香港

0x00:前言

PE文件可以说是在逆向的各个领域都有涉及,特别是病毒领域,如果你是一名病毒制造者,那你肯定是对PE文件有详细的了解,那么这里我就详细介绍一下PE文件,最后我们用C来写一个PE格式解析器。

0x01:PE格式

总体介绍

首先说明一个概念,可执行文件(Executable File)是指可以由操作系统直接加载执行的文件,在Windows操作系统中可执行文件就是PE文件结构,在Linux下则是ELF文件,我们这里只讨论Windows下的PE文件,要了解PE文件,首先要知道PE格式,那么什么是PE格式呢,既然是一个格式,那肯定是我们都需要遵循的定理,下面这张图就是PE文件格式的图片(来自看雪),非常大一张图片,其实PE格式就是各种结构体的结合,Windows下PE文件的各种结构体在WinNT.h这个头文件中,可以在VS中查询。

PE文件整体结构

PE结构可以大致分为:

  • DOS部分
  • PE文件头
  • 节表(块表)
  • 节数据(块数据)
  • 调试信息

PE指纹

为了更加直观的描述我们用16进制编辑器直接将一个exe文件载入,分析其结构,首先我们需要清楚的概念是PE指纹,也就是判断一个文件是否是PE文件的依据,首先是根据文件的前两个字节是否为4D 5A,也就是’MZ’,然后看第四排四个字节指向的地址00 00 00 f8是否为50 45,也就是’PE’,满足这两个条件也就满足了PE文件的格式,简称PE指纹,在后面制作解析器的时候会通过它来判断是否为一个有效的PE文件。

DOS部分

DOS部分主要是为了兼容以前的DOS系统,DOS部分可以分为DOS MZ文件头(IMAGE_DOS_HEADER)和DOS块(DOS Stub)组成,PE文件的第一个字节位于一个传统的MS-DOS头部,称作IMAGE_DOS_HEADER,其结构如下:

| | | — | | typedefstruct _IMAGE_DOS_HEADER {// DOS .EXE header     WORD   e_magic;                     // Magic number     WORD   e_cblp;                      // Bytes on last page of file     WORD   e_cp;                        // Pages in file     WORD   e_crlc;                      // Relocations     WORD   e_cparhdr;                   // Size of header in paragraphs     WORD   e_minalloc;                  // Minimum extra paragraphs needed     WORD   e_maxalloc;                  // Maximum extra paragraphs needed     WORD   e_ss;                        // Initial (relative) SS value     WORD   e_sp;                        // Initial SP value     WORD   e_csum;                      // Checksum     WORD   e_ip;                        // Initial IP value     WORD   e_cs;                        // Initial (relative) CS value     WORD   e_lfarlc;                    // File address of relocation table     WORD   e_ovno;                      // Overlay number     WORD   e_res[4];                    // Reserved words     WORD   e_oemid;                     // OEM identifier (for e_oeminfo)     WORD   e_oeminfo;                   // OEM information; e_oemid specific     WORD   e_res2[10];                  // Reserved words     LONG   e_lfanew;                    // File address of new exe header   } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; |

DOS部分我们需要熟悉的是e_magic成员和e_lfanew成员,前者是标识PE指纹的一部分,后者则是寻找PE文件头的部分,除了这两个成员,其他成员全部用0填充都不会影响程序正常运行,所以我们不需要过多的对其他部分深究,DOS部分在16进制编辑器中看就是下图的部分:

我们可以看到e_lfanew指向PE文件头,我们可以通过它来寻找PE文件头,而DOS块的部分自然就是PE文件头和DOS MZ文件头中间的部分,这部分是由链接器所写入的,可以随意进行修改,并不影响程序的运行:

PE文件头

PE文件头由PE文件头标志,标准PE头,扩展PE头三部分组成。PE文件头标志自然是50 40 00 00,也就是’PE’,我们从结构体的角度看一下PE文件头的详细信息

| | | — | | typedefstruct _IMAGE_NT_HEADERS {     DWORD Signature; //PE文件头标志 => 4字节     IMAGE_FILE_HEADER FileHeader; //标准PE头 => 20字节     IMAGE_OPTIONAL_HEADER32 OptionalHeader; //扩展PE头 => 32位下224字节(0xE0) 64位下240字节(0xF0) } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32; |

标准PE头结构如下,有20个字节,我们可以从PE文件头标志后20个字节找到它

| | | — | | typedefstruct _IMAGE_FILE_HEADER {     WORD    Machine; //可以运行在什么平台上 任意:0 ,Intel 386以及后续:14C x64:8664     WORD    NumberOfSections; //节的数量     DWORD   TimeDateStamp; //编译器填写的时间戳     DWORD   PointerToSymbolTable;   //调试相关     DWORD   NumberOfSymbols; //调试相关     WORD    SizeOfOptionalHeader;   //标识扩展PE头大小     WORD    Characteristics;        //文件属性 => 16进制转换为2进制根据哪些位有1,可以查看相关属性 } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; |

扩展PE头在32位和64位系统上大小是不同的,在32位系统上有224个字节,16进制就是0xE0,结构如下,重要的属性我都有标注

| | | — | | typedefstruct _IMAGE_OPTIONAL_HEADER { // // Standard fields. //     WORD    Magic; //PE32: 10B PE64: 20B     BYTE    MajorLinkerVersion;     BYTE    MinorLinkerVersion;     DWORD   SizeOfCode; //所有含有代码的区块的大小 编译器填入 没用(可改)     DWORD   SizeOfInitializedData; //所有初始化数据区块的大小 编译器填入 没用(可改)     DWORD   SizeOfUninitializedData; //所有含未初始化数据区块的大小 编译器填入 没用(可改)     DWORD   AddressOfEntryPoint; //程序入口RVA     DWORD   BaseOfCode; //代码区块起始RVA     DWORD   BaseOfData; //数据区块起始RVA // // NT additional fields. //     DWORD   ImageBase; //内存镜像基址(程序默认载入基地址)     DWORD   SectionAlignment; //内存中对齐大小     DWORD   FileAlignment; //文件中对齐大小(提高程序运行效率)     WORD    MajorOperatingSystemVersion;     WORD    MinorOperatingSystemVersion;     WORD    MajorImageVersion;     WORD    MinorImageVersion;     WORD    MajorSubsystemVersion;     WORD    MinorSubsystemVersion;     DWORD   Win32VersionValue;     DWORD   SizeOfImage; //内存中整个PE文件的映射的尺寸,可比实际值大,必须是SectionAlignment的整数倍     DWORD   SizeOfHeaders; //所有的头加上节表文件对齐之后的值     DWORD   CheckSum; //映像校验和,一些系统.dll文件有要求,判断是否被修改     WORD    Subsystem;     WORD    DllCharacteristics; //文件特性,不是针对DLL文件的,16进制转换2进制可以根据属性对应的表格得到相应的属性     DWORD   SizeOfStackReserve;     DWORD   SizeOfStackCommit;     DWORD   SizeOfHeapReserve;     DWORD   SizeOfHeapCommit;     DWORD   LoaderFlags;     DWORD   NumberOfRvaAndSizes;     IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //数据目录表,结构体数组 } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32; |

程序中的扩展PE头大小在标准PE头中的显示如下图

扩展PE头在程序中显示如下,每一个属性可以通过偏移找到

还需要知道的是,程序的真正入口点 = ImageBase + AddressOfEntryPoint

节表

节表的结构如下,整体为40个字节

| | | — | | typedefstruct _IMAGE_SECTION_HEADER {     BYTE    Name[IMAGE_SIZEOF_SHORT_NAME]; //ASCII字符串 可自定义 只截取8个字节 union {   //该节在没有对齐之前的真实尺寸,该值可以不准确             DWORD   PhysicalAddress;             DWORD   VirtualSize;     } Misc;     DWORD   VirtualAddress;   //内存中的偏移地址     DWORD   SizeOfRawData;   //节在文件中对齐的尺寸     DWORD   PointerToRawData;   //节区在文件中的偏移     DWORD   PointerToRelocations;     DWORD   PointerToLinenumbers;     WORD    NumberOfRelocations;     WORD    NumberOfLinenumbers;     DWORD   Characteristics;   //节的属性 } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; |

程序中显示如下

值得注意的是扩展PE头中的 FileAlignment 以及 SizeOfHeaders 这两个成员,SizeOfHeaders 表示所有的头加上节表文件对齐之后的值,对齐的大小参考的就是 FileAlignment 成员,如果所有的头加上节表的大小为320,FileAlignment 为 200,那么 SizeOfHeaders 大小就为 400,因为是根据FileAlignment 对齐的,这种对齐虽然牺牲了空间,但是可以提高程序运行效率,下图中的前面部分0x00100000就是程序在内存中对齐的大小,也就是程序运行起来时对齐的大小,0x00000400是程序在文件中的对齐大小,也就是没有运行时对齐的大小,需要清楚的是,PE程序在运行时内存中的对齐值和没有运行时的对齐值可能是截然不同的,了解这一点这对我们后面写PE解析器有帮助。

导入表

导出表(Import Table)和导入表是靠 IMAGE_DATA_DIRECTORY 这个结构体数组来寻找的,IMAGE_DATA_DIRECTORY 的结构如下

| | | — | | typedefstruct _IMAGE_DATA_DIRECTORY {     DWORD   VirtualAddress;     DWORD   Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; |

在程序中查找导出表如下图所示,因为结构体数组中每一个结构体大小为 16 位,又是扩展PE头中的最后一个成员,所以我们从节表段向上推 8 行即为我们的结构体数组开头,前 8 位是导出表的内容,因为是一个exe文件,这里刚好就没有导出表只有导入表,可以看到导入表RVA地址是0x00003700的位置

导入表的结构如下

| | | — | | typedefstruct _IMAGE_IMPORT_DESCRIPTOR { union {         DWORD   Characteristics;            // 0 for terminating null import descriptor         DWORD   OriginalFirstThunk;         // RVA 指向 INT (PIMAGE_THUNK_DATA结构数组)     } DUMMYUNIONNAME;     DWORD   TimeDateStamp;                  // 0 if not bound, // -1 if bound, and real date\time stamp //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) // O.W. date/time stamp of DLL bound to (Old BIND)     DWORD   ForwarderChain;                 // -1 if no forwarders     DWORD   Name; //RVA指向dll名字,以0结尾     DWORD   FirstThunk;                     // RVA 指向 IAT (PIMAGE_THUNK_DATA结构数组) } IMAGE_IMPORT_DESCRIPTOR; typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR; |

可以看到,OriginalFirstThunk 和 FirstThunk 指向的内容分别是 INT 和 IAT ,但实际上 INT 和 IAT 的内容是一样的,所以他们指向的内容是一样的,只是方式不同而已,下图可以完美的解释

但是上图只是PE文件加载前的情况,PE文件一旦运行起来,就会变成下图的情况

我们还需要了解的结构体是 IMAGE_THUNK_DATA 和 IMAGE_IMPORT_BY_NAME 结构如下

| | | — | | typedefstruct _IMAGE_IMPORT_BY_NAME {     WORD    Hint; //可能为空,编译器决定,如果不为空,是函数在导出表的索引     BYTE    Name[1]; //函数名称,以0结尾 } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME; #include"pshpack8.h"// Use align 8 for the 64-bit IAT. typedefstruct _IMAGE_THUNK_DATA64 { union {         ULONGLONG ForwarderString;  // 指向一个转向者字符串的RVA         ULONGLONG Function;         // 被输入的函数的内存地址         ULONGLONG Ordinal; // 被输入API的序数值         ULONGLONG AddressOfData;    // 指针指向 IMAGE_IMPORT_BY_NAME     } u1; } IMAGE_THUNK_DATA64; typedef IMAGE_THUNK_DATA64 * PIMAGE_THUNK_DATA64; #include"poppack.h"// Back to 4 byte packing typedefstruct _IMAGE_THUNK_DATA32 { union {         DWORD ForwarderString;      // PBYTE          DWORD Function;             // PDWORD         DWORD Ordinal;         DWORD AddressOfData;        // PIMAGE_IMPORT_BY_NAME     } u1; } IMAGE_THUNK_DATA32; typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32; |

其实他们的作用很明显,就是用来寻找当前的模块依赖哪些函数,可以用这几个结构体求到依赖函数的名字。

导出表

导出表(Export Table)一般是DLL文件用的比较多,exe文件很少有导出表,导出表的数据结构如下

| | | — | | typedefstruct _IMAGE_EXPORT_DIRECTORY {     DWORD   Characteristics;     DWORD   TimeDateStamp;     WORD    MajorVersion;     WORD    MinorVersion;     DWORD   Name; // 指针指向该导出表文件名字符串     DWORD   Base; // 导出函数起始序号     DWORD   NumberOfFunctions; // 所有导出函数的个数     DWORD   NumberOfNames; // 以函数名字导出的函数个数     DWORD   AddressOfFunctions;     // 指针指向导出函数地址表RVA     DWORD   AddressOfNames;         // 指针指向导出函数名称表RVA     DWORD   AddressOfNameOrdinals;  // 指针指向导出函数序号表RVA } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY; |

可以看到导出表里面最后还有三个表,这三个表可以让我们找到函数真正的地址,在编写PE格式解析器的时候可以用到,AddressOfFunctions 是函数地址表,指向每个函数真正的地址,AddressOfNames 和 AddressOfNameOrdinals 分别是函数名称表和函数序号表,我们知道DLL文件有两种调用方式,一种是用名字,一种是用序号,通过这两个表可以用来寻找函数在 AddressOfFunctions 表中真正的地址。

重定位表

当PE文件被装载到虚拟内存的另一个地址中的时候,也就是载入时不将默认的值作为基地址载入,链接器登记的哪个地址是错误的,需要我们用重定位表来调整,重定位表在数据目录项的第 6 个结构,结构如下

| | | — | | typedefstruct _IMAGE_BASE_RELOCATION {     DWORD   VirtualAddress; // 重定位数据的开始 RVA 地址     DWORD   SizeOfBlock; // 重定位块的长度 //  WORD    TypeOffset[1]; // 重定位项数组 } IMAGE_BASE_RELOCATION; typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION; |

重定位表有许多个,以八个字节的 0 结尾

0x02:PE解析器编写

这里放一个由C写的简易的PE分析工具,写的比较简单,主要是为了熟悉PE结构

| | | — | | #include<stdio.h> #include<stdlib.h> #include<windows.h> DWORD&nbsp;RVAOffset(PIMAGE_NT_HEADERS pNtHeader, DWORD Rva) { PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)IMAGE_FIRST_SECTION(pNtHeader); for&nbsp;(int&nbsp;i =&nbsp;0; i < pNtHeader->FileHeader.NumberOfSections; i++) { DWORD SectionBeginRva = pSectionHeader[i].VirtualAddress; DWORD SectionEndRva = pSectionHeader[i].VirtualAddress + pSectionHeader[i].SizeOfRawData; if&nbsp;(Rva >= SectionBeginRva && Rva <= SectionEndRva) { DWORD Temp = Rva - SectionBeginRva; DWORD Rwa = Temp + pSectionHeader[i].PointerToRawData; return&nbsp;Rwa; } } } intmain(int&nbsp;argc,&nbsp;char* argv[]) { HANDLE hFile; HANDLE hMapping; LPVOID ImageBase; char&nbsp;szFilePath[MAX_PATH]; OPENFILENAME ofn;//定义结构,调用打开对话框选择要分析的文件及其保存路径 PIMAGE_DOS_HEADER &nbsp;pDH =&nbsp;NULL;//指向IMAGE_DOS结构的指针 PIMAGE_NT_HEADERS &nbsp;pNtH =&nbsp;NULL;//指向IMAGE_NT结构的指针 PIMAGE_FILE_HEADER pFH =&nbsp;NULL;//指向IMAGE_FILE结构的指针 PIMAGE_OPTIONAL_HEADER pOH =&nbsp;NULL;//指向IMAGE_OPTIONALE结构的指针 memset(szFilePath,&nbsp;0, MAX_PATH); memset(&ofn,&nbsp;0,&nbsp;sizeof(ofn)); ofn.lStructSize =&nbsp;sizeof(ofn); ofn.hwndOwner =&nbsp;NULL; ofn.hInstance = GetModuleHandle(NULL); ofn.nMaxFile = MAX_PATH; ofn.lpstrInitialDir =&nbsp;"."; ofn.lpstrFile = szFilePath; ofn.lpstrTitle =&nbsp;"choose a PE file --by Thunder_J"; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; ofn.lpstrFilter =&nbsp;"*.*\0*.*\0"; if&nbsp;(!GetOpenFileName(&ofn)) { printf("打开文件错误:%d\n", GetLastError()); return0; } hFile = CreateFile(szFilePath, GENERIC_READ, FILE_SHARE_READ,&nbsp;NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,&nbsp;0); if&nbsp;(!hFile) { MessageBox(NULL,&nbsp;"打开文件错误",&nbsp;NULL, MB_OK); return0; } hMapping = CreateFileMapping(hFile,&nbsp;NULL, PAGE_READONLY,&nbsp;0,&nbsp;0,&nbsp;NULL); if&nbsp;(!hMapping) { printf("创建映射错误:%d", GetLastError()); CloseHandle(hFile); return0; } ImageBase = MapViewOfFile(hMapping, FILE_MAP_READ,&nbsp;0,&nbsp;0,&nbsp;0); if&nbsp;(!ImageBase) { printf("文件映射错误:%d", GetLastError()); CloseHandle(hMapping); CloseHandle(hFile); return0; } /************************************************************************/ /* &nbsp;PE头的判断 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; */ /************************************************************************/ if&nbsp;(!ImageBase)&nbsp;//判断映像地址 { printf("Not a valid PE file 1!\n"); CloseHandle(hMapping); CloseHandle(hFile); return0; } printf("--------------------PEheader------------------------\n"); pDH = (PIMAGE_DOS_HEADER)ImageBase; if&nbsp;(pDH->e_magic!=IMAGE_DOS_SIGNATURE)&nbsp;//判断是否为MZ { printf("Not a valid PE file 2!\n"); CloseHandle(hMapping); CloseHandle(hFile); return0; } pNtH = (PIMAGE_NT_HEADERS)((DWORD)pDH + pDH->e_lfanew);&nbsp;//判断是否为PE格式 if&nbsp;(pNtH->Signature!=IMAGE_NT_SIGNATURE) { printf("Not a valid PE file 3!\n"); CloseHandle(hMapping); CloseHandle(hFile); return0; } printf("PE e_lfanew is: 0x%x\n", pNtH); /************************************************************************/ /* &nbsp;FileHeader &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;*/ /************************************************************************/ pFH = &pNtH->FileHeader; printf("-----------------FileHeader------------------------\n"); printf("NumberOfSections: %d\n", pFH->NumberOfSections); printf("SizeOfOptionalHeader: %d\n", pFH->SizeOfOptionalHeader); /************************************************************************/ /* &nbsp;OptionalHeader &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;*/ /************************************************************************/ pOH = &pNtH->OptionalHeader; printf("-----------------OptionalHeader---------------------\n"); printf("SizeOfCode:0x%08x\n", pOH->SizeOfCode); printf("AddressOfEntryPoint: 0x%08X\n", pOH->AddressOfEntryPoint); printf("ImageBase is 0x%x\n", ImageBase); printf("SectionAlignment: 0x%08x\n", pOH->SectionAlignment); printf("FileAlignment: 0x%08x\n", pOH->FileAlignment); printf("SizeOfImage: 0x%08x\n", pOH->SizeOfImage); printf("SizeOfHeaders: 0x%08x\n", pOH->SizeOfHeaders); printf("NumberOfRvaAndSizes: 0x%08x\n", pOH->NumberOfRvaAndSizes); /************************************************************************/ /* &nbsp;SectionTable &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;*/ /************************************************************************/ int&nbsp;SectionNumber =&nbsp;0; DWORD SectionHeaderOffset = (DWORD)pNtH +&nbsp;24&nbsp;+ (DWORD)pFH->SizeOfOptionalHeader;&nbsp;//节表位置的计算 printf("--------------------SectionTable---------------------\n"); for&nbsp;(SectionNumber; SectionNumber < pFH->NumberOfSections;SectionNumber++) { PIMAGE_SECTION_HEADER pSh = (PIMAGE_SECTION_HEADER)(SectionHeaderOffset +&nbsp;40&nbsp;* SectionNumber); printf("%d 's Name is %s\n", SectionNumber +&nbsp;1, pSh->Name); printf("VirtualAddress: 0x%08X\n", (DWORD)pSh->VirtualAddress); printf("SizeOfRawData: 0x%08X\n", (DWORD)pSh->SizeOfRawData); printf("PointerToRawData: 0x%08X\n", (DWORD)pSh->PointerToRawData); } /************************************************************************/ /* &nbsp;ExportTable &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; */ /************************************************************************/ printf("--------------------ExportTable----------------------\n"); DWORD Export_table_offset = RVAOffset(pNtH, (DWORD)pNtH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)ImageBase + Export_table_offset); DWORD EXport_table_offset_Name = (DWORD)ImageBase + RVAOffset(pNtH, pExportDirectory->Name); DWORD * pNameOfAddress = (DWORD *)((DWORD)ImageBase + RVAOffset(pNtH, pExportDirectory->AddressOfNames)); DWORD * pFunctionOfAdress = (DWORD *)((DWORD)ImageBase + RVAOffset(pNtH, pExportDirectory->AddressOfFunctions)); WORD * pNameOrdinalOfAddress = (WORD *)((DWORD)ImageBase + RVAOffset(pNtH, pExportDirectory->AddressOfNameOrdinals)); printf("Name:%s\n", EXport_table_offset_Name); printf("NameOfAddress:%08X\n", RVAOffset(pNtH, pExportDirectory->AddressOfNames)); printf("FunctionOfAdress:%08X\n", RVAOffset(pNtH, pExportDirectory->AddressOfFunctions)); printf("NameOrdinalOfAddress:%08X\n", RVAOffset(pNtH, pExportDirectory->AddressOfNameOrdinals)); if&nbsp;(pExportDirectory->NumberOfFunctions ==&nbsp;0) { puts("!!!!!!!!!!!!!!!!!NO EXPORT!!!!!!!!!!!!!!!!!!!!!"); if&nbsp;(hFile != INVALID_HANDLE_VALUE) { CloseHandle(hFile); } if&nbsp;(hMapping !=&nbsp;NULL) { CloseHandle(hMapping); } if&nbsp;(ImageBase !=&nbsp;NULL) { UnmapViewOfFile(ImageBase); } } printf("NumberOfNames:%d\n", pExportDirectory->NumberOfNames); printf("NumberOfFunctions:%d\n", pExportDirectory->NumberOfFunctions); /************************************************************************/ /* &nbsp;ImportTable &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; */ /************************************************************************/ printf("--------------------ImportTable----------------------\n"); int&nbsp;cont =&nbsp;0; do { DWORD dwImportOffset = RVAOffset(pNtH, pNtH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); dwImportOffset = dwImportOffset + cont; PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)ImageBase + dwImportOffset); if&nbsp;(pImport->OriginalFirstThunk ==&nbsp;0&nbsp;&& pImport->TimeDateStamp ==&nbsp;0&nbsp;&& pImport->ForwarderChain ==&nbsp;0&nbsp;&& pImport->Name ==&nbsp;0&nbsp;&& pImport->FirstThunk ==&nbsp;0) break; DWORD dwOriginalFirstThunk = (DWORD)ImageBase + RVAOffset(pNtH, pImport->OriginalFirstThunk); DWORD dwFirstThunk = (DWORD)ImageBase + RVAOffset(pNtH, pImport->FirstThunk); DWORD dwName = (DWORD)ImageBase + RVAOffset(pNtH, pImport->Name); printf("---------Import File Name: %s\n", dwName); if&nbsp;(dwOriginalFirstThunk ==&nbsp;0x00000000) { dwOriginalFirstThunk = dwFirstThunk; } DWORD* pdwTrunkData = (DWORD*)dwOriginalFirstThunk; int&nbsp;n =&nbsp;0, x =&nbsp;0; while&nbsp;(pdwTrunkData[n] !=&nbsp;0) { DWORD TrunkData = pdwTrunkData[n]; if&nbsp;(TrunkData < IMAGE_ORDINAL_FLAG32)//名字导入 { PIMAGE_IMPORT_BY_NAME pInportByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)ImageBase + RVAOffset(pNtH, TrunkData)); printf("ImportByName: %s\n", pInportByName->Name); } else { DWORD FunNumber = (DWORD)(TrunkData - IMAGE_ORDINAL_FLAG32); printf("ImportByNumber: %-4d \n", FunNumber); } if&nbsp;(x !=&nbsp;0&nbsp;&& x %&nbsp;3&nbsp;==&nbsp;0)&nbsp;printf("\n"); n++; x++; } cont = cont +&nbsp;40; }&nbsp;while&nbsp;(1); { if&nbsp;(ImageBase) { UnmapViewOfFile(ImageBase); } if&nbsp;(hMapping) { CloseHandle(hMapping); } if&nbsp;(hFile != INVALID_HANDLE_VALUE) { CloseHandle(hFile); } return0; } } |

运行效果

节表以及之前信息

导出表

导入表

0x03:总结

这个PE解析器虽然简单,但是自己写了之后对PE的理解和之前截然不同,后续可以对这个解析器进行各种优化,判断是否有壳之类的功能可以添加上去。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:痴卡 chika chika《过个年回来带大家温习一下——PE结构》

评论:0   参与:  0