最近听别人讲的我晕晕乎乎的,于是上网上百度下,感觉这篇还不错。  链接:http://www.blogfshare.com/pe-export.html

一、导入表简介

在编程中常常用到“导入函数”(Import functions),导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或者多个DLL中,在调用者程序中只保留一些函数信息,包括函数名及其驻留的DLL名等。

于磁盘上的PE 文件来说,它无法得知这些输入函数在内存中的地址,只有当PE 文件被装入内存后,Windows 加载器才将相关DLL 装入,并将调用输入函数的指令和函数实际所处的地址联系起来。这就是“动态链接”的概念。动态链接是通过PE 文件中定义的“导入表”来完成的,导入表中保存的正是函数名和其驻留的DLL 名等。

1.调用导入函数的指令

程序被执行的时候是怎样使用导入函数的呢?我们来对一个简单的弹出一个MessageBox的程序反汇编一把,看看调用导入函数的指令都是什么样子的.

灰常简单的一个小程序,如图双击程序只显示一个对话窗口,然后就结束~试验用小程序,我们尽量的将内部的结构删减,调试起来才方便些。我们这次体验的目的就是想靠所学的知识,试图来找到MessageBox 在内存中的地址。
注:MessageBox 是来自于USER32.DLL 动态链接库里的一个函数,我们通过对PE 文件的静态反编译分析来观察hello.exe 这个试验品是如何定位和调用MessageBox 这个在USER32.dll的函数的。
(MessageBox 有两个版本,一个是MessageBoxA 还有一个是MessageBoxW 分别代表ASCII码形式和UNICODE~)

需要反汇编的两句源码如下:

invoke  MessageBox,NULL,offset szText,offset szCaption,MB_OK

invoke  ExitProcess,NULL

我们直接对其反汇编:

反汇编后,对MessageBox和ExitProcess函数的调用变成了对0040101A和00401020地址的调用,但是这两个地址显然是位于程序自身模块而不是DLL模块中,实际上,这是由编译器在程序所有代码的后面自动加上的Jmp dword ptr[xxxxxxxx]类型的指令,这个指令时一个间接寻址的跳转指令,xxxxxxxx地址中存放的才是真正的导入函数的地址。在这个例子中,00402000地址处存放的就是ExitProcess函数的地址。

那在没有装载到内存前,PE文件中的00402000地址处的内容又是什么呢?我们来分析一下它的节表。

由于建议装入地址是00400000h,所以00402000地址实际上处于RVA为2000h的地方,再看看各个节的虚拟地址,可以发现2000h开始的地方位于.rdata节内,而这个节的Raw_偏移为600h,也就是00402000h的内容实际上对应于PE文件中偏移600h处的数据。

我们再来看看文件0600h处的内容是什么:

查看的结果是0002076h,这显然不是内存中的ExitProcess函数的地址。不过,我们将它作为RVA看会怎么样?RVA地址00002076h也处于.rdata节内,减去节的其实地址00002000h后得到这个RVA相对于节首的偏移是76h,也就是对应文件0676h开始的地方,接下来会惊奇地发现,0676h再过去两个字节的内容正是函数名字符串“ExitProcess”!

是不是感觉有点不对?

如果我告诉你,当PE文件被装载的时候,Windows装载器会根据xxxxxxxx处的RVA得到函数名,再根据函数名在内存中找到函数地址,并且用函数地址将xxxxxxx处的内容替换成真正的函数地址,那么所有的疑惑就迎刃而解了。接下来看看如何获取导入表的位置。

2.获取导入表的位置

导入表的位置和大小可以从PE文件中IMAGE_OPTIONAL_HEADER32结构的数据目录字段中获取。从IMAGE_OPTIONAL_DIRECTORY结构的VirtualAddress字段得到的是导入表的RVA值,如果在内存中查找导入表,那么将RVA值加上PE文件装入的基址就是实际的地址,如果在PE文件中查找导入表,那么需要使用上一篇中讲的将RVA转换成文件偏移的方法进行转换。

二、导入表的结构

1.PE文件中的导入表

导入表由一系列的IMAGE_IMPORT_DESCRIPTOR结构组成,结构的数量取决于程序要使用的DLL文件的数量,每一个结构对应一个DLL文件,在所有这些结构的最后,由一个内容全为0的IMAGE_IMPORT_DESCRIPTOR结构作为结束。

