【第37节】windows原理:PE文件的延迟载入表和导入地址表

目录

一、延迟载入表

1.1 延迟加载简介

1.2 延迟加载实现

1.3 延迟加载的机理

二、导入地址表IAT

2.1 IAT简介

2.2 IAT的作用


一、延迟载入表

1.1 延迟加载简介

        延迟加载的DLL属于隐含链接的DLL,它的加载机制很特别。一般来说,程序运行到某个阶段,代码里要是引用了这种DLL包含的特定符号,像函数名或者变量名,系统才会开始加载这个DLL。

        这种延迟加载和直接用`LoadLibrary`、`GetProcAddress`函数达到的效果差不多。拿大型图形处理软件来说,软件启动的时候要加载好多DLL,才能实现各种复杂功能。要是采用延迟加载机制,软件启动时就只加载核心模块。等用户进行特定图形编辑操作,触发对特定DLL符号的引用时,相关DLL才会被加载。这么做,软件启动速度会快很多,而且在不同Windows系统版本上,还能更好地解决因为系统DLL版本不一致产生的兼容性问题。

        要知道,使用延迟加载机制,对DLL本身没什么特殊要求。不管是系统自带的DLL,还是开发者自己编写的DLL,都能通过这种机制实现延迟加载。

1.2 延迟加载实现

若要在EXE或其他DLL中启用延迟加载机制,可按以下步骤操作:
(1)包含必要的头文件及静态库

#include <windows.h>
#include <delayimp.h>
#pragma comment(lib,"Delayimp.lib")

        `windows.h`是Windows系统编程里很关键的头文件,里面有大量Windows API的声明。`delayimp.h`专门针对延迟加载机制,定义了相关的数据结构和函数。用`#pragma comment(lib,"Delayimp.lib")`这个指令,可以把`Delayimp.lib`静态库链接到项目里,这样就能实现延迟加载功能了。 

(2)设置连接器选项
        在Visual Studio这类集成开发环境里,要在项目属性中找到“连接器”设置,然后进入“输入”选项卡。在“延迟加载的DLL”这一项里,填上需要延迟加载的DLL名称,注意名称的大小写要和实际DLL名称一模一样,不然没办法正确实现延迟加载。

(3)设置卸载功能(如果有需要)
        要是项目需要卸载延迟加载的DLL,还是在项目属性的“连接器”设置里,找到“高级”选项卡,把“卸载延迟加载”勾选上。

        完成上面这些设置后,就可以像用显式加载的DLL那样,调用这个DLL导出的函数。两者只有一个区别,就是延迟加载的DLL,只有在实际用到它导出的具体API时,托管代码才会开始加载它。

        另外,如果确定在很长一段时间内都不会再用某个延迟加载的DLL了,就可以调用`FUnloadDelayLoadedDLL2("xxx.dll")`函数把它卸载掉。要特别留意,这个函数的参数只能是单纯的DLL文件名,不能有路径信息。而且,延迟加载的DLL只能用这个函数来释放,要是错误地用`FreeLibrary`去卸载,会让导入函数的地址没办法正常清除,在后续程序运行的时候,访问相关函数就会出现访问异常错误。

1.3 延迟加载的机理

        DLL延迟加载机制的关键是`delayLoadHelper()`函数。程序调用延迟加载函数时,其实调用的是`delayLoadHelper()`。这个函数会去引用可执行文件里特殊的`Delay Import`节,这个节存着延迟加载DLL的相关信息。在`delayLoadHelper()`函数里面,会自动按顺序调用`LoadLibrary()`和`GetProcAddress()`函数,`LoadLibrary()`用来加载DLL,`GetProcAddress()`用来获取DLL里目标函数的地址。

        `delayLoadHelper()`函数成功拿到延迟加载函数的地址后,会巧妙处理后续对该函数的调用,让后续调用能直接指向延迟加载函数,避免重复加载和获取地址,提升程序运行效率。

延迟加载结构体

typedef struct _IMAGE_DELAYLOAD_DESCRIPTOR {
    DWORD AllAttributes;        // 1(没用)为0
    DWORD DllNameRVA;           // 2(有用)延迟加载的DLL名字
    DWORD ModuleHandleRVA;      // 3(有用)
    DWORD ImportAddressTableRVA;// 4(重要)延迟载入IAT的RVA (PIMAGE_THUNK_DATA)
    DWORD ImportNameTableRVA;   // 5(重要)延迟载入INT的RVA
    DWORD BoundImportAddressTableRVA; // 6(有用)绑定IAT的RVA
    DWORD UnloadInformationTableRVA;  // 7(有用)
    DWORD TimeDateStamp;            // 8(若未绑定)
} IMAGE_DELAYLOAD_DESCRIPTOR, *PIMAGE_DELAYLOAD_DESCRIPTOR;

        在这个结构体里,`ImportAddressTableRVA`和`ImportNameTableRVA`这两个部分特别重要。它们的解析方法,跟导入表的解析方法是一样的。系统通过解析这些信息,就能精准定位到延迟加载DLL的相关内容,从而实现延迟加载功能 。 

