理解PE格式—找出导出表(Export Table)中的函数地址(理论篇)

PE的结构,参考文档https://docs.microsoft.com/en-us/windows/win32/debug/pe-format的目录:

根据这个结构来找kernel32.dll中的函数名和函数名的RVA。

PE signature

文件 0x3c 的地方指定了PE signature的位置(也就是NT_Header,加密解密里说NT_Header,微软的文档里没有这么一说) E8

E8的前四个字节是 PE00,在这之后是 COFF file header

COFF file header

紧接着PE signature的2个Byte(Machine) 判断PE文件时运行在什么平台:

COFF file header + 2 处的2个byte指定了这个PE文件有多少个Section : 06
用010Editor查看也是6个Section

COFF file header + 16 处的两byte指定了OptionalHeader的大小 : F0

COFF file header + 20 处是 OptionalHeader 的开始位置:100h. 大小F0.

也就是100h – 1F0h之间全是 OptionalHeader

OptionalHeader

OptionalHeader 的前两个字节判断了这是个PE文件还是个PE+文件

本次是20b ,是在64位平台的pe+格式。
OptionalHeader 分三部分:

Standard fields , Windows-specific fields 和 Data directories。

Standard fields

100h – 118h 是 Standard fields部分

在PE32中还有额外的字段BaseOfData。

Windows-specific fields

118h – 12Eh 是 Windows-Specific Fields部分

SizeOfHeaders 在100h + 60 = 13Ch的位置 :400h
400 h就是File Header 和 Section Table(Section Header)的总大小
即 400h开始部分就是Section Data部分

Data directories

因此PE signature的位置开始+4(PE signature的长度)+20(Standard fields的长度)+96(Export Table的偏移)= +120 = +78h 的地方就是Export Table相对PE signature的偏移。
Export Table 里指向了Export Table的地址和大小,100h+112=100+70h=170h

Address: 90170h
Size:DD40h=56640
由于这个地址是RVA 相对虚址。在文件上查看还要算出 内存偏移 和 文件偏移的差值△k

Section Table(Section Header)

从100h + 232 + 8 = 1F0 开始是Section Table(Section Header) 的起始位置。
按照之前的计算,一共有6个Section,所以有6个Section Header,每个的格式如下:

每个Secion Header 的大小是40 = 28h。 Secion Header的总大小是: 6*40=240=F0h

第一个Section(.text), 微软用这个名字的Section来存代码。

VirtualAddress:1000h
Pe被加载到内存,这部分Section在相对 image base偏移1000h的位置
VirtualSize:7529Ch
在内存中的地址范围是1000h — 7629Ch
PointerToRawData:400h
PE在磁盘上当文件存放时,这部分Section在相对 image base偏移400h的位置。
内存偏移 和 文件偏移的差值△k1= 1000h – 400h = C00h

第二个Section(.rdata)

VirtualAddress:77000h
VirtualSize:31BC6
在内存中的地址范围是77000h — A8BC6h
这里第二个Section从77000h开始是因为内存对齐了。
PointerToRawData:75800h
△k2=77000h-75800=1800h

因此,之前找到的Export Table的RVA(90170h)就在.rdata的Section Data中。
算出了以上数据,即可以通过以上数据找到Export Table在文件上的偏移位置:
90170h-△k2=90170h-1800h=8E970h
8E970h 开始就是The export data section

Section Data

The export data section 分为5个表:

我只关心第一个表:Export Directory Table。
Export Directory Table表结构:

文件上的数据:

Ordinal Base 指定了序号从多少开始:01h
Address Table Entries 指定了有多少个Function: 65D

Export Address Table RVA : 90198h-1800h=8E998h

Name Pointer RVA 指定了Function Name的地址:91B0Ch-1800h=9030Ch
Ordinal Table RVA 指定了函数的顺序表的地址:93480h-1800h=91C80‬h

查看Ordinal Table :

这是一个数组,一个元素2字节。
表中序号从0开始,加上Ordinal Base的值(1),所以00 00 代表了序数是1。

查看Name Pointer RVA

这是一个数组,一个元素4个字节。
根据地址查看第一个函数名称:
94147h-1800h=92947h

00 代表结束。可见 函数是从A开始排列的

查看Export Address Table RVA

这个地址有可能是个内存地址,有可能是个其他dll的函数名称。

Name Pointer 和Ordinal Table 这两个是平行的数组。
比如两个数组的第五个元素是对应的,通过Name Pointer的第五个元素指向的地址可以知道这个函数的函数名,通过Ordinal Table 的第五个元素指向的数字(假如是20),再去Export Address Table 查第(20 – Ordinal Base)个元素的地址,就可以查到指定函数的名称和地址。
实际在内存中的地址还要加上基址。

绝知此事要躬行,实践篇:

Leave a Reply

Your email address will not be published. Required fields are marked *