主要是PE文件头部分。来再看雪 《加密与解密》
//PE文件格式小部分资料,摘自《加密与解密》-看雪
typedef struct _IMAGE_DOS_HEADER{ // DOS .EXE header
+0h WORD e_magic; // Magic number
+2h WORD e_cblp; // Bytes on last page of file
+4h WORD e_cp; // Pages in file
+6h WORD e_crlc; // Relocations
+8h WORD e_cparhdr; // Size of header in paragraphs
+Ah WORD e_minalloc; // Minimum extra paragraphs needed
+Ch WORD e_maxalloc; // Maximum extra paragraphs needed
+Eh WORD e_ss; // Initial (relative) SS value
+10h WORD e_sp; // Initial SP value
+12h WORD e_csum; // Checksum
+14h WORD e_ip; // Initial IP value
+16h WORD e_cs; // Initial (relative) CS value
+18h WORD e_lfarlc; // File address of relocation table
+1Ah 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
+3Ch LONG e_lfanew; // File address of new exe header 新exe头文件地址
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
对于PE文件来说, 最有用的是e_lfanew字段, 这个字段指出了真正的PE文件头,它占用4个字节,
位于文件开始偏移3Ch字节中。
PE文件头, PE装载器将从DOS MZ header的e_lfanew字段里找到PE Header的起始偏移量,跳到真正的
的PE文件头处。
IMAGE_NT_HEADERS的数据结构如下所示(左边的数字是到PE文件头的偏移量)
typedef struct _IMAGE_NT_HEADERS{
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
1. 字串 "PE\0\0" 它是PE文件头的开始。 5045, 直接查找串 'PE', 或者 16进制 5045就可以找到
PE头。
2. 映像文件头 (IMAGE_FILE_HEADER)
PE文件头的开始后然后就是 映像文件头,映像文件头里包括这些东西,如CPU类型,块数目,时间日期
标记, COFF符号指针,就是调试信息, 符号数等。
由于16进制低位在前,所有 4C01实际上是 014Ch,表示的Intel i386系列的CPU。
还是将全部的IMAGE_NT_HEADERS 结构的成员全部列出。
;PE文件头结构定义
IMAGE_NT_HEADERS STRUC
;-----------------PE文件标识"PE"
+0h Signature DWORD ? ;PE文件标识'PE'
;-----------------映像文件头(IMAGE_FILE_HEADER)
+04h Machine WORD ? ;运行平台
+06h NumberOfSections WORD ? ;块数目
+08h TimeDateStamp DWORD ? ;文件创建日期和时间
+0Ch PointerToSymbolTable DWORD ? ;指向符号表(用于测试)
+10h NumberOfSymbols DWORD ? ;符号表中符号个数(用于调试)
+14h SizeofOptionalHeader WORD ? ;IMAGE_OPTIONAL_HEADER32结构大小
+16h Characteristics WORD ? ;文件属性
;-----------------可选映像头(IMAGE_OPTIONAL_HEADER32)
+18h Magic WORD ? ;标志字(总是010bh)
+1Ah MajorLinkerVersion BYTE ? ;链接器版本号
+1Bh MinorLinkerVersion BYTE ? ;
+1Ch SizeOfCode DWORD ? ;代码段大小
+20h SizeOfInitializeData DWORD ? ;已初始化数据块大小
+24h SizeOfOfUninitlaizeData DWORD ? ;未初始化数据块大小
+28h AddressOfEntryPoint DWORD ? ;程序执行入口RVA, 程序执行入口的相对虚拟地址,这个好
+2Ch BaseOfCode DWORD ? ;代码段开始RVA
+30h BaseOfData DWORD ? ;数据段起始RVA
+34h ImageBase DWORD ? ;程序默认装入的基址RVA
+38h SectionAlignment DWORD ? ;内存中的块对齐粒度
+3Ch FileAlighment DWORD ? ;文件中的快对器粒度
+40h MajorOperatingSystemVersion WORD ? ;操作系统主版本号
+42h MinorOperatingSystemVersion WORD ? ;操作系统次版本号
+44h MajorImageVersion WORD ? ;用户自定义版本号
+46h MinorImageVersion WORD ? ;用户自定义次版本号
+48h MajorSubsystemVersion WORD ? ;所需自系统版本号
+4Ah MinorSubsystemVersion WORD ? ;
+4Ch Reserved DWORD ? ;保留
+50h SizeOfImage DWORD ? ;内存中整个PE映像尺寸
+54h SizeOfHeaders DWORD ? ;部首+块表大小
+58h CheckSum DWORD ? ;校验和
+5Ch Subsystem WORD ? ;文件子系统
+5Eh DllCharacteristics WORD ?
+60h SizeOfStackReserve DWORD ? ;初始化的堆栈大小
+64h SizeOfStackCommit DWORD ? ;初始化实际提交的堆栈大小
+68h SizeOfHeapReserve DWORD ? ;初始化时保留的堆大小
+6Ch SizeOfHeapCommit DWORD ? ;初始化时实际提交的堆大小
+70h LoaderFlags DWORD ?
+74h NumberOfRvaAndSizes DWORD ? ;数据目录结构的数量
DataDirectory IMAGE_DATA_DIRECTORY 16 DUP(<0>); 数据目录表
IMAGE_NT_HEADERS ENDS
Section Table(节表)
节表其实就是紧挨着 PE header的一结构数组。该数组成员的数目由file header (IMAGE_FILE_HEADER)结构中NumberOfSections域的域值来决定。节表结构又命名为IMAGE_SECTION_HEADER。
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 节表名称,如".text"
union {
DWORD PhysicalAddress; // 物理地址
DWORD VirtualSize; // 真实长度
} Misc;
DWORD VirtualAddress; // RVA
DWORD SizeOfRawData; // 物理长度
DWORD PointerToRawData; // 节基于文件的偏移量
DWORD PointerToRelocations; // 重定位的偏移
DWORD PointerToLinenumbers; // 行号表的偏移
WORD NumberOfRelocations; // 重定位项数目
WORD NumberOfLinenumbers; // 行号表的数目
DWORD Characteristics; // 节属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
同样,不是所有成员都是很有用的,我们只关心那些真正重要的。
Field | Meanings |
---|---|
Name1 | 事实上本域的名称是"name",只是"name"已被MASM用作关键字,所以我们只能用"Name1"代替。这儿的节名长不超过8字节。记住节名仅仅是个标记而已,我们选择任何名字甚至空着也行,注意这里不用null结束。命名不是一个ASCIIZ字符串,所以不用null结尾。 |
VirtualAddress | 本节的RVA(相对虚拟地址)。PE装载器将节映射至内存时会读取本值,因此如果域值是1000h,而PE文件装在地址400000h处,那么本节就被载到401000h。 |
SizeOfRawData | 经过文件对齐处理后节尺寸,PE装载器提取本域值了解需映射入内存的节字节数。(译者注:假设一个文件的文件对齐尺寸是0x200,如果前面的 VirtualSize域指示本节长度是0x388字节,则本域值为0x400,表示本节是0x400字节长)。 |
PointerToRawData | 这是节基于文件的偏移量,PE装载器通过本域值找到节数据在文件中的位置。 |
Characteristics | 包含标记以指示节属性,比如节是否含有可执行代码、初始化数据、未初始数据,是否可写、可读等。 |
现在我们已知晓 IMAGE_SECTION_HEADER结构,再来模拟一下PE装载器的工作吧:
- 读取 IMAGE_FILE_HEADER的NumberOfSections域,知道文件的节数目。
- SizeOfHeaders域值作为节表的文件偏移量,并以此定位节表。
- 遍历整个结构数组检查各成员值。
- 对于每个结构,我们读取PointerToRawData域值并定位到该文件偏移量。然后再读取SizeOfRawData域值来决定映射内存的字节数。将VirtualAddress域值加上ImageBase域值等于节起始的虚拟地址。然后就准备把节映射进内存,并根据Characteristics域值设置属性。
- 遍历整个数组,直至所有节都已处理完毕。
注意我们并没有使用节名:这其实并不重要。
PE文件的框架结构: DOS首部 -> PE文件头 -> 块表(Section Table) -> 块(Section) -> 调试信息
不得不佩服美妙的设计。
转载于:https://blog.51cto.com/whatday/1382662