IMAGE_IMPORT_DESCRIPTOR结构的定义:

IMAGE_IMPORT_DESCRIPTOR STRUCT 
union 
    Characteristics              DWORD   ? 
    OriginalFirstThunk        DWORD   ? 
ends 
TimeDateStamp                     DWORD   ? 
ForwarderChain                     DWORD   ? 
Name1                                    DWORD   ? 
FirstThunk                            DWORD   ?
IMAGE_IMPORT_DESCRIPTOR ENDS

①Name1

它表示DLL 名称的相对虚地址(译注:相对一个用null作为结束符的ASCII字符串的一个RVA,该字符串是该导入DLL文件的名称,如:KERNEL32.DLL)。

②OriginalFirstThunk和FirstThunk

现在可以看成是相同的(现在),它们都指向一个包含一系列IMAGE_THUNK_DATA结构的数组,数组中的每个IMAGE_THUNK_DATA结构定义了一个导入函数的信息,数组最后以一个内容为0的IMAGE_THUNK_DATA结构作为结束。

一个IMAGE_THUNK_DATA结构实际上就是一个双字,之所以把它定义成结构,是因为它在不同时刻有不同的含义:

IMAGE_THUNK_DATA STRUC
union u1
ForwarderString       DWORD  ?        ; 指向一个转向者字符串的RVA
Function                      DWORD  ?        ; 被输入的函数的内存地址
Ordinal                       DWORD  ?        ; 被输入的API 的序数值
AddressOfData         DWORD  ?        ; 指向 IMAGE_IMPORT_BY_NAME
ends
IMAGE_THUNK_DATA ENDS

当 IMAGE_THUNK_DATA 值的最高位为 1时,表示函数以序号方式输入,这时候低 31位被看作一个函数序号。(读者可以用预定义值IMAGE_ORDINAL_FLAG32或80000000h来对最高位进行测试)
当 IMAGE_THUNK_DATA 值的最高位为 0时,表示函数以字符串类型的函数名方式输入,这时双字的值是一个 RVA,指向一IMAGE_IMPORT_BY_NAME 结构。

IMAGE_IMPORT_BY_NAME STRUCT
Hint        WORD    ? 
Name1      BYTE      ?
IMAGE_IMPORT_BY_NAME ENDS

结构中的 Hint 字段也表示函数的序号,不过这个字段是可选的,有些编译器总是将它设置为 0,Name1字段定义了导入函数的名称字符串,这是一个以 0 为结尾的字符串。

我们来看一个例子:

2.内存中的导入表

为什么需要两个一样的IMAGE_THUNK_DATA 数组呢?

当PE文件被装入内存的时候,其中一个数组的值将被改作他用,正如上面分析的,Windows装载器会将指令Jmp dword ptr[xxxxxxxx]指定的xxxxxxxx处的RVA替换成真正的函数地址,其实xxxxxxx地址正是FirstThunk字段指向的那个数组的一员。

实际上,当PE文件被装入内存后,内存中的映象就被Windows装载器修正成了下图的样子,其中由FirstThunk字段指向的那个数组中的每个双字都被替换成了真正的函数入口地址,之所以在PE文件中使用两份IMAGE_THUNK_DATA 数组的拷贝并修改其中的一份,是为了最后还可以留下一份拷贝用来反过来查询地址所对应的导入函数名。

3.导入地址表(IAT)

暂把上面FirstThunk指向的真正导入函数地址数组称为导入地址数组。在PE文件中,所有DLL对应的导入地址数组是被排列在一起的,全部这些数组的组合也被称为导入地址表(Import Address Table),导入表中第一个IMAGE_IMPORT_DESCRIPTOR结构的FirstThunk字段指向的就是IAT的起始地址。也可以通过数据目录表的第13项找到IAT数据块的位置和大小。

