PE结构笔记
提示:前面加*为必须背下来的 DOS头: typedef struct _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; //* NT头指针 } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; NT头: typedef struct _IMAGE_NT_HEADERS { DWORD Signature; //*pe签名 IMAGE_FILE_HEADER FileHeader; //*PE标准 IMAGE_OPTIONAL_HEADER OptionalHeader; //*PE可选头 } IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS; NT::标准PE头: typedef struct _IMAGE_FILE_HEADER { WORD Machine; //*cpu识别 WORD NumberOfSections; //*文件的节数目 (节表与节的数目一样) DWORD TimeDateStamp; //*文件创建日期和时间 DWORD PointerToSymbolTable; //用于调试 DWORD NumberOfSymbols; //用于调试 WORD SizeOfOptionalHeader; //*PE可选头的大小,32位PE文件默认为E0h,64位PE文件默认大小为F0h 大小可以自定 WORD Characteristics; //*关于文件信息的标记,比如文件是exe还是dll } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; NT::可选PE头(32位下大小为E0 64位的为F0) typedef struct _IMAGE_OPTIONAL_HEADER { WORD Magic; //*说明文件类型:10B 32位下的PE文件 20B 64位下的PE文件 BYTE MajorLinkerVersion; //链接程序的主版本号 BYTE MinorLinkerVersion; //链接程序的次版本号 DWORD SizeOfCode; //*所有含代码的节的总大小,必须是FileAlignment的整数倍 编译器填的 没用 DWORD SizeOfInitializedData; //*已初始化数据的大小 必须是FileAlignment的整数倍 编译器填的 没用 DWORD SizeOfUninitializedData; //*未初始化数据的大小 必须是FileAlignment的整数倍 编译器填的 没用 DWORD AddressOfEntryPoint; //*****程序执行入口RVA DWORD BaseOfCode; //*代码开始的基址, 编译器填的 没用 DWORD BaseOfData; //*数据开始的基址, 编译器填的 没用 DWORD ImageBase; //*****内存镜像基址 DWORD SectionAlignment; //*内存中的区块的对齐大小 DWORD FileAlignment; //*文件中的区块的对齐大小 WORD MajorOperatingSystemVersion; //要求操作系统最低版本号的主版本号 WORD MinorOperatingSystemVersion; //要求操作系统最低版本号的副版本号 WORD MajorImageVersion; //可运行于操作系统的主版本号 WORD MinorImageVersion; //可运行于操作系统的次版本号 WORD MajorSubsystemVersion; //要求最低子系统版本的主版本号, WORD MinorSubsystemVersion; //要求最低子系统版本的次版本号 DWORD Win32VersionValue; //莫须有字段,不被病毒利用的话一般为0 DWORD SizeOfImage; //*内存中整个PE文件的映射尺寸,可以比实际值大,但必须是SectionAlignment的整数倍 DWORD SizeOfHeaders; //*所有头+节表按照文件对齐后的大小,否则加载会出错 DWORD CheckSum; //*校验和,一些系统文件有要求,用来判断文件是否被修改 WORD Subsystem; //可执行文件期望的子系统 WORD DllCharacteristics; //DllMain()函数何时被调用,默认为 0 DWORD SizeOfStackReserve; //*初始化时的栈大小 DWORD SizeOfStackCommit; //*初始化时实际提交的栈大小 DWORD SizeOfHeapReserve; //*初始化时保留的堆大小 DWORD SizeOfHeapCommit; //*初始化时实际提交的堆大小 DWORD LoaderFlags; //与调试有关,默认为 0 DWORD NumberOfRvaAndSizes; //下边数据目录的项数,这个字段自Windows NT 发布以来 一直是16 IMAGE_DATA_DIRECTORY DataDirectory [IMAGE_NUMBEROF_DIRECTORY_ENTRIES];// 数据目录表 } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32; 节表: IMAGE_SIZEOF_SHORT_NAME组成,每个结构体代表一个节区 #define IMAGE_SIZEOF_SHORT_NAME typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //8个字节 一般情况下是以“\0”结尾的ASCII码字符串来标识的名称 内容可以自定义 可能由于字母太多编译器不添加"\0"需要注意 union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; //在文件对齐前真实的大小,该值可以不准确 DWORD VirtualAddress; //*节在内存中的偏移地址。加上ImageBase才是内存中真实地址 DWORD SizeOfRawData; //*节在文件中对齐之后的大小 DWORD PointerToRawData; //*节在文件中的偏移 文件对齐的整数倍 DWORD PointerToRelocations; // DWORD PointerToLinenumbers; // WORD NumberOfRelocations; // WORD NumberOfLinenumbers; // DWORD Characteristics; //*节的属性 } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; 注意: VirtualAddress 和 PointerToRawData 不带有任何值,分别由_IMAGE_OPTIONAL_HEADER中的SectionAlignment和FileAlignment确定 VirtualSize和SizeOfRawData一般具有不同的值,即磁盘节区的大小与加载到内存的大小是不一样的。 导入表(IAT)/ 导出表(): typedef struct _IMAGE_IMPORT_DESCRIPTOR { _ANONYMOUS_UNION union { DWORD Characteristics; DWORD OriginalFirstThunk; // INT的地址(Import Name Table)(RVA) } DUMMYUNIONNAME; // DWORD TimeDateStamp; // DWORD ForwarderChain; // DWORD Name; //库名称字符串的地址 (RVA) DWORD FirstThunk; // IAT的地址(Import Address Table)(RVA) } IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;
typedef struct _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;
注意: INT与IAT是长整型(4个字节数据类型)数组,以NULL结束 INT中各元素的值为IMAGE_IMPORT_BY_NAME结构体指针(有时IAT也拥有相同的值) INT与IAT的大小应相同 INT输入顺序: 1.读取IID的Name成员,获取库名称字符串("kernel32.dll") 2.装载相应库——————> LoadLibrary("kernel32.dll") 3.读取IID的OriginalFirstThunk成员,获取INT地址 4.逐一读取INT中数组的值,获取相应IMAGE_IMPORT_BY_NAME地址(RVA) 5.使用IMAGE_IMPORT_BY_NAME的Hint(ordinal) 或Name项,获取相应函数的起始地址。 GetProcAddress("GetCurrentThreadId") 6.读取IID的FirstThunk(IAT)成员,获得IAT地址 7.将上面获得的函数地址输入相应IAT数组值。 8.重复以上补助4~7,直到INT结束(遇到NULL时) RVA与RAW转换: RAW = RAV - VirtualAddress + PointerToRawData 物理地址 = 内存相对地址 - 内存节区的起始地址 + 物理节区的起始位置