PE结构&导入表

PE体系

PE结构&整体叙述

PE结构&导入表

PE结构&导出表

PE结构&基址重定位表

PE结构&绑定导入实现

PE结构&延迟加载导入表

导入表简介

当我们源文件里面需要去如何画窗口,如何显示指定字符串这样功能的代码,只需要简单调用Windows API函数。这些调用的函数在源文件中并不存在。这些代码存储在DLL文件中,即动态链接库中。在动态链接库里存放的不是函数的源代码,而是编译链接后生成的字节码。
源程序调用了动态链接库的相关函数,在进行编译和链接的时候,编译程序和链接程序就会把调用的相关信息写入最终生成的PE文件中,从而来告诉操作系统这些函数的执行字节码能从哪里获取,这些信息就是导入表所要描述的内容

DLL加载方式有两种:显式链接(Explicit Linking) 和 隐式链接(Implicit Linking)

  1. 显示链接:程序在使用DLL时进行加载,使用完毕后释放内存
  2. 隐式链接:程序在开始时即一同加载DLL,程序终止时再释放占用的内存

IAT提供的机制与DLL的隐式链接有关。

优点:

  1. 不把函数库包含进应用程序中,单独组成DLL文件,在需要使用时再进行调用。
  2. 使用内存映射技术将加载后的DLL代码、资源在多个进程中实现共享。
  3. 在对函数库进行更新时,只更新DLL文件即可。

导入函数

当我们调用一个API时,这个API我们不可能自己手写,基础源码这些东西都不知道,所以直接导入头文件然后使用即可。

#include<iostream>
#include<Windows.h>
using namespace std;
int main() {
	LPCSTR text = "hello world";
	LPCSTR title = "第一个MessageBoxA";
	MessageBoxA(NULL, text, title, MB_OK);

}

关于API调用的汇编代码如下:

