news 2026/11/3 9:53:12

PE文件之导入表(一):导入函数调用机制、导入表基本结构

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PE文件之导入表(一):导入函数调用机制、导入表基本结构

PE文件之导入表(一)::导入函数调用机制、导入表基本结构

导入表是PE数据组织中的一个很重要的组成部分,它是为实现代码重用而设置的。Windows加载器在运行PE时会将导入表中声明的动态链接库一并加载到进程的地址空间,并修正指令代码中调用的函数地址。

数据目录中一共有四种类型的数据与导入表有关:

  1. 导入表
  2. 导入函数地址表
  3. 绑定地址表
  4. 延迟加载导入表

当程序调用了动态链接库的相关函数,在进行编译和链接的时候,编译程序和链接程序就会将调用的相关信息写入最终生成的PE文件中,以告诉操作系统这些函数的执行指令字节码从哪里能够获取。这些信息就是导入表所要描述的内容。

我们先来看看导入表是如何使用的。

导入函数

可以看到,我们的程序中invoke了两个外部函数(它们不在HelloWor模块中,而在其它可执行模块中),那么,我们的程序是如何能够找到正确的函数地址呢?我们先来看看硬盘中的PE文件中存储了怎样的调用代码:

**对函数的调用,反汇编之后是先用call指令进行一个段内调用,再进行一个无条件跳转。**我们以jmp指令的操作数作为地址,在内存窗口中查看:


我们在可执行文件(未运行)中查看对应的地址:

可以看到对于同一个PE文件中的内容,在硬盘和在内存中是不一样的。具体来说,硬盘中PE文件对应地址中保存的是一个RVA,这个地址指向了函数名称(可选)+函数名称字符串:


加载器根据这些字符串在内存中找到函数地址,并且用函数的地址替换PE文件中的RVA:

call的操作数是jmp指令所在的地址;而jmp指令的操作数则是该导入函数在导入表的地址。在程序中所有的导入函数地址被排列在一起,组成IAT(导入地址表),通过这样的分解操作配合导入表实现对外部函数的调用。

接下来我们看看导入表。

导入表

导入表的RVA和size由数据目录项的第二项指定:

我们把RVA转换为FOA,得到地址0x6100,在十六进制编辑器中查看(其中第一行0x600是IAT,下面的是导入表的内容):

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

下面对各字段进行解释:

OriginalFirstThunk

双字。因为它是指向另外数据结构的通路,因此简称为桥1。

**该字段指向一个包含了一系列结构(IMAGE_THUNK_DATA)的数组。**指向的数组中的每个结构定义了一个导入函数的信息,最后以一个内容为全0的结构作为结束。该结构实际上只是一个双字,但在不同的时刻却拥有不同的解释。

OriginalFirstThunk字段有两种解释:

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

它是指向 INT(Import Name Table,导入名称表) 的 RVA。INT 是一个以 IMAGE_THUNK_DATA 为元素的数组,按名称导入时,每个元素指向一个 IMAGE_IMPORT_BY_NAME 结构(包含 Hint 和函数名字符串);按序号导入时,元素的高位被置位,低位直接存储序号值。加载器正是通过遍历 INT 来获知每个 DLL 需要导入哪些函数。

在示例程序中,第一个IMAGE_IMPROT_DESCRIPTOR中OriginalFirstThunk字段的内容为:


最高位是0,这是一个RVA,表明函数是以字符串类型的函数名导入的。我们把这个RVA转换为FOA(0x654),在编辑器中查看该地址的内容(每次取双字,因为IMAGE_THUNK_DATA大小为双字):

得到的是0x0000205C,最后以双字的0标志着这个数组的结束。

因为这个动态链接库只调用了一个函数,所以,数组里只有两个元素。如果调用的函数多了,数组中的元素也会增加。**这组数中每一个都是一个RVA,不过这个RVA却指向了另外一个结构IMAGE_IMPORT_BY_NAME。**这个结构大小不确定,是桥1的最终目的地。结构的第一个为双字,紧跟着的是函数的名字。

该结构的第一个字段是双字,代表着函数的编号。在DLL中,每个函数都有自己的编号,访问函数可以通过编号访问,也可以通过函数名访问。第二个字段是函数名字字符串的内容,以’\0’作为字符串结束的标志。

我们把0x0000205C转换为FOA:

TimeDateStamp

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

ForwarderChain

双字。链表的前一个结构。

Name1

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

在示例程序中为:6A 20 00 00,把这个地址转换为FOA,就是user32.dll这个字符串。

FirstThunk

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

桥2指向的数据和桥1是完全相同的,只是存储位置不同。

我们把桥1指向的IMAGE_THUNK_DATA结构数组称为INT,桥2指向的IMAGE_THUNK_DATA数组称为IAT。

指向 IAT(Import Address Table,导入地址表) 的 RVA。IAT 同样是一个 IMAGE_THUNK_DATA 数组,其初始内容与 INT 完全一致(在未绑定的情况下)。当 Windows 加载器完成函数地址解析后,会将解析出的真实函数地址逐一写入 IAT 的对应槽位。程序运行时,对导入函数的调用通常通过 call [IAT槽位] 间接跳转,因此 IAT 是动态链接的最终“落点”。


IMAGE_THUNK_DATA