二、导入地址表IAT

2.1 IAT简介

        在Windows可执行文件(PE文件)的数据目录表中,第13项直接指向IAT表(Import Address Table,导入地址表)。从数据目录出发,可找到`IMAGE_THUNK_DATA`数组,该数组构成了IAT表的主体。

        IAT表在PE文件中扮演着关键角色,主要用于记录程序运行时所需调用的外部DLL函数的实际地址。以一款游戏程序为例,在程序启动阶段,Windows加载器会依据IAT表中的信息,遍历各个需要导入的DLL,并将这些DLL中函数的实际地址填充到IAT表中。当游戏程序运行过程中调用外部函数时,便能通过IAT表快速找到对应的正确地址,确保游戏功能正常实现。

        数据目录表的第12项是可选的。若存在第12项,它指向的导入表和从第2项得到的IAT表功能相同。这一设计主要是为了兼容不同的PE文件格式,或是满足特定的编程需求。在实际的PE文件分析和处理工作中,第13项指向的IAT表更受关注,因为它是程序运行时动态解析外部函数地址的核心环节。

        导入地址表(Import Address Table,IAT)在Windows系统中通常通过`IMAGE_IMPORT_DESCRIPTOR`和`IMAGE_THUNK_DATA`等结构体来表示,下面对相关结构体进行详细定义及说明:

// 定义IMAGE_IMPORT_DESCRIPTOR结构体
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD Characteristics;
        DWORD OriginalFirstThunk;
    };
    DWORD TimeDateStamp;
    DWORD ForwarderChain;
    DWORD Name;
    DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;

// 定义IMAGE_THUNK_DATA结构体
typedef struct _IMAGE_THUNK_DATA {
    union {
        DWORD ForwarderString;
        DWORD Function;
        DWORD Ordinal;
        DWORD AddressOfData;
    };
} IMAGE_THUNK_DATA, *PIMAGE_THUNK_DATA;

`IMAGE_IMPORT_DESCRIPTOR`结构体用于描述一个导入库的详细信息:

        - `OriginalFirstThunk`:指向导入名称表(Import Name Table,INT)的相对虚拟地址(RVA)。若该值为0,表明该导入库没有导入名称表。- `TimeDateStamp`:导入库的时间戳,在进行版本控制时,开发人员可通过对比时间戳,判断导入库的版本是否符合要求。

        - `ForwarderChain`:用于处理转发导入的链信息,当一个DLL将函数转发到另一个DLL时,该字段发挥重要作用。

        - `Name`:指向导入库名称字符串的相对虚拟地址(RVA),通过该地址,系统能够确定需要导入的DLL名称。

        - `FirstThunk`:指向导入地址表(IAT)的相对虚拟地址(RVA),在程序运行时,系统会将实际的函数地址填充到这个表中。

        `IMAGE_THUNK_DATA`结构体用于表示导入函数的相关信息,作为一个联合体,在不同场景下可表示不同信息:

        - `ForwarderString`:若为转发导入,这里指向转发字符串的地址,通过该字符串,系统能够找到转发的目标。

        - `Function`:在导入地址表中,这里存储着实际导入函数的地址,程序运行时通过该地址调用函数。

        - `Ordinal`:当函数通过序号导入时,这里存储着函数的序号,系统可根据序号找到对应的函数。

        - `AddressOfData`:指向`IMAGE_IMPORT_BY_NAME`结构体的地址,该结构体包含了导入函数的名称等详细信息。

2.2 IAT的作用

        借助这些结构体,程序在运行时能够动态解析和调用导入的函数,实现对外部库函数的高效引用。

        IAT表的结构和内容在逆向工程、病毒分析、程序调试等领域有着极高的应用价值。在逆向工程中,研究人员通过分析IAT表,能够深入了解程序的功能架构和依赖关系,为程序的逆向分析提供重要线索。在病毒分析工作中,通过分析病毒程序的IAT表,安全人员可以发现病毒调用的系统API函数,进而判断病毒的攻击方式、传播途径以及潜在危害。

        在实际编程过程中,开发人员也可通过修改IAT表实现一些特殊功能,如API拦截。通过将IAT表中某个函数的地址修改为自定义函数地址,当程序调用该函数时,会转而执行自定义函数,从而实现对程序行为的精准监控和灵活修改。

        总之,IAT表是PE文件中极为重要的数据结构,不仅对程序的正常运行和动态链接起着决定性作用,还为开发人员和安全研究人员分析、修改程序提供了关键切入点 。

 

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

攻城狮7号

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

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

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

打赏作者

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

抵扣说明:

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

余额充值