《初识PE》导入表的更多相关文章

  1. PE文件结构详解(四)PE导入表

    PE文件结构详解(二)可执行文件头的最后展示了一个数组,PE文件结构详解(三)PE导出表中解释了其中第一项的格式,本篇文章来揭示这个数组中的第二项:IMAGE_DIRECTORY_ENTRY_IMPO ...

  2. 逆向-PE导入表

    导入表 动态链接库需要导入表 结构 typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; // 0 for ...

  3. PE导入表分析

    A.dll 导入 B.dll 导出函数 A.dll 表内容 这个结构指向的B导出函数的地址 Hook这个位置 等同于 Hook B.dll导出函数

  4. PE文件结构详解(五)延迟导入表

    PE文件结构详解(四)PE导入表讲 了一般的PE导入表,这次我们来看一下另外一种导入表:延迟导入(Delay Import).看名字就知道,这种导入机制导入其他DLL的时机比较“迟”,为什么要迟呢?因 ...

  5. 初识PE文件结构

    前言 目前网络上有关PE文件结构说明的文章太多了,自己的这篇文章只是单纯的记录自己对PE文件结构的学习.理解和总结. 基础概念 PE(Portable Executable:可移植的执行体)是Win3 ...

  6. 手动添加导入表修改EXE功能

    目标: 改动PE导入表,手工给HelloWorld增加一个功能,就是启动的时候写入一条开机启动项,C:\cmd0000000000000000000000000000.exe 实现方法: 直接在注册相 ...

  7. 利用模块加载回调函数修改PE导入表实现注入

    最近整理PE文件相关代码的时候,想到如果能在PE刚刚读进内存的时候再去修改内存PE镜像,那不是比直接对PE文件进行操作隐秘多了么? PE文件在运行时会根据导入表来进行dll库的"动态链接&q ...

  8. 小甲鱼PE详解之输入表(导入表)详解(PE详解07)

    捷径并不是把弯路改直了,而是帮你把岔道堵上! 走得弯路跟成长的速度是成正比的!不要害怕走上弯路,弯路会让你懂得更多,最终还是会在终点交汇! 岔路会将你引入万劫不复的深渊,并越走越深…… 在开始讲解输入 ...

  9. 利用PE数据目录的导入表获取函数名及其地址

    PE文件是以64字节的DOS文件头开始的(IMAGE_DOS_HEADER),接着是一段小DOS程序,然后是248字节的 NT文件头(IMAGE_NT_HEADERS),NT的文件头位置由IMAGE_ ...

随机推荐

  1. svn中“clean up”死循环问题解决办法

    SVN在使用update命令时,提示使用"clean up "命令,在使用clean up命令时报错"Previous operation has not finishe ...

  2. css 样式那些事

    1.      input placeholder 的颜色修改 ::-moz-placeholder { color: #f3d999; } ::-webkit-input-placeholder { ...

  3. HP Autonomy KeyView入门使用&Maven打包流程

    正文:使用自带的KeyView.jar可能报各种各样的错误,比如no manifest,找不到main方法等等,这里需要自己把需要用到的Filter类和KeyView.jar一起打包,并修改manif ...

  4. 关于ASCII,Unicode和UTF-8

    自己也不是很明白这些编码,百度了一下,整理出来与大家分享分享,在此感谢作者. 先说说这些编码 ANSI:最早的时候计算机ASCII码只能表示256个符号(含控制符号),这个字符集表示英文字母足够,其中 ...

  5. linux下安装nginx+php

    参考:http://blog.csdn.net/ihelloworld/article/details/7029796 http://blog.chinaunix.net/uid-21374062-i ...

  6. MySQL字段自增自减的SQL语句

    MySQL的自增语句大家应该都很熟悉 也很简单 update `info` set `comments` = `comments`+1 WHERE `id` = 32 这样就可以了,但是有时候我们会涉 ...

  7. Linux后门账户控制

    赋予用户sudo权限 vi /etc/sudoers 添加如下一行: USER ALL=(ALL) NOPASSWD: ALL (实现当前用户允许转换成任意用户及执行任意命令) 添加root权限账户 ...

  8. 第七十五节,CSS表格与列表

    CSS表格与列表 学习要点: 1.表格样式 2.列表样式 3.其他功能 一.表格样式 表格有五种独有样式,样式表如下:             属性               值           ...

  9. MongoDB数据模型(二)

    原文地址 接上一篇 四.模型树结构 父引用的模型树结构 这个数据模型描述了一个树形结构,在子节点中存储父节点的引用. 模式 父引用模式存储每个树节点到文档中,除了树节点外,文档还存储了父节点的id. ...

  10. cmusphinx格式问题

    在windows下.lm和.dict同时为ANSI编码,输出正确,否则输出乱码或不输出结果.