DLL 装载机制
1 DLL 加载机制
- 显式链接:程序使用DLL时加载,使用完毕后释放内存
- 隐式链接: 程序开始时即一同加载DLL,程序终止时再释放占用的内存。IAT提供的机制即与隐式链接有关。
1.1 导入表机制
程序调用系统API时并非直接调用而是通过获取程序PE文件中.text节区
的内存区域( 更确切地说是IAT内存区域)中存储的API的地址来获取API在内存中的真正地址。API地址内存地址获取途径
NT可选头 ---> DataDirectory[1]. VirtualAddress ---> IMAGE_ IMPORT_ DESCRIPTOR ---> IMAGE_THUNK_DATA.u1->Functiion
为什么不直接调用而是通过IAT机制?
- 在不同系统环境中kernel32.dll的版本各不相同,API函数加载到内存中的位置(地址)也不相同
- DLL重定位:DLL文件的ImageBase值一般为
10000000
。比如某个程序使用a.dll 与b.dll 时,PE 装载器先把a.dll装载到内存的10000000
(lmageBase) 处, 然后尝试把b.dll也装载到该处。但是由于该地址处已经装载了a.dll, 所以PE装载器查找其他空白的内存空间(ex:3E000000
), 然后将b.dll 装载进去。
1.2 导入表结构体 : IMAGE_ IMPORT_ DESCRIPTOR
IMAGE_ IMPORT_ DESCRIPTOR
结构体中记录着PE文件要导入哪些库文件。它不在PE头而在PE体中,但查找其位置的信息在PE头中,IMAGE_OPTIONAL_HEADER32.DataDirectory[1]. VirtualAddress
的值即是IMAGE_IMPORT_ DESCRIPTOR
结构体数组的起始地址(RVA值)。
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk; //INT(Import Name Table)地址(RVA)
};
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name; //库名称字符串的地址(RVA)
DWORD FirstThunk; //IAT的地址(RVA)
} IMAGE_IMPORT_DESCRIPTOR;
//INT与IAT是长整型(4个字节数据类型)数组,以NULL结束(未另外明确指出大小)。
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
BYTE Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
//INT中各元素的值为IMAGE_IMPORT_BY_NAME结构体指针(有时IAT也拥有相同的值)
1.3 IAT工作原理
- 读取
IMAGE_ IMPORT_ DESCRIPTOR
的Name
成员, 获取库名称字符串(“kernel32. dll “)。 - 装载相应库,Loadlibrary (“kernel32.dll”)
- 读取
IMAGE_ IMPORT_ DESCRIPTOR
的OriginalFirstThunk
成员 - 逐一读取
INT
中数组的值,获取相应IMAGE_IMPORT_BY_NAME
地址(RVA) - 使用
IMAGE_IMPORT_BY_NAME
的Hint (ordinal)
或Name
项,获取相应函数的起始地址,GetProcAddress (“GetCurrentThreadld”) - 读取
IMAGE_ IMPORT_ DESCRIPTOR
的FirstThunk (IAT)
成员, 获符IAT地址。 - 将上面获得的函数地址输入相应
IAT
数组值。 - 重复以上步骤4-7, 直到INT结束(遇到NULL 时)
其中FirstThunk是指向 MAGE_THUNK_DATA
结构体得指针
IMAGE_THUNK_DATA
{
union u1
{
DWORD ForwarderString; //指向一个转向者字符串的RVA
DWORD Function ; //被输入的函数的内存地址
DWORD Ordinal ; //被输入的API的序数值
DWORD AddressOfData ; //高位为0则指向IMAGE_IMPORT_BY_NAME 结构体二
};
}

1.4 EAT 导出地址表
EAT机制可以使不同的应用程序可以调用库文件中提供的函数是,只有通过EAT才能准确求得从相应库中导出函数的起始地址。 IMAGE_EXPORT_DIRECTORY
结构体如下
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp; //时间戳
WORD MajorVersion;
WORD MinorVersion;
DWORD Name; //指向改导出表文件名字符串
DWORD Base; //导出表的起始序号
DWORD NumberOfFunctions; //实际导出函数的个数
DWORD NumberOfNames; //Export函数中具名的函数个数
DWORD AddressOfFunctions; //导出函数地址数组,总大小NumberOfFunctions * 4字节
DWORD AddressOfNames; //导出函数名称数组,总大小为NumberOfNames * 4字节
DWORD AddressOfNameOrdinals; //导出函数序号表,大小为NumberOfNames * 2字节
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

1.5 库函数导出机制
从库中获得函数地址的API为GetProcAddress()
函数。该API引用EAT来获取指定API的地址。GetProcAddress()
API拥有函数名称,EAT原理如下:
- 利用
AddressOfNames
成员转到函数名称数组 - 函数名称数组”中存储着字符串地址。通过比较( strcmp) 字符串, 查找指定的函数名称
- 利用
AddressOfNameOrdinals
成员, 转到ordinal
数组。 - 在
ordinal
数组中通过name_index 查找相应ordinal 值。 - 利用
AddressOfFunctions
成员转到“函数地址数组” (EAT)。 - 在"函数地址数组” 中将刚刚求得的
ordinal
用作数组索引,获得指定函数的起始地址。