.text:00401516 mov     [ebp+text], offset aHelloWorld ; "hello world"
.text:0040151D mov     [ebp+title], offset unk_48800C
.text:00401524 mov     dword ptr [esp+0Ch], 0 ; uType
.text:0040152C mov     eax, [ebp+title]
.text:0040152F mov     [esp+8], eax    ; lpCaption
.text:00401533 mov     eax, [ebp+text]
.text:00401536 mov     [esp+4], eax    ; lpText
.text:0040153A mov     dword ptr [esp], 0 ; hWnd
.text:00401541 mov     eax, ds:__imp__MessageBoxA@16 ; MessageBoxA(x,x,x,x)
.text:00401546 call    eax ; MessageBoxA(x,x,x,x) ; MessageBoxA(x,x,

关于导入函数就两行:

.text:00401541 mov     eax, ds:__imp__MessageBoxA@16 ; MessageBoxA(x,x,x,x)
.text:00401546 call    eax ; MessageBoxA(x,x,x,x) ; MessageBoxA(x,x,

也就是把一个内存单元里面存的地址放在eax里面去,然后call它。即MessageBoxA的实际地址被放在一个偏移处,查看这个偏移
在这里插入图片描述
地址0x004924C0存放着0x77211930MessageBoxA的实际地址)
0x004924C0内存单元开始取dd长度的内存数据放在eax里面,
然后直接 call 0x77211930

查看一下文件中0x004924C0中存放的数据:
在这里插入图片描述
它存放的是0x61562100

查看一下内存中0x004924C0中存放的数据:
在这里插入图片描述
它存放的是0x77211930
这意味着,文件被装载到内存后,这里的值发生了变化,真正的API函数地址的应该是内存中此处的值。那文件中的值0x61562100和内存映像中的值0x77211930它俩有什么关系呢?

注意

dump程序时,

错误示范:

在这里插入图片描述
这样操作后,你得到的文件是一个在这里插入图片描述

dmp文件,然后我找了三个小时。。啥也没找到。。。

正确示范:

在这里插入图片描述
这样dump后你得到的才是一个exe程序
在这里插入图片描述
文件偏移和虚拟地址转换才用得上。。。以前dump的时候都是直接用的是PEtools,今晚不知道脑子哪里抽筋,偏偏乱搞了三小时。。。

观察一下文件数据,导入表中数据
在这里插入图片描述
RVA数值为0x92000,大小为0xEE8

核心内容

在这里插入图片描述
导入表数据所在地址RVA=0x92000
导入表数据大小=0xEE8
导入函数数据地址表所在地址RVA=0x9228C
导入地址表表数据大小=0x23C
在这里插入图片描述
转换成文件偏移后是:
导入表数据所在地址0x8F200,导入表数据大小=0xEE8
导入地址表数据所在地址0x8F48C,导入地址表表数据大小=0x23C

导入表描述符IMAGE_IMPORT_DESCRIPTOR

导入表数据的起始是一组导入表描述符结构。每组为20个字节,该结构定义如下:

IMAGE_IMPORT_DESCRIPTOR	STRUCT
union
	Characteristics					dd
	OriginalFirstThunk				dd	;000h	-1
ends	
TimeDateStamp						dd	;0004h	-	时间表
ForwarderChain						dd	;0008h	-	链表的前一个结构
Name1								dd	;000ch	-	指向链接库名字的指针
FirstThunk							dd	;0010	-2

以下作出解释:

IMAGE_IMPORT_DESCRIPTOR.OriginalFirstThunk:

+0000h,双字。因为它是指向另外数据结构的通路,因此简称桥1。该字段指向一个包含了一系列结构的数据。
指向的数组中每个结构定义了一个导入函数的信息,最后一个内容为全0的结构作为结束。指向的数据中每一项为一个结构,此结构的名称是IMAGE_THUNK_DATA。该结构实际上只是一个双字,但在不同时刻却拥有不同的解释。该字段有两种解释:

  1. 双字最高位为0,表示导入符号是一个数值,该数值是一个RVA。
  2. 双字最高位为1,表示导入符号是一个名称

IMAGE_IMPORT_DESCRIPTOR.TimeDateStamp:

+0004h,双字。时间戳,一般不用。如果该导入表被绑定,那么绑定后的这个时间戳就被设置为对应DLL文件的时间戳。操作系统在加载时,可以通过这个时间戳来判断绑定的信息是否过时

IMAGE_IMPORT_DESCRIPTOR.ForwarderChain:

+0008h,双字。链表的前一个结构

IMAGE_IMPORT_DESCRIPTOR.Name1:

+000ch,双字。这个字段的含义和名称并不一致,这里的Name1是一个RVA,它指向该结构对应的DLL文件的名称,而这个名称是以“\0”结尾的Ansi字符串。

IMAGE_IMPORT_DESCRIPTOR.FirstThunk :

+0010h,双字。与OriginalFirstThunk相同,它指向的链表定义了针对Name1这个动态链接库引入的所有导入函数,简称桥2

导入表双桥结构

重新找个程序举例,上面那个数据太大了,头疼。。。
在这里插入图片描述

在这里插入图片描述
导入表数据所在地址RVA=0x1B1C4
导入表数据大小=0x50
导入函数数据地址表所在地址RVA=0x1B000
导入地址表表数据大小=0x1C4

转换成文件偏移后是:

导入表数据所在地址0x83C4,导入表数据大小=0x50
导入地址表数据所在地址0x8200,导入地址表表数据大小=0x1C4
在这里插入图片描述

在这里插入图片描述

桥1和桥2最终都通向了一个目的地,都指向了引入函数的“编号-名称”(Hint/Name)描述部分。而从桥2到目的地的过程中,还经过了另外一个很重要的结构IAT(Import Address Table)
在这里插入图片描述

AC B2 01 00

桥1,最高位为0,这是一个RVA,表明函数是以字符串类型的函数名导入的。RVA转换为FOA,值为
0x84AC,从文件的该位置开始取双字,直到取出的双字为"0"结束,每一个双字都是结构IMAGE_THUNK_DATA。该结构的详细定义如下


IMAGE_THUNK_DATA STRUCT
	union	u1
		ForwarderString		dd?
		Function			dd?
		Ordinal				dd?
		AddressOfData		dd?
	ends
IMAGE_THUNK_DATA	ENDS

在这里插入图片描述

0x0001B450转换成文件偏移为0x8650
在这里插入图片描述

2E	00	5F	5F	76	63	72	74	5F	47	65	74	4D	6F	64	75	6C	65	46	69	6C	65	4E	61	6D	65	57	00

这些值组成的数据结构就是IMAGE_IMPORT_BY_NAME

IMAGE_IMPORT_BY_NAME STRUCT
	Hint	dw	?;0000h	-函数编号
	Name1	db	?;0004h	-表示函数名的字符串
	IMAGE_IMPORT_BY_NAME ENDS

以下是对每个字段的具体解释:

IMAGE_IMPORT_BY_NAME.Hint:
+0000h,双字。函数的编号,在DLL中对每个函数进行了编号,访问函数时可以通过名称访问,也可以通过编号访问

IMAGE_IMPORT_BY_NAME.Name1:
+0004h,大小不确定。函数名字字符串的具体内容,以“\0”作为字符串结束标志。
其中002E标识该函数在动态链接库的编号,后面紧跟着函数名“GetModuleFileNameW”。

00 00 00 00

时间戳,这里为0

00 00 00 00

链表的前一个结构,这里为0

9E	B4	01	00

RVA,指向动态链接库RUNTIME140D.dll的名字字符串,转换为文件偏移是0x869E
在这里插入图片描述

98	B0	01	00

桥二,转换成文件偏移是0x8298
在这里插入图片描述
在这里插入图片描述

根据上面可知IAT0x8200~0x83C4,桥2指向的地方在IAT范围内,所以指向IAT
在这里插入图片描述
和桥1指向的数据值相同。。但是存储的位置是不同的。桥1指向的INT与桥2指向的IAT内容完全一样,但INT和IAT却存储在文件的不同位置。

每一个结构IMAGE_IMPORT_DESCRIPORT都对应一个唯一的动态链接库,以及引用了该动态链接库的多个函数,每个函数的最终“值-名称”描述均可以沿桥1或者桥2找到。这种导入表结构被称为双外结构。
双桥结构的导入表在文件中存在两份内容完全相同的地址列表。一般情况下,桥2指向的地址列表被定义为IAT,而桥1指向的地址列表则被定义为INT(import name table)。

注意:
有的链接程序只只为导入表存储一个桥,如Borland公司的Tlink只保留桥2,这样的导入表我们称之为单桥结构的导入表(单桥结构的导入表是无法指向绑定导入操作的。)

文件中的导入表:

在这里插入图片描述

在文件中,桥1指向INT,桥2指向IAT,内容一致。
在这里插入图片描述

内存中的导入表:

在这里插入图片描述
在内存中,桥1可以找到调用的函数名称或函数的索引编号,桥2却可以帮你找到该函数指令代码在内存空间中的地址。
当PE被加载进虚拟地址空间以后,IAT 的内容会被操作系统更改为函数的VA。这个修改最终会导致通向“值-名称”描述的桥2发生断裂,如上图。

当桥2发送断裂以后,如果没有桥1作为参照(因为桥1和桥2维护了两个一一对应的函数RVA),我们就无法重新找到该地址到底是调用了那个函数。这就是为什么会存在两个桥的原因。也是为什么单桥导入表无法实施绑定的原因。

实际操作

内存中的导入表:
在这里插入图片描述
桥1的值为01B2AC
在这里插入图片描述

桥2的IAT 值为0x01B098
在这里插入图片描述

两个函数实际地址值和函数名(函数编号)可以一一对应。。。

PE装载器把导入函数输入至IAT的顺序

  1. 读取IID的Name成员,获取库名称字符串(eg:kernel32.dll)
  2. 装载相应库:
    LoadLibrary(“kernel32.dll”)
  3. 读取IID的OriginalFirstThunk成员,获取INT地址
  4. 逐一读取INT中数组的值,获取相应IMAGE_IMPORT_BY_NAME地址(RVA)
  5. 使用IMAGE_IMPORT_BY_NAME的Hint(ordinal)或Name项,获取相应函数的起始地址:GetProcAddress(“GetCurrentThreadld”)
  6. 读取IID的FirstThunk(IAT)成员,获得IAT地址
  7. 将上面获得的函数地址输入相应IAT数组值
  8. 重复以上步骤4~7,知道INT结束(遇到NULL)

同一个DLL文件的多个函数的导入表

在这里插入图片描述
这是文件中的导入表,上图中FirstThunk和OriganalFirstThunk一样也有一份函数指针表,即桥2.只是没画而已下面是装载入内存后的导入表:
在这里插入图片描述
定位导入函数地址表的方法有两种:

  1. 从导入表的而最后一个导入表项## IMAGE_IMPORT_DESCRIPTOR结构中的字段IMAGE_IMPORT_DESCRIPTOR.FirstThunk定位IAT
  2. 通过数据目录第13个数据项的描述直接定位IAT

每个动态链接库都维护了自己的IAT内容,不同的 链接库维护的这些内容可以是不连续的。

导入表的平面解析
在这里插入图片描述
解释如下:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寻梦&之璐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值