PE(64位)文件之导入表

导入表是PE文件中一个重要的表项,负责声明从其他的库中调入函数。一般代表着这个PE文件使用了哪些其他库的函数。

首先我们先了解下几个名词:

PE文件:PE结构的文件,一般为可执行程序,.exe、.dll、.sys、.vxd.ocx、.com等。

VA (Virtual Address): 虚拟地址,代表着PE文件映射进内存之后的地址。

RVA (Reverse Virtual Address)相对虚拟地址,对于PE文件映射进内存之后相对基地址偏移。

FOA(File Offset Address)文件中的偏移(文件映射进内存后内存对其并不一定与文件中一致,因此ROA不一定等于RVA)。

接下来我们将通过PE文件格式去手动解析导入表的位置以及结构。

 

首先从文件起始开始解析为_IMAGE_DOS_HEADER结构,但实际上这个结构大多数都是没有使用的,主要是为了兼容很早以前的DOS。会使用到的数据结构有:e_magin若不等于”MZ”则代表不是一个DOS程序,随后e_lfanew代表着 _IMAGE_NT_HEADERS结构的FOA(E8)

 _IMAGE_NT_HEADERS结构:

 

Signature 为PE代表这是个PE程序。该结构中另含有_IMAGE_FILE_HEADER与_IMAGE_OPTIONAL_HEADER64 两个结构。

导入表与导入表都存放在IMAGE_OPTIONAL_HEADER64结构的DataDirectory数组中

IMAGE_OPTIONAL_HEADER64结构:

 

其中DataDirectory在结构中的偏移是0x88

 

对于这个数组的下标描述如下:

 

可以看到有 IMAGE_DIRECTORY_ENTRY_IMPORT,除此之外还有一个IMAGE_DIRECTORY_ENTRY_IAT,这个是IAT(导入地址表),仅在当程序被加载完成后才生效,里面存放的是每个函数的RVA。

IMAGE_DATA_DIRECTORY数组的类型结构:

 

注意这里给出的是VirutalAddress(VA)需要转换为FOA在文件中查看数据。

RVA2FOA的算法首先需要找到相应的段信息

 

可以看到.rdata段的虚拟地址范围是7f000~B2000

文件地址在0x7d800

而我们导入表的地址为0x0a6f8c:大小是0x0794的大小

 

RVA2FOA的算法:

RVA-段基址+段在文件中的基址获得FOA

0x0a6f8c - 0x7f000 + 0x7d800 = 0xA578C

接下来是一个_IMAGE_IMPORT_DESCRIPTOR 的数组

_IMAGE_IMPORT_DESCRIPTOR结构:

 

 

每一个_IMAGE_IMPORT_DESCRIPTOR结构描述一个DLL。

其中name是Dll的名称的RVA。

 

同样的RVA2FOA

 

 

可以看到这个DLL名字叫”api-ms-win-core-rtlsupport-l1-1-0.dll”

随后我们需要获得这个Dll中的其他函数的引用,其中就有OriginalFirstThunk的结构也是一个数组,结构体中说明这是一个IMAGE_THUNK_DATA结构体的RVA。

FirstThunk在文件中与OriginalFirstThunk一致,但如果加载入内存,并做好了初始化操作,FirstThunk指向的是IAT(IMAGE_DIRECTORY_ENTRY_IAT)一张导入函数地址表。

为了保证一致我们选择OriginalFirstThunk结构。这个结构指向IMAGE_THUNK_DATA的RVA(0x0A8F40)

 

IMAGE_THUNK_DATA结构:

 

 

可以看到这里似乎啥都有,但一定要注意这是个union共同体,它所描述的只能是其中的一个意思。

因为这个数据在不同的情况下是不同的数据,导入函数有函数名称和ID两种,在这里也就有两种不同的表达方式。Ordinal就是函数ID。而ForwarderString就是函数名称。

但是如何区分究竟是ID导入还是函数名称导入呢。主要看这个union的最高位,如果最高位为1,那么这个就是ID导入,取低2字节(0xffff)作为ID

 

但是大多数函数导入的方式都是函数名称,我们试着获得第一个函数的函数名称,在结构的注释中说明这个RVA实际指向的是PIMAGE_IMPORT_BY_NAME,我们解析下

 

同样的RVA2FOA

 

随后套用PIMAGE_IMPORT_BY_NAME结构体

 

 

可以看到0x02是Hint(实际上是函数ID),而后是一个变长字符数组,函数名称为”RtlCaptureContext”

加载器会通过和我们一样的方式去获取、加载这些DLL并且通过加载入的DLL中的导出表获得需要使用的函数地址,填入IAT使得建立了程序与DLL之间的调用对接。

PETOOLS的PE编辑器:

 

猜你喜欢

转载自blog.csdn.net/xuandao_ahfengren/article/details/112272745