浅剖PE结构!!!
PE结构是软件的核心,了解和掌握PE结构对逆向工程是至关重要的,此乃知己知彼,百战不殆!!!希望对像我这样的新手朋友有帮助!!!一、PE(Protable Executable:可移植的执行体)文件整体结构:
1.DOS头2.PE头 3.区(节)段表4.节(.text、.data、.rsrc、.recol) 5.调试信息:COFF行号、COFF符号表、codeview调试信息。
PE结构按照我的理解就是PE是一堆二进制数据组成,在UE(uedit.exe)中显示的是一堆十六进制数据,这些数据
实际是由一个个结构体中定义的特殊数据,具有特殊的含义。下面是深入剖析PE结构。
二、剖析PE结构:
(1)DOS头:由_IMAGE_DOS_HEADER 结构定义,DOS头是为了兼容以前的老式计算机所留下来的。
typedef struct _IMAE_DOS_HEADER { //DOS .EXE header 位置
WORD e_magic; //Magic number; !!! 标志性数据 0x00
WORD e_cblp; //Bytes on last page of file 0x02
WORD e_cp; //Pages in file 0x04
WORD e_crlc; //Relocations 0x06
WORD e_cparhdr; //Size of header in paragraphs 0x08
WORD e_minalloc; //Minimum extra paragraphs needed 0x0A
WORD e_maxalloc; //Maximum extra paragraphs needed 0x0C
WORD e_ss; //Initial (relative) SS value 0x0E
WORD e_sp; //Initial SP value 0x10
WORD e_csum; //Checksum 0x12
WORD e_ip; //Initial IP value 0x14
WORD e_cs; //Initial (relative) CS value 0x16
WORD e_lfarlc; //File address of relocation table 0x18
WORD e_ovno; //Overlay number 0x1A
WORD e_res; //Reserved words 0x1C
WORD e_oemid; //OEM identifier (for e_oeminfo) 0x24
WORD e_oeminfo; //OEM information; e_oemid specific 0x26
WORD e_res2; //Reserved words 0x28
LONG e_lfanew; //File address of new exe header 0x3C
} IMAGE_DOS-HEADER, *PIMAGE_DOS_HEADER;
此处的“位置”是其对应的成员相对于DOS头的文件偏移量。
重要成员介绍:
1.e_magic:1.为DOS执行文件的标志性数据,为40 5A 90 00(MZ?),此标志是微软为了兼容以前的老式电脑而留下来的。
2.由Winnt.h头文件中的一个宏定义:#define IMAGE_DOS_SIGNATURE 0x4D5A // MZ,而e_magic由IMAGE_DOS_SIGNATURE定义。
2.e_lfanew:是一个指针,指向PE头。
下图就是成员.e_lfanew指向的PE头:
(2)PE头:由_IMAGE_NT_HEADER结构定义,此处就是常说的PE头的神秘面纱。
typedefstruct _IMAGE_NT_HEADERS { 位置
DWORD Signature; 0x00
IMAGE_FILE_HEADER FileHeader; 0x04
IMAGE_OPTIONAL_HEADER OptionalHeader; 0x18
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;
重要成员介绍:
1. Signature:在一个有效的 PE 文件里,Signature 字段被设置为00004550h, ASCII 码字符是“PE00”。标志这 PE 文件头的开始。“PE00” 字符串是 PE 文件头的开始,DOS 头部的 e_lfanew 字段正是指向这里。
2.FileHeader:为一个子结构体 IMAGE_FILE_HEADER 构成:
typedef struct _IMAGE_FILE_HEADER
{
+04h WORD Machine; // 运行平台
+06h WORD NumberOfSections; // 文件的区块数目
+08h DWORD TimeDateStamp; // 文件创建日期和时间
+0Ch DWORD PointerToSymbolTable; // 指向COFF符号表(主要用于调试)
+10h DWORD NumberOfSymbols; // COFF符号表中符号个数(同上)
+14h WORD SizeOfOptionalHeader; // IMAGE_OPTIONAL_HEADER3 2 结构大小
+16h WORD Characteristics; // 文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
2.1_IMAGE_FILE_HEADER 重要成员介绍:
2.1.1.Machine:可执行文件的目标CPU类型。
2.1.1.NumberOfSection: 区块的数目。(注:区块表是紧跟在 IMAGE_NT_HEADERS 后边的)
2.1.2.TimeDataStamp: 表明文件是何时被创建的。这个值是自1970年1月1日以来用格林威治时间(GMT)计算的秒数,这个值是比文件系统(FILESYSTEM)的日期时间更加精确的指示器。
2.1.3.PointerToSymbolTable: COFF 符号表的文件偏移位置,现在基本没用了。
2.1.4.NumberOfSymbols: 如果有COFF 符号表,它代表其中的符号数目,COFF符号是一个大小固定的结构,如果想找到COFF 符号表的结束位置,则需要这个变量。
2.1.5.SizeOfOptionalHeader: 紧跟着IMAGE_FILE_HEADER 后边的数据结构(IMAGE_OPTIONAL_HEADER)的大小。(对于32位PE文件,这个值通常是00E0h;对于64位PE32+文件,这个值是00F0h )。
2.1.6.Characteristics: 文件属性,有选择的通过几个值可以运算得到。( 这些标志的有效值是定义于 winnt.h 内的 IMAGE_FILE_** 的值,具体含义见下表。普通的EXE文件这个字段的值一般是 0100h,DLL文件这个字段的值一般是 210Eh。),多种属性可以通过 “或运算” 使得同时拥有!
3.OptionalHeader:由子结构体 IMAGE_OPTIONAL_HEADER 构成。
typedef struct _IMAGE_OPTIONAL_HEADER
{
//
// Standard fields(标准的文件结构).
//
+18h WORD Magic; // 标志字, ROM 映像(0107h),普通可执行文件(010Bh)
+1Ah BYTE MajorLinkerVersion; // 链接程序的主版本号
+1Bh BYTE MinorLinkerVersion; // 链接程序的次版本号
+1Ch DWORD SizeOfCode; // 所有含代码的节的总大小
+20h DWORD SizeOfInitializedData; // 所有含已初始化数据的节的总大小
+24h DWORD SizeOfUninitializedData; // 所有含未初始化数据的节的大小
+28h DWORD AddressOfEntryPoint; // 程序执行入口RVA
+2Ch DWORD BaseOfCode; // 代码的区块的起始RVA
+30h DWORD BaseOfData; // 数据的区块的起始RVA
//
// NT additional fields. 以下是属于NT结构增加的领域。
//
+34h DWORD ImageBase; // 程序的首选装载地址
+38h DWORD SectionAlignment; // 内存中的区块的对齐大小
+3Ch DWORD FileAlignment; // 文件中的区块的对齐大小
+40h WORD MajorOperatingSystemVersion; // 要求操作系统最低版本号的主版本号
+42h WORD MinorOperatingSystemVersion; // 要求操作系统最低版本号的副版本号
+44h WORD MajorImageVersion; // 可运行于操作系统的主版本号
+46h WORD MinorImageVersion; // 可运行于操作系统的次版本号
+48h WORD MajorSubsystemVersion; // 要求最低子系统版本的主版本号
+4Ah WORD MinorSubsystemVersion; // 要求最低子系统版本的次版本号
+4Ch DWORD Win32VersionValue; // 莫须有字段,不被病毒利用的话一般为0
+50h DWORD SizeOfImage; // 映像装入内存后的总尺寸
+54h DWORD SizeOfHeaders; // 所有头 + 区块表的尺寸大小
+58h DWORD CheckSum; // 映像的校检和
+5Ch WORD Subsystem; // 可执行文件期望的子系统
+5Eh WORD DllCharacteristics; // DllMain()函数何时被调用,默认为 0
+60h DWORD SizeOfStackReserve; // 初始化时的栈大小
+64h DWORD SizeOfStackCommit; // 初始化时实际提交的栈大小
+68h DWORD SizeOfHeapReserve; // 初始化时保留的堆大小
+6Ch DWORD SizeOfHeapCommit; // 初始化时实际提交的堆大小
+70h DWORD LoaderFlags; // 与调试有关,默认为 0
+74h DWORD NumberOfRvaAndSizes; // 下边数据目录的项数,Windows NT 发布是16
+78h IMAGE_DATA_DIRECTORY DataDirectory;
// !!!数据目录表
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
3.1子结构体IMAGE_OPTIONAL_HEADER32重要成员介绍:
3.1.1.AddressOfEntryPoint字段: 指出文件被执行时的入口地址,这是一个RVA地址(RVA的含义在下一节中详细介绍)。如果在一个可执行文件上附加了一段代码并想让这段代码首先被执行,那么只需要将这个入口地址指向附加的代码就可以了。
3.1.2.ImageBase字段: 指出文件的优先装入地址。也就是说当文件被执行时,如果可能的话,Windows优先将文件装入到由ImageBase字段指定的地址中,只有指定的地址已经被**模块使用时,文件才被装入到**地址中。链接器产生可执行文件的时候对应这个地址来生成机器码,所以当文件被装入这个地址时不需要进行重定位操作,装入的速度最快,如果文件被装载到**地址的话,将不得不进行重定位操作,这样就要慢一点。 对于EXE文件来说,由于每个文件总是使用独立的虚拟地址空间,优先装入地址不可能被**模块占据,所以EXE总是能够按照这个地址装入,这也意味着EXE文件不再需要重定位信息。对于DLL文件来说,由于多个DLL文件全部使用宿主EXE文件的地址空间,不能保证优先装入地址没有被**的DLL使用,所以DLL文件中必须包含重定位信息以防万一。因此,在前面介绍的 IMAGE_FILE_HEADER 结构的 Characteristics 字段中,DLL 文件对应的 IMAGE_FILE_RELOCS_STRIPPED 位总是为0,而EXE文件的这个标志位总是为1。 在链接的时候,可以通过对link.exe指定/base:address选项来自定义优先装入地址,如果不指定这个选项的话,一般EXE文件的默认优先装入地址被定为00400000h,而DLL文件的默认优先装入地址被定为10000000h。
3.1.3.SectionAlignment 字段和 FileAlignment字段: SectionAlignment字段指定了节被装入内存后的对齐单位。也就是说,每个节被装入的地址必定是本字段指定数值的整数倍。而FileAlignment字段指定了节存储在磁盘文件中时的对齐单位。
3.1.4.Subsystem字段: 指定使用界面的子系统。这个字段决定了系统如何为程序建立初始的界面,链接时的/subsystem:**选项指定的就是这个字段的值。
3.1.5.DataDirectory字段 :这个字段可以说是最重要的字段之一,它由16个相同的IMAGE_DATA_DIRECTORY结构组成,虽然PE文件中的数据是按照装入内存后的页属性归类而被放在不同的节中的,但是这些处于各个节中的数据按照用途可以被分为导出表、导入表、资源、重定位表等数据块,这16个IMAGE_DATA_DIRECTORY结构就是用来定义多种不同用途的数据块的。
3.1.5.1.IMAGE_DATA_DIRECTORY的定义:typedef struct _IMAGE_DATA_DIRECTORY {DWORD VirtualAddress;//虚拟地址;DWORD Size; //大小;} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;在PE文件中寻找特定的数据时,就是从这些IMAGE_DATA_DIRECTORY 结构开始的。
3.1.5.2.DataDirectory数据目录表在Winnt.h的定义为:#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 导出表!!!#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 导入表 !!!#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 资源目录!!!#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 异常目录#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 安全目录#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 重定位基本表!!!#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 调试目录#define IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 描术字串#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 机器值#define IMAGE_DIRECTORY_ENTRY_TLS 9 TLS目录#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG10 载入配值目录#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 绑定输入表#define IMAGE_DIRECTORY_ENTRY_IAT 12 导入地址表#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 延迟载入描述#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 COM信息
(3)区块表(也叫节表):PE文件中所有节的属性都被定义在节表中,节表由一系列的IMAGE_SECTION_HEADER结构排列而成,每个结构用来描述一个节,结构的排列顺序和它们描述的节在文件中的排列顺序是一致的。全部有效结构的最后以一个空的IMAGE_SECTION_HEADER结构作为结束,所以节表中总的IMAGE_SECTION_HEADER结构数量等于节的数量加一。节表总是被存放在紧接在PE文件头的地方。 另外,节表中 IMAGE_SECTION_HEADER 结构的总数总是由PE文件头 IMAGE_NT_HEADERS 结构中的 FileHeader.NumberOfSections 字段来指定的。
此结构共占40(28h)个字节:
typedef struct _IMAGE_SECTION_HEADER {+0h BYTE Name; // 节表名称,如“.text” //IMAGE_SIZEOF_SHORT_NAME=8 union +8h { DWORD PhysicalAddress; // 物理地址 DWORD VirtualSize; // 真实长度,这两个值是一个联合结构,可以使用其中的任何一个,一 // 般是取后一个 } Misc;+ch DWORD VirtualAddress; // 节区的 RVA 地址+10h DWORD SizeOfRawData; // 在文件中对齐后的尺寸+14h DWORD PointerToRawData; // 在文件中的偏移量+18h DWORD PointerToRelocations; // 在OBJ文件中使用,重定位的偏移+1ch DWORD PointerToLinenumbers; // 行号表的偏移(供调试使用地)+1eh WORD NumberOfRelocations; // 在OBJ文件中使用,重定位项数目+20h WORD NumberOfLinenumbers; // 行号表中行号的数目+24h DWORD Characteristics; // 节属性如可读,可写,可执行等} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
重要成员介绍:1.Name:区块名。这是一个由8位的ASCII 码名,用来定义区块的名称。多数区块名都习惯性以一个“.”作为开头(例如:.text),这个“.” 实际上是不是必须的。值得我们注意的是,如果区块名超过 8 个字节,则没有最后的终止标志“NULL” 字节。并且前边带有一个“$” 的区块名字会从连接器那里得到特殊的待遇,前边带有“$” 的相同名字的区块在载入时候将会被合并,在合并之后的区块中,他们是按照“$” 后边的字符的字母顺序进行合并的。 另外每个区块的名称都是唯一的,不能有同名的两个区块。但事实上节的名称不代表任何含义,他的存在仅仅是为了正规统一编程的时候方便程序员查看方便而设置的一个标记而已。所以将包含代码的区块命名为“.Data” 或者说将包含数据的区块命名为“.Code” 都是合法的。当我们要从PE 文件中读取需要的区块时候,不能以区块的名称作为定位的标准和依据,正确的方法是按照 IMAGE_OPTIONAL_HEADER32 结构中的数据目录字段结合进行定位。
2.Virtual Size:该区块表对应的区块的大小,这是区块的数据在没有进行对齐处理前的实际大小。
3.Virtual Address:该区块装载到内存中的RVA 地址。这个地址是按照内存页来对齐的,因此它的数值总是 SectionAlignment 的值的整数倍。在Microsoft 工具中,第一个快的默认 RVA 总为1000h。在OBJ 中,该字段没有意义地,并被设为0。
4.SizeOfRawData:该区块在磁盘中所占的大小。在可执行文件中,该字段是已经被FileAlignment 潜规则处理过的长度。
5.PointerToRawData:该区块在磁盘中的偏移。这个数值是从文件头开始算起的偏移量
6.PointerToRelocations:这个在EXE文件中没有意义,在OBJ 文件中,表示本区块重定位信息的偏移值。(在OBJ 文件中如果不是零,它会指向一个IMAGE_RELOCATION 结构的数组)
7.PointerToLinenumbers:行号表在文件中的偏移值,文件的调试信息。
8.NumberOfRelocations:在EXE文件中也没有意义,在OBJ 文件中,是本区块在重定位表中的重定位数目来着。
9.NumberOfLinenumbers:该区块在行号表中的行号数目。
10.Characteristics:该区块的属性。该字段是按位来指出区块的属性(如代码/数据/可读/可写等)的标志。IMAGE_SCN_CNT_CODE 0x00000020 包含可执行代码的部分。IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040 该部分包含初始化数据。IMAGE_SCN_CNT_UNINITIALIZED_DATA0x00000080 该节包含未初始化的数据。IMAGE_SCN_MEM_DISCARDABLE 0x02000000 可以根据需要丢弃该节。如:重定位数据表IMAGE_SCN_MEM_SHARED 0x10000000 可以在内存中共享该节。IMAGE_SCN_MEM_EXECUTE 0x20000000 该节可以作为代码执行。IMAGE_SCN_MEM_READ 0x40000000 该区块可读。IMAGE_SCN_MEM_WRITE 0x80000000 该区块可写。
此贴为浅析PE文件,也是我这几天的学习成果 ,全部是干货!!!其中有些资源从网上找的,帮大家整理到一起了,希望对大家有帮助。若有违规,请版主删帖!!!
页:
[1]