// 32位typedefstruct_IMAGE_THUNK_DATA{union{DWORD ForwarderString;// 指向转发的函数名DWORD Function;// 函数的内存地址DWORD Ordinal;// 函数的序号DWORD AddressOfData;// 指向 IMAGE_IMPORT_BY_NAME}u1;}IMAGE_THUNK_DATA;

IMAGE_THUNK_DATA是 PE 文件导入机制中的核心数据结构,它充当了函数标识的“多态指针”——既可以表示函数名,也可以表示函数序号,并在加载过程中被替换为函数地址。

在磁盘中:

// 32位下判断:if(u1.Ordinal&IMAGE_ORDINAL_FLAG32){// 0x80000000// 按序号导入WORD ordinal=u1.Ordinal&0xFFFF;// 取低16位为序号}else{// 按名称导入IMAGE_IMPORT_BY_NAME*importByName=(IMAGE_IMPORT_BY_NAME*)(u1.AddressOfData);char*functionName=importByName->Name;}

在内存中绑定后作为函数指针:

void(*functionPointer)()=(void(*)())u1.Function;

IMAGE_IMPORT_BY_NAME

typedefstruct_IMAGE_IMPORT_BY_NAME{WORD Hint;// 导出表中函数名的序号提示CHAR Name[1];// 以null结尾的函数名字符串}IMAGE_IMPORT_BY_NAME;

为什么要让桥1和桥2指向的数据完全相同

为什么需要两个一模一样的IMAGE_THUNK_DATA数组呢?答案是当PE文件被装入内存的时候,其中一个数组的值将被改作他用,还记得前面分析Hello World程序时提到的吗?Windows装载器会将指令Jmp dword ptr[xxxxxxxx]指定的xxxxxxxx处的RVA替换成真正的函数地址,其实xxxxxxxx地址正是由FirstThunk字段指向的那个数组中的一员。之所以在PE文件中使用两份IMAGE_THUNK_DATA数组的拷贝并修改其中的一份,是为了最后还可以留下一份拷贝用来反过来查询地址所对应的导入函数名。

总结

每一个结构IMAGE_IMPORT_DESCRIPTOR都对应一个唯一的动态链接库文件,以及引用了该动态链接库的多个函数,每个函数的最终“值-名称”描述均可以沿着桥1或者桥2找到,这种导入表结构被称为双桥结构。

双桥结构的导入表在文件中存在两份内容完全相同的地址列表。一般情况下,桥2指向的地址列表被定义为IAT,而桥1指向的地址列表则被定义为INT(Import Name Table)。

在PE文件加载到虚拟内存空间中后,导入表相关数据结构变化如下:

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/3 9:45:07

3万亿Token的PDF革命:FinePDFs如何重塑大模型训练格局

3万亿Token的PDF革命:FinePDFs如何重塑大模型训练格局 导语 Hugging Face最新发布的FinePDFs数据集以3万亿Token规模、覆盖1733种语言的PDF文档,重新定义了大模型训练数据的边界,为多语言理解和长文档处理提供了前所未有的资源支持。 行业…

作者头像 李华
网站建设 2026/11/3 9:44:46

突破软件工程大模型训练瓶颈:SWE-smith自动化数据生成技术革新

突破软件工程大模型训练瓶颈:SWE-smith自动化数据生成技术革新 在人工智能驱动的软件工程领域,语言模型(LM)的性能提升高度依赖高质量训练数据的规模与多样性。当前行业面临的核心挑战在于现有数据集普遍存在规模局限——多数仅包…

作者头像 李华
网站建设 2026/11/3 9:41:41

ERNIE 4.5-VL:4240亿参数多模态大模型如何重塑AI产业格局

ERNIE 4.5-VL:4240亿参数多模态大模型如何重塑AI产业格局 导语 百度ERNIE 4.5系列大模型通过异构混合专家架构与2Bits无损量化技术,将千亿参数模型的部署成本降至传统方案的12.5%,重新定义了企业级AI的落地经济学。 行业现状:大…

作者头像 李华
网站建设 2026/11/3 9:39:25

详解ComfyUI集成Wan2.1 Fun Control实现精准视频生成全流程

详解ComfyUI集成Wan2.1 Fun Control实现精准视频生成全流程 关于Wan2.1-Fun-Control技术方案 Wan2.1-Fun-Control作为阿里巴巴团队研发的开源视频生成控制框架,创新性地采用控制代码(Control Codes)机制,通过深度学习算法融合多…

作者头像 李华
网站建设 2026/11/3 9:38:26

3 步装好 G-Helper:免费替代 Armoury Crate 的华硕笔记本控制工具

3 步装好 G-Helper:免费替代 Armoury Crate 的华硕笔记本控制工具 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, Vivobook, …

作者头像 李华
网站建设 2026/11/3 9:32:06

36.6%解决率逼近GPT-4o:SWE-Dev-9B如何重新定义开源代码大模型?

36.6%解决率逼近GPT-4o:SWE-Dev-9B如何重新定义开源代码大模型? 你还在为复杂的软件开发任务头疼吗?是否渴望有一个高效的AI助手能帮你解决实际工程问题?今天我们要介绍的SWE-Dev-9B开源代码大模型,或许能成为你的得力…

作者头像 李华