0%

软件安全——第4章

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
      8
      IMAGE_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的结构体用于表示这部分结束。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      IMAGE_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
      OriginalFirstThunk是指向Import Name(lookup) Table中的指针
      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字节对齐。
  • .edata:导出函数节,本文件向其他程序提供调用函数的列表、函数所在地址和具体代码实现,多见于DLL文件。
    • Image Export Directory:导出目录表,其起始地址保存在NT HEADER可选头的第一个属性之中便于获取
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      IMAGE_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
      7
      Typedef 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
      4
      Typedef struct _IMAGE_Export_Name_Pointer_Table_
      {
      DWORD dwPointer;
      }IMAGE_Export_Name_Pointer_Table;
    • Export Ordinal Table:导出符号表,保存导出函数的编号。
      1
      2
      3
      4
      Typedef struct _IMAGE_Export_Ordinal_Table_
      {
      DWORD dwOrdinal;
      }_IMAGE_Export_Ordinal_Table_;
      注意:由于代码段中可能存在无名函数、重载函数等,因此导出函数的编号与导出函数名可能并不是一一对应,因此需要导出函数编号为每一个函数进行编号,唯一确定一个函数。
      根据导出函数表定位函数内存地址的方法:
      • 从AddressOfNames中获取到需要定位的函数的名字(记下函数名的索引)
      • 从AddressOfNameOrdinals中获取到该函数的编号(以索引定位)
      • 从AddressOfFunctions中获取该编号对应函数的地址(编号值就是数组索引值)
  • .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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
0x000 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00
0x010 B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00
0x020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x030 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00
0x040 0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 68
0x050 69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E 6F
0x060 74 20 62 65 20 72 75 6E 20 69 6E 20 44 4F 53 20
0x070 6D 6F 64 65 2E 0D 0D 0A 24 00 00 00 00 00 00 00
0x080 9F 32 0F AA DB 53 61 F9 DB 53 61 F9 DB 53 61 F9
0x090 D2 2B F2 F9 D7 53 61 F9 CF 38 60 F8 D3 53 61 F9
0x0A0 B7 27 65 F8 D6 53 61 F9 B7 27 62 F8 DF 53 61 F9
0x0B0 B7 27 64 F8 FB 53 61 F9 DB 53 60 F9 FF 52 61 F9
0x0C0 B7 27 60 F8 DC 53 61 F9 0D 27 64 F8 DF 53 61 F9
0x0D0 0D 27 65 F8 DA 53 61 F9 0D 27 9E F9 DA 53 61 F9
0x0E0 DB 53 F6 F9 DA 53 61 F9 0D 27 63 F8 DA 53 61 F9
0x0F0 52 69 63 68 DB 53 61 F9 00 00 00 00 00 00 00 00
0x100 50 45 00 00 4C 01 05 00 9B C0 42 62 00 00 00 00
0x110 00 00 00 00 E0 00 02 01 0B 01 0E 1C 00 8C 00 00
0x120 00 98 00 00 00 00 00 00 A2 80 00 00 00 10 00 00
0x130 00 A0 00 00 00 00 40 00 00 10 00 00 00 02 00 00
0x140 06 00 00 00 00 00 00 00 06 00 00 00 00 00 00 00
0x150 00 60 01 00 00 04 00 00 00 00 00 00 02 00 40 81
0x160 00 00 10 00 00 10 00 00 00 00 10 00 00 10 00 00
0x170 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00
0x180 70 ED 00 00 18 01 00 00 00 20 01 00 B8 13 00 00
0x190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1A0 00 40 01 00 50 13 00 00 CC DD 00 00 54 00 00 00
0x1B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1C0 08 DF 00 00 18 00 00 00 20 DE 00 00 40 00 00 00
0x1D0 00 00 00 00 00 00 00 00 00 A0 00 00 C4 04 00 00
0x1E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1F0 00 00 00 00 00 00 00 00 2E 74 65 78 74 00 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
2
3
4
IMAGE_SECTION_HEADER .rdata
0x220 2E 72 64 61 74 61 00 00 A2 5C 00 00 00 A0 00 00
0x230 00 5E 00 00 00 90 00 00 00 00 00 00 00 00 00 00
0x240 00 00 00 00 40 00 00 40

已知该PE文件的Image Base=0x400000,导入表的RVA=0xED70
(1) .rdata节的RVA=,通过(填16进制地址)处的值可以知道。(4分)
(2) .rdata节在磁盘中的文件偏移为
______,通过______(填16进制地址)处的值可以知道。(4分)
(3) 导入表在磁盘中的文件偏移应为_________。(3分)

答案:

  1. 0x401000

  2. 内存;0x10A4

  3. IDT的RVA;IDT;OriginalFirstThunk;IAT

  4. (1) MZ;DOS头
    (2) 0x100;0x3C
    (3) 0x100;PE
    (4) 5;0x106
    (5) 0x400000;0x134
    (6) 0x80A2;0x128
    (7) 0xED70;0x180

  5. (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)