PE文件定义
PE 文件(”Portable executable”, 可移植的可执行文件)文件格式,是微软Windows NT, 中Win32、Win32s中的可执行的二进制的文件格式。 包括:.exe, .dll, .sys, .com, .ocs. PE文件最重要的两个因素:
1.磁盘上的可执行文件和它被映射到windows内存之后的格式非常相像。
2.对于Win32 来讲, 模块中多使用的所有代码、数据、资源、导入表、和其他需要的模块数据结构都在一个连续的内存块中。因此,只需要知道PE Loader把可执行文件映射到了内存的什么地方(基址),通过作为映像的一部分指针,就可以找到这个模块的所有不同的块。
PE文件总览:
1. DOS Header: (size:64byte)
_IMAGE_DOS_HEADER结构体:
1 typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header 2 WORD e_magic; // Magic number 3 WORD e_cblp; // Bytes on last page of file 4 WORD e_cp; // Pages in file 5 WORD e_crlc; // Relocations 6 WORD e_cparhdr; // Size of header in paragraphs 7 WORD e_minalloc; // Minimum extra paragraphs needed 8 WORD e_maxalloc; // Maximum extra paragraphs needed 9 WORD e_ss; // Initial (relative) SS value 10 WORD e_sp; // Initial SP value 11 WORD e_csum; // Checksum 12 WORD e_ip; // Initial IP value 13 WORD e_cs; // Initial (relative) CS value 14 WORD e_lfarlc; // File address of relocation table 15 WORD e_ovno; // Overlay number 16 WORD e_res[4]; // Reserved words 17 WORD e_oemid; // OEM identifier (for e_oeminfo) 18 WORD e_oeminfo; // OEM information; e_oemid specific 19 WORD e_res2[10]; // Reserved words 20 LONG e_lfanew; // File address of new exe header 21 } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
结构体中有两个重要的数据成员。第一个为e_magic,这个必须为MZ,即0x5A4D。另一个重要的数据成员是最后一个成员e_lfanew,这个成员的值为IMAGE_NT_HEADERS的偏移。其中,*e_lfanew这个字段的值: PE Header 在磁盘文件中相对于文件开始的偏移地址.
实例截图:
2. PE Header: (size: 248bytes)
IMAGE_NT_HEADERS 紧接在DOS Stub之后,位置有e_lfanew所指
1 typedef struct _IMAGE_NT_HEADERS { 2 DWORD Signature; //4 bytes PE文件头标志:(e_lfanew)->‘PE’ 3 IMAGE_FILE_HEADER FileHeader; //20 bytes PE文件物理分布的信息 4 IMAGE_OPTIONAL_HEADER32 OptionalHeader;//224bytes PE文件逻辑分布的信息 5 } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
PE Header 总览
IMAGE_NT_HEADERS结构体成员解析:
2.1.Signature: (4 bytes)
2.2.IMAGE_FILE_HEADER(20 bytes)
1 typedef struct _IMAGE_FILE_HEADER { 2 WORD Machine; //运行平台 3 WORD NumberOfSections; //文件区块数目 4 DWORD TimeDateStamp; //文件创建日期和时间 5 DWORD PointerToSymbolTable; //指向符号表(主要用于调试) 6 DWORD NumberOfSymbols; //符号表中符号个数 7 WORD SizeOfOptionalHeader; //IMAGE_OPTIONAL_HEADER32 结构大小 8 WORD Characteristics; //文件属性 9 } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
IMAGE_FILE_HEADER结构体成员解析:
1). Machine 代表了CPU的类型 //运行平台
#define IMAGE_FILE_MACHINE_UNKNOWN 0 #define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386. #define IMAGE_FILE_MACHINE_R3000 0x0162 // MIPS little-endian, 0x160 big-endian #define IMAGE_FILE_MACHINE_R4000 0x0166 // MIPS little-endian #define IMAGE_FILE_MACHINE_R10000 0x0168 // MIPS little-endian #define IMAGE_FILE_MACHINE_WCEMIPSV2 0x0169 // MIPS little-endian WCE v2 #define IMAGE_FILE_MACHINE_ALPHA 0x0184 // Alpha_AXP #define IMAGE_FILE_MACHINE_SH3 0x01a2 // SH3 little-endian #define IMAGE_FILE_MACHINE_SH3DSP 0x01a3 #define IMAGE_FILE_MACHINE_SH3E 0x01a4 // SH3E little-endian #define IMAGE_FILE_MACHINE_SH4 0x01a6 // SH4 little-endian #define IMAGE_FILE_MACHINE_SH5 0x01a8 // SH5 #define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM Little-Endian …………………. #define IMAGE_FILE_MACHINE_CEE 0xC0EE
2) NumberOfSections: 代表区块的数目,区块表紧跟在IMAGE_NT_HEADERS后面, 区块表大概是一个链表结构,链表长度由NumberOfSection的数值决定.
3) TimeDataStamp: 表明文件的创建时间
4) SizeOfOptionalHeader: 是IMAGE_NT_HEADERS的另一个子结构IMAGE_OPTIONAL_HEADER的大小
5) Characteristics: 代表文件的属性EXE文件一般是0100h DLL文件一般是210Eh,多种属性可以用或运算同时拥有
#define IMAGE_FILE_RELOCS_STRIPPED 0x0001 // 重定位信息被移除 #define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 // 文件可执行 #define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 // 行号被移除 #define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 // 符号被移除 …….. #define IMAGE_FILE_32BIT_MACHINE 0x0100 // 32位机器 #define IMAGE_FILE_DEBUG_STRIPPED 0x0200 // .dbg文件的调试信息被移除 …………………. #define IMAGE_FILE_SYSTEM 0x1000 // 系统文件 #define IMAGE_FILE_DLL 0x2000 // 文件是一个dll #define IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 // 文件只能运行在单处理器上
实例截图:
2.3. IMAGE_OPTIONAL_HEADER(224 bytes)
紧接IMAGE_FILE_HEADER之后,IMAGE_OPTIONAL_HEADER的大小由IMAGE_FILE_HEADER中倒数第二个成员(SizeOfOptionalHeader)指定. IMAGE_OPTIONAL_HEADER结构体如下:
1 typedef struct _IMAGE_OPTIONAL_HEADER { 2 WORD Magic; //映像文件的状态 3 BYTE MajorLinkerVersion; //连接器的主版本号 4 BYTE MinorLinkerVersion; //连接器的次版本号 5 DWORD SizeOfCode; //代码段的大小,如果有多个代码段则为总和 6 DWORD SizeOfInitializedData; //初始化数据段大小.如果多个则为总和 7 DWORD SizeOfUninitializedData;//未初始化数据段大小,.如果多个则为总和.bbs 8 DWORD AddressOfEntryPoint; //PE文件入口地址的RAV:OEP = ImageBase + RAV 9 DWORD BaseOfCode; //代码块起始地址的RVA 10 DWORD BaseOfData;//数据块的起始地址的RVA 11 // 12 // NT additional fields. 13 // 14 DWORD ImageBase; //可执行文件的基址ImageBase 15 DWORD SectionAlignment; //每一个块必须保证始于这个值的整数倍 16 DWORD FileAlignment; //对齐映射文件部分原始数据 2 or 512 or 64: 默认为512 17 WORD MajorOperatingSystemVersion;//要求的操作系统的主版本号 18 WORD MinorOperatingSystemVersion;//要求的操作系统的次版本号 19 WORD MajorImageVersion;//映像的主版本号 20 WORD MinorImageVersion;//映像的次版本号 21 WORD MajorSubsystemVersion;//子系统的主版本号 22 WORD MinorSubsystemVersion;//子系统的次版本号 23 DWORD Win32VersionValue;//保留值.必须为0 24 DWORD SizeOfImage;//映像文件的大小 25 DWORD SizeOfHeaders; 26 DWORD CheckSum;//映像文件的校验和 27 WORD Subsystem;//运行此映像的字系统 28 WORD DllCharacteristics;//映像文件的DLL特征 29 DWORD SizeOfStackReserve;//堆栈保留字节. 0x100000 30 DWORD SizeOfStackCommit;//线程开始提交的初始堆栈大小 31 DWORD SizeOfHeapReserve;//为初始进程保留的虚拟内存总数 32 DWORD SizeOfHeapCommit;//进程开始提交初始虚拟内存的大小 33 DWORD LoaderFlags; 34 DWORD NumberOfRvaAndSizes; //0x10 35 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; 36 //指向第一个IMAGE_DATA_DIRECTORY 37 } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
_IMAGE_OPTIONAL_HEADER结构体实例截图以及成员解析:
1) Magic:32位可执行文件来:0x010B
64位可执行文件来:0x020B
0x107
2) SizeOfCode: 代码段的大小,如果有多个代码段则为总和
RAW:经过文件对齐处理后大小(PE文件中的大小)
3) SizeOfInitializedData: 初始化数据段大小.如果多个则为总和
4) ImageBase: 建议的装载地址
PE建议装载地址:
实际装载地址:
5) AddressOfEntryPoint: 程序执行的入口RVA地址
OEP = ImageBase + (AddressOfEntryPoint)RVA
6) SectionAlignment:为内存中节的对齐大小,一般为0×00001000
7) FileAlignment:为PE文件中节的对齐大小
8) SizeofImage:映像文件的大小
9) DataDirectory为数据目录表数组,比较重要:共有16个表项
Size = sizeof(_IMAGE_DATA_DIRECTORY) * 16
sizeof(_IMAGE_DATA_DIRECTORY) = 8 bytes
_IMAGE_DATA_DIRECTORY结构体以及成员定义:
1 typedef struct _IMAGE_DATA_DIRECTORY { 2 DWORD VirtualAddress; 3 DWORD Size; 4 } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; 5 #define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16 6 // Directory Entries 7 #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory 8 #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory 9 #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory 10 #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory 11 #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory 12 #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table 13 #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory 14 // IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage) 15 #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data 16 #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP 17 #define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory 18 #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory 19 #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers 20 #define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table 21 #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors 22 #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor
Import Table RVA & Size截图:
3. Section Header:
Section Header 数量:_IMAGE_FILE_HEADER结构体中NumberOfSections成员。
Section Header 定位:紧跟在IMAGE_NT_HEADERS后面
结构体大小:40 bytes
_IMAGE_SECTION_HEADER 结构体以及重要变量的定义:
1 #define IMAGE_SIZEOF_SHORT_NAME 8 2 typedef struct _IMAGE_SECTION_HEADER { 3 BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; 4 union { 5 DWORD PhysicalAddress; 6 DWORD VirtualSize; 7 } Misc; 8 DWORD VirtualAddress; //内存中偏移地址 9 DWORD SizeOfRawData; //PE文件中对其之后的大小 10 DWORD PointerToRawData;//为PE块区在PE文件中偏移 11 DWORD PointerToRelocations; 12 DWORD PointerToLinenumbers; 13 WORD NumberOfRelocations; 14 WORD NumberOfLinenumbers; 15 DWORD Characteristics; //块区的属性:可读、可写.. 16 } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; 17 #define IMAGE_SIZEOF_SECTION_HEADER 40
重要数据成员:
1) Name[IMAGE_SIZEOF_SHORT_NAME]:
8字节大小的NAME, 如果节区名称小于8个字节,则多余的用0填充,否则全部填充节名,末尾不保证有1个0,同样会被名字填充
2) PointerToRawData:
为节区在PE文件中的偏移
3) Characteristics: 为节区的属性,如可读、可写、可执行等
1 #define IMAGE_SCN_CNT_CODE 0x00000020 // Section contains code. 2 #define IMAGE_SCN_LNK_NRELOC_OVFL 0x01000000 // Section contains extended relocations. 3 #define IMAGE_SCN_MEM_DISCARDABLE 0x02000000 // Section can be discarded. 4 #define IMAGE_SCN_MEM_NOT_CACHED 0x04000000 // Section is not cachable. 5 #define IMAGE_SCN_MEM_NOT_PAGED 0x08000000 // Section is not pageable. 6 #define IMAGE_SCN_MEM_SHARED 0x10000000 // Section is shareable. 7 #define IMAGE_SCN_MEM_EXECUTE 0x20000000 // Section is executable. 8 #define IMAGE_SCN_MEM_READ 0x40000000 // Section is readable.
Section Header 实例截图:
4. 导入表
_IMAGE_IMPORT_DESCRIPTOR 数据结构:(20 bytes)
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; // 0 for terminating null import descriptor DWORD OriginalFirstThunk; // RVA 指向INT (PIMAGE_THUNK_DATA) }; DWORD TimeDateStamp; DWORD ForwarderChain; // -1 if no forwarders DWORD Name; //dll 名称 DWORD FirstThunk; //指向引入函数真实地址单元处的RVA IAT } IMAGE_IMPORT_DESCRIPTOR; typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
其中OriginalFirstThunk和FirstThunk非常类似,指向两个本质上相同的数组IMAGE_THUNK_DATA。
1) 定位查找IMAGE_IMPORT_DESCRIPTO结构
A 获取引入表的RVA.也就是
data directory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress 所指的值
typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory
查看 IMAGE_DIRECTORY_ENTRY_IMPORT的值
&data directory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = 0x0002D51C
&data directory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualSize= 0xA0
B. 查找导入表所在的区段:
if(RVA>=SECTION.VirtualAddress && RVA<SECTION.Misc.VirtualSize)
{
//处于该节
}
else
{
//不在该节中
}
C. 找到引入表所在的节后就可以用该节的VirtualAddress和PointerToRawData两个域确定引入表在文件中的偏移量:RVA –△k
VirtualAddress = 0x00026000
PointerToRawData = 0x00025000
△k = VirtualAddress – PointerToRawData = 0x1000H
Address = 0x0002D51C - △k = 0x0002C51C
然后定位到文件偏移处:
阴影部分是IID的内容, IID的大小为20h, 阴影部分存在链各个IID,最后一个为0000000, 说明此PE文件只有8个IID, 对应8个dll。
0xA0 = 160 bytes = 7 * 20bytes +20bytes(空白)
截图
第四个变量的地址RVA:0002DB14, 需要转换成对应的文件偏移
(0x0002DB14 –△k) = 0x0002cb14,定位到文件偏移为0x0002cb14的地方
查看内容,里面记录的是IMAGE_IMPORT_DESCRIPTOR的第四个成员变量多对应的dll的名字,mfc90u.dll,到此,我们已经找到了这个输入的dll
2) 获取dll调用的所有函数
IMAGE_IMPORT_DESCRIPTOR中的第一个参数和最后一个参数,original_first_thunk 和first_thunk分别指向了INT(输入名称表)IAT(输入地址表)这两个表里面分别记录了指向调用函数名的地址,和此函数在dll中的序号(序号用来快速索引dll中的函数)
0x0002D85C和0x000262A0是INT 和IAT数组的首地址,下面我们跳到该地址(由于△K=0x1000,故RVA=文件偏移0x0002C85C和0x000252A0)
_IMAGE_THUNK_DATA32数据结构:
typedef struct _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;
a.当一个函数以序号导入.MAGE_THUNK_DATA结构中的AddressOfData最高位被设成1.用16进制表示为(0x80000000),例入一个IMAGE_THUNK_DATA的AddressOfData的值为0x 800010E4在mfc90u.dll数组中.就表示IMAGE_THUNK_DATA将引入mfc90u.dll
中的第10E4号函数
INT数组:
IAT数组:
b.如果一个函数以名称导入.IMAGE_THUNK_DATA结构中的Ordinal域就包含一个RVA.这个RVA指向一个IMAGE_IMPORT_BY_NAME 结构.该结构保存了一个引入函数的相关信息:例如MSVCR90.dll
typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; //序列号 BYTE Name[1];//函数名称 } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
有第四个变量Name定位dll名字
定位INT和IAT:
INT:
IAT
数组里面的值为指向IMAGE_IMPORT_BY_NAME的地址,IMAGE_IMPORT_BY_NAME里面存放的是所调用函数的名字的地址,下来我们选取一个数组里面的值,跳转到相应的IMAGE_IMPORT_BY_NAME
可以看到,上面的那个图片中显示了所调用的函数的名字,名字前面的两个字节代表的是函数在dll中的序号,方便以后快速索引到
3. 需要注意的地方
INT 和IAT数组在一开始的时候,里面存放的地址都是一样的,他们都是指向所调用函数的名字的字符串。而在加载到内存的时候,IAT的值会发生变换,它的值存放的是dll中函数实际被调用的地址,在加载到内存后,就只需要保存IAT就可以了,利用它来调用函数
今天先到这里.有时间的话在继续...