RE October 07, 2020

PE文件结构详解

Words count 6.6k Reading time 6 mins. Read count 0

一、PE文件基础介绍

pe文件是windows操作系统下的可执行文件的一种称呼,常见的有exe文件和dll文件等,32位是PE32,64位的是PE+或者PE32+。

二、PE文件格式

1、PE文件种类如下表所示

种类 主扩展名
可执行系列 EXE, SCR
库系列 DLL, OCX, CPL, DRV
驱动程序系列 SYS, VXD
对象文件系列 OBJ

2、我们直接用010editer看下test.exe文件的情况

image-20201007123249413

往后还有3个节区,分别是.text、.rdata、.data这三个,其中dos头到节区头是我们熟知的pe文件头,后面的节区统称为pe文件体。

节区之间一般有null填充,文件/内存中节区的起始位置应该在各文件/内存最小单位的倍数上,在计算地址时有一条黄金公式:

虚拟地址=基地址+相对虚拟地址

按照自己的理解就是:真实地址=基地址+偏移地址

三、PE头

Dos头

考虑的是pe文件对于早期的dos文件进行兼容而存在的,其结构体为IMAGE_DOS_HEADER,大小是64字节(0x40),其中有2个重要的成员:

  1. e_magic: Dos签名(4D 5A, MZ)
  2. e_ifanew: 指示NT头的偏移(文件不同,值不同),表示从哪里开始

image-20201016153427726

Dos存根

stub,位于Dos头下方,可选,大小不固定,由代码和数据混合组成,非必要,可删除,或者用来合并。

image-20201016153614792

NT头

结构体为IMAGE_NT_HEADERS,大小为F8,由3个成员组成:

签名结构体,值为50450000h(“PE”00)

1、文件头,表现文件大致属性,结构体为IMAGE_FILE_HEADER,重要成员有4个:

Machine:每个cpu都拥有的唯一的Machine码,兼容32位Intel x86芯片的Machine码为14C;

NumberOfSections:指出文件中存在的节区数量;

SizeOfOptionalHeader:指出结构体IMAGE_OPTIONAL_HEADER32(32位系统)的长度

Characteristics:标识文件属性,文件是否是可运行形态、是否为DLL等,以bit OR形式进行组合

image-20201016155025545

image-20201016154830767

2、可选头,结构体为IMAGE_OPTIONAL_HAEDER32,重要成员有9个:

Magic: IMAGE_OPTIONAL_HAEDER32为10B,IMAGE_OPTIONAL_HAEDER64为20B。

image-20201016155636400

AddressOfEntryPoint: 持有EP的RVA,指出程序最先执行的代码起始地址的偏移地址

image-20201016155654904

image-20201016155452975

ImageBase:指出文件的优先装入地址(32位进程虚拟内存范围为:0~7FFFFFFF)(建议)

image-20201016155731951

SectionAlignment,FileAlignment:前者制定了节区在内存中的最小单位,后者制定了节区在磁盘文件中的最小单位,简单地理解就是内存对齐和文件对齐的大小

image-20201016155905358

主操作系统和子系统版本号:

image-20201016160138317

SizeOfImage:指定了PE Image在虚拟内存中所占空间的大小

image-20201016160209519

image-20201016160735748

SizeOfHeaders:指出整个PE头的大小

image-20201016160647687

Subsystem:区分系统驱动文件和普通可执行文件

image-20201016160841125

堆栈预备申请大小:

image-20201016160933211

NumberOfRvaAndSize:指定DataDirectory数组的个数,每8字节为一个数组单元

DataDirectory:由IMAGE_DATA_DIRECTORY结构体组成的数组

image-20201016161101640

sizeofcode这里是可以自己定义size大小,造成反调试,MajorSubsystemVersion不可改,前后的可以改,造成反调试

看看这个:导入表在内存中的相对虚拟地址0x2010,然后导入表的大小0x3c

image-20201016161513312

image-20201016161628572

再看看这个,导入地址表在内存中的相当虚拟地址以及它的大小:

image-20201016161757956

image-20201016161828551

3、节区头中定义了各节区的属性,包括不同的特性、访问权限等,结构体为IMAGE_SECTION_HEADER,重要成员有4个:

  • VirtualSize:内存中节区所占大小
  • VirtualAddress:内存中节区起始地址(RVA)
  • SizeOfRawData:磁盘文件中节区所占大小
  • Charateristics:节区属性(bit OR)

节名称其实是可以改变的,简称可利用

每2行半为一个节单位元,其中选中的是text段,解析为:从文件偏移为0x400的位置读取0x200的字节,映射到内存偏移为0x1000的位置,有效大小为0x40,剩下的3个以此类推。结尾那个值,只有低位有效,所以中间都是可改值。

image-20201016162222464

image-20201016162507237

image-20201016162205932

下面看下导入的表和函数调用在内存中的情况:

image-20201016200046879

每1.4行就是一个数据单元,例如从0x402010到0x402020,表示从相对虚拟地址为0x206a的位置(隔开一个word),得到name=kernel32.dll(库名),然后0x204c那里获取到0x205c,确认ExitProcess函数名,写到0x2000的偏移处,那么0x756258f0就是我们的ExitProcess的真实地址,我们检验下:

image-20201016200620410

没问题,再来一个,0x402024到0x402034,就是第二个数据单元,从0x2086处获取到user32.dll库名,然后从0x2054处获取到0x2078开始的MessageBoxA函数名,从而在0x2008处写入MessageBoxA真实地址,检验下:

image-20201016200935376

至此函数导入表和真实地址绑定都搞定了。

四、PE文件从磁盘到内存的映射:

  1. 查找RVA所在节区

  2. 使用简单的公式计算文件偏移:

    RAW - PointerToRawData = RVA - ImageBase

    RAW = RVA - ImageBase + PointerToRawData

example:ImageBase为0x10000000,节区为.text,文件中起始地址为0x00000400,内存中的起始地址为0x01001000,RVA = 5000,RAW = 5000 - 1000 + 400 = 4400。

起始本质就是偏移地址不变进行一个转换的运算工作

0%