Chapter 4 PE文件结构
4.1 PE文件及其表示形式
可移植的可执行文件(Portable Executable File)
PE文件主要包括:.exe,.dll,.sys等,.dll是动态加载库,不能直接执行
在DOS系统中,可执行文件格式为MZ
4.2 PE文件格式与恶意软件的关系
文件感染:
- 使PE文件具备病毒功能
- 而又不破坏PE文件原有的功能和外在形态
- 感染与控制权获取
方法:
- 代码植入
- 控制权获取
- 图标更改等(如熊猫烧香)
注意:PE文件感染实质上就是修改文件的内容,与修改文本文件中的内容性质相同。PE文件与文本文件同是文件,但不同的是windows系统内核已经被设计好能够识别PE文件的结构并执行其中的代码,所以PE文件与文本文件有差异。
4.3 PE文件总体结构
1. MS_DOS HEADER
PE文件开头位置,用于PE文件开头位置定位与合法性检测。长度为0x40。开头两个字符为’MZ’。,结尾4字节(0x3C)为新exe HEADER的地址。设计MS_DOS HEADER是为了向下兼容DOS系统(如果不是为了这个目的完全可以将NT HEADER作为PE文件头),在DOS系统执行此文件时会显示该文件无法在DOS模式下运行。
2. MS_DOS Stub
在MS_DOS下运行的程序代码与数据,一般为提醒用户’This Program cannot be run in DOS mode’
3. NT HEADER
分为3个部分:开头字符串’PE\0\0’、映像文件头、可选文件头
(1) 开头字符串:用于表示该文件是否为可执行文件
(2) File Header:映像文件头,包含可执行文件的一些必要信息,一般紧跟在开头字符串后面,包含的项有:
- Machine:机器类型,2字节,0x14C表示x86架构
- Number of Sections:节数量,2字节,PE文件中代码、数据等分别存放于不同的节中。
- TimeDataStamp:4字节,生成该文件的时间,指从1970年1月1日开始计算经过的秒数。
- Pointer to Symbol Table:4字节,COFF符号表偏移(COFF文件:通用对象文件格式,PE文件便是基于COFF文件设计,不要求掌握)
- Number of Symbols:4字节,符号数量(指COFF符号表中的符号)
- Size of Optional Header:2字节,可选头大小
- Characteristics:4字节,表示该文件是exe文件还是dll文件
(3) Optional Header:可选文件头,包含可执行文件的其他必要信息,长度由节数量等因素确定
- Address of Entry Point (PE_HEADER+0x28, 4 bytes):准备运行的文件的第一条指令的RVA(RVA:相对虚拟地址,相对于内存中Image Base的地址)
- Image Base (PE_HEADER+0x34, 4 bytes):内存镜像加载地址,PE文件在内存中的优先装载地址。
- 病毒不能只靠修改Image Base执行自己的代码,因为原有代码中可能存在如call 0x401010的代码,其中0x401010是硬编码在指令中的,一旦Image Base发生变化,这些硬编码地址可能就将无效,程序原有的代码也将无法执行。
- Section Alignment (PE_HEADER+0x38, 4 bytes):内存中节对齐的粒度
- File Alignment (PE_HEADER+0x3C, 4 bytes):文件中节对齐的粒度
- 注意这里内存节对齐和文件节对齐粒度的理解。内存中节与节之间的地址之差与文件中的很可能不一样,在文件中,为了减少存储空间的浪费,通常不会将节对齐粒度设置得太大,一般为0x200,而内存中节对齐一般取0x1000作为粒度。
- Data Directory (PE_HEADER+0x78, 8n bytes):开头记录Data Directory中节属性数量,每一条属性长度均为8字节,记录这些节的RVA和size。这些节与常用的代码节、数据节等不同,多为辅助节,如导入表、导出表、引入地址表(IAT)等,保存程序运行的关键控制信息。
4. Section Tables
节表,每个节表保存了该节的长度、在文件和内存中的开始地址、节名、节属性(RWX属性等)
- 节名:8字节
- Virtual Size:4字节,实际长度
- RVA:该节的RVA
- Size of Raw Data:文件中该节所占的大小
- Pointer to Raw Data:文件中该节的起始地址
- Characteristics:节属性,由几个比特异或得到
- bit 5:表明这个节中是否存放代码
- bit 6:表明这个节中是否为已初始化数据
- bit 7:表明这个节中是否为未初始化数据(bss段)
- bit 9:表明这个节中是否包含注释或其他信息
- bit 11:表明这个节中的内容是否应该被放入最终的exe文件中
- bit 25:表明这个块是否可以丢弃(通常为重定位节.reloc)
- bit 28:表明这个块是否可以共享
- bit 29:表明这个块是否可执行
- bit 30:表明这个块是否可读
- bit 31:表明这个块是否可写
5. Sections
- .text / CODE:代码节,保存全部代码。每个PE文件均存在
- .data / DATA:数据节,保存已初始化数据(编译时已经确定的数据)
- .bbs:数据节,保存未初始化数据(未初始化的全局和静态变量)
- .rdata:引入函数节,保留引入函数的信息(函数名及所属dll文件名等)这些函数位于一个或多个dll文件中。
- Import Address Table (IAT):一系列Image_Thunk_Data结构数组(4字节),每一个结构都定义了一个指向导入函数的Hint和名字的指针或Hint或其他值,而导入函数的Hint和名字实际保存于Import Hints/Names & DLL Names中。在磁盘文件中,IAT与INT保存的内容相同;在内存中,这里保存所有引入函数在内存中的地址。
1
2
3
4
5
6
7
8IMAGE_THUNK_DATA STRUCT
union u1
ForwarderString DWORD ? ; 转向者RVA
Function DWORD ? ; 被引入的函数的内存地址(IAT表)
Ordinal DWORD ? ; 被引入API的函数序号(INT表)
AddressOfData DWORD ? ; 被引入API的hint/name RVA(INT表)
ends
IMAGE_THUNK_DATA ENDS - Import Directory Table (IDT):引入目录表,其地址存放在NT HEADER可选头的第二个属性之中便于获取,由IMAGE_IMPORT_DESCRIPTOR结构体(长度20字节)数组组成,其数量取决于使用的DLL文件的数量,每一个结构对应一个DLL文件。所有DLL结构体后有一个全0的结构体用于表示这部分结束。 OriginalFirstThunk是指向Import Name(lookup) Table中的指针
1
2
3
4
5
6
7
8
9
10IMAGE_IMPORT_DESCRIPTOR STRUCT
union
Characteristics dd ?
OriginalFirstThunk dd ? ; 指向INT中对应DLL的RVA
Ends
TimeDateStamp dd ?
ForwarderChain dd ?
Name1 dd ? ; 指向dll文件名字符串的RVA
FirstThunk dd ? ; 指向IAT中对应DLL的RVA
IMAGE_IMPORT_DESCRIPTOR ENDS
FirstThunk是指向Import Address Table中的指针 - Import Name table:一系列Image_Thunk_Data结构数组,在Import Name Table中,data最高位为0时表示通过函数名引入,为1表示通过序号引入。不同DLL文件的结构体之间通过一个全0的DWORD分隔。
- Import Hints/Names & DLL Names:保存每个函数的Hint和名字,Hint为2字节,名字紧跟在Hint之后。一个DLL文件的所有引入函数列举完毕后在后面附上DLL的名字,下一个DLL文件的函数信息写在后面,需要4字节对齐。
- Import Address Table (IAT):一系列Image_Thunk_Data结构数组(4字节),每一个结构都定义了一个指向导入函数的Hint和名字的指针或Hint或其他值,而导入函数的Hint和名字实际保存于Import Hints/Names & DLL Names中。在磁盘文件中,IAT与INT保存的内容相同;在内存中,这里保存所有引入函数在内存中的地址。
- .edata:导出函数节,本文件向其他程序提供调用函数的列表、函数所在地址和具体代码实现,多见于DLL文件。
- Image Export Directory:导出目录表,其起始地址保存在NT HEADER可选头的第一个属性之中便于获取
1
2
3
4
5
6
7
8
9
10
11
12
13
14IMAGE_EXPORT_DIRECTORY STRUCT
{
DWORD Characteristics
DWORD TimeDateStamp ; 文件生成时间
WORD MajorVersion
WORD MinorVersion
DWORD Name ; 指向DLL的名字(RVA)
DWORD Base ; ExportAddress开始序号,一般为1
DWORD NumberOfFunctions ; 函数的数量
DWORD NumberOfNames
DWORD AddressOfFunctions ; Address Table RVA, 函数地址数组
DWORD AddressOfNames ; Name Pointer RVA, 函数名所在地址数组
DWORD AddressOfNameOrdinals ; Ordinal RVA, 函数索引序列号数组
};IMAGE_EXPORT_DIRECTORY ENDS - Export Address Table:导出地址表,多用于保存导出函数地址
1
2
3
4
5
6
7Typedef struct _image_Export_address_Table
{
union{
DWORD dwExportRVA; // 指向导出地址
DWORD dwForwarderRVA; // 指向另外DLL中某个API函数名
};
}IMAGE_Export_Address_Table, *pIMAGE_Export_Address_Table - Export Name Pointer Table:导出名字指针表,保存导出函数名字字符串的地址
1
2
3
4Typedef struct _IMAGE_Export_Name_Pointer_Table_
{
DWORD dwPointer;
}IMAGE_Export_Name_Pointer_Table; - Export Ordinal Table:导出符号表,保存导出函数的编号。 注意:由于代码段中可能存在无名函数、重载函数等,因此导出函数的编号与导出函数名可能并不是一一对应,因此需要导出函数编号为每一个函数进行编号,唯一确定一个函数。
1
2
3
4Typedef struct _IMAGE_Export_Ordinal_Table_
{
DWORD dwOrdinal;
}_IMAGE_Export_Ordinal_Table_;
根据导出函数表定位函数内存地址的方法:- 从AddressOfNames中获取到需要定位的函数的名字(记下函数名的索引)
- 从AddressOfNameOrdinals中获取到该函数的编号(以索引定位)
- 从AddressOfFunctions中获取该编号对应函数的地址(编号值就是数组索引值)
- Image Export Directory:导出目录表,其起始地址保存在NT HEADER可选头的第一个属性之中便于获取
- .rsrc:资源节,存放图标、对话框等程序需要用到的资源。树形结构,有一个主目录,下有嵌套子目录或数据。Windows通常有3层目录(资源类型、资源标识符、资源语言),第4层是具体的资源。具体结构不做要求。
- .reloc:重定位节,存放了一个重定位表。若装载器不是把程序装到程序编译时默认的基地址时,就需要这个重定位表来做一些调整。
练习题
由于本章内容多为记忆内容,这里只给出少数需要计算的例题供参考。
1. 一个exe文件的Image Base=0x400000,Address of Entry Point=0x1000,那么该程序的第一条指令在内存中的地址为________。
2. 在导入名称表(INT)中有一个指针的值为0x20A4,这个指针在________(填“内存”或“磁盘”)中的有效,已知IAT的Raw Data Address=0x800,RVA=0x1800,则0x20A4指向的内存地址在磁盘中的原像为________。
3. exe文件本身也属于文件,要想找到一个exe文件的某个导入函数的内存地址,首先应该在exe文件中找到可选头中存放的_______________,通过这个来定位到_________的地址,在这里可以通过遍历所有结构的________________字段来获取到这个函数的索引,接着在_____________________中找到这个索引下的地址值,即为目标函数在内存中的地址。
4. 下面是一个PE文件的头部数据,据此回答下列问题:
1 | 0x000 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 |
(1) 最开头两个字节表示的字符是_______,这是PE文件的____________结构。(4分)
(2) NT HEADER的起始地址为_______,你是通过__________(填16进制地址)的值得到的。(4分)
(3) PE文件头的标志在_______(填16进制地址)处,代表的字符为________。(4分)
(4) 这个PE文件有______个节,通过______(填16进制地址)处的值可以知道。(4分)
(5) 这个PE文件的Image Base为___________,通过______(填16进制地址)处的值可以知道。(4分)
(6) 这个PE文件的Address of Entry Point为___________,通过______(填16进制地址)处的值可以知道。(4分)
(7) 导入表的RVA是__________,通过______(填16进制地址)处的值可以知道。(4分)
5. 阅读某PE文件中.rdata节的IMAGE_SECTION_HEADER,回答下列问题:
1 | IMAGE_SECTION_HEADER .rdata |
已知该PE文件的Image Base=0x400000,导入表的RVA=0xED70
(1) .rdata节的RVA=,通过(填16进制地址)处的值可以知道。(4分)
(2) .rdata节在磁盘中的文件偏移为______,通过______(填16进制地址)处的值可以知道。(4分)
(3) 导入表在磁盘中的文件偏移应为_________。(3分)
答案:
-
0x401000
-
内存;0x10A4
-
IDT的RVA;IDT;OriginalFirstThunk;IAT
-
(1) MZ;DOS头
(2) 0x100;0x3C
(3) 0x100;PE
(4) 5;0x106
(5) 0x400000;0x134
(6) 0x80A2;0x128
(7) 0xED70;0x180 -
(1) 0xA000;0x22C
(2) 0x9000;0x234
(3) 0xDD70(.rdata节的VA=0x400000+0xA000=0x40A000,IDT的VA-IDT的磁盘偏移=.rdata的VA-.rdata的磁盘偏移,故IDT的磁盘偏移=IDT的VA-(0x40A000-0x900)=0x400000+0xED70-0x401000=0xDD70)