1.关于IAT(import address table)表
当exe程序中调用dll中的函数时,反汇编可以看到,call后面并不是跟的实际函数的地址,而是给了一个地址;
.png)
这些连起来就是一张表,就是IAT表;
1)内存镜像中的dll中函数的调用;
例如:一个exe中调用系统提供的dll中的MessageBox函数时:
.png)
可以看到call的地址是42d2c4;
查看42d2c4中的内容:
.png)
可以看到:42d2c4中保存的是762b0026;
762b0026是dll的领空;也就是dll中MessageBox函数的地址;
总结:
exe程序调用dll中的函数时,会使用FF15call;
call的并不是实际的函数地址,而是该函数对应的IAT表的地址;
通过IAT表来找到实际的函数地址;
2)文件镜像中的IAT相关
接下来取文件中找42d2c4中保存的内容;
需要将RVA转成FOA;
.png)
.png)
42d2c4-400000 = 2d2c4;在.idata节;
foa = 2d2c4 - 2d000 + 2b000 = 2b2c4;
可以看到:文件镜像中保存的值为2d2f4;
.png)
继续追到2d2f4,对应的foa为2b2f4
.png)
看到并不是MessageBox的地址,而是一MessageBox先关的描述;
结论:IAT表在文件和内存中是不一样的;
3)原因分析
对于一个exe程序,在运行时会加载到独立的4gb空间;
exe程序是可以按ImageBase来加载的;
但该exe引用的dll并不能保证,因为ImageBase可能被占用;
因此对于dll来说有重定位表来记录dll中的可能需要修改绝对地址;
对于引用dll的exe程序来说,因为无法确定dll中引用的函数的地址,所以不会再文件镜像中将引用dll的函数的地址写死;
而是保存的是这些函数相关的信息;
当程序运行后,并且所有dll都加载到该程序的4gb空间后,此时dll中函数的地址才确定;
IAT表中将保存dll中函数在内存中的真实地址;
因此IAT表中在文件和内存中不同;
4)IAT表总结
IAT表在程序执行前和程序执行后是不一样的;
程序在编译时给分了一个空间,保存一个偏移,这个偏移指向的是引用dll中的函数的函数名,而不是真正的函数地址;
当程序加载完毕,所有的dll都贴到程序的4gb空间后,会把IAT表中的值改为真正要用的函数的地址;因为dll的地址因不是按ImageBase加载而不一样;
2.导入表
比如说当去餐馆吃饭时,餐馆会提供一个菜单,告诉谁提供了哪些菜;
自己在点餐时也需要给餐馆提供一个单,来告诉自己点了哪些菜;
对于程序来说dll提供导出表,导出表中保存的是导出函数的清单以及地址;
程序也需要提供一个导入表,来说明使用了哪些函数;
1)导入表的结构
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk; //RVA 指向IMAGE_THUNK_DATA结构数组
};
DWORD TimeDateStamp; //时间戳
DWORD ForwarderChain;
DWORD Name; //RVA,指向dll名字,该名字已0结尾
DWORD FirstThunk; //RVA,指向IMAGE_THUNK_DATA结构数组
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
数据目录的第二项就是导入表;
导入表结构可能有多个,一个dll对应一个导入表结构;
其中比较重要的两个属性:
OriginalFirstThunk ->指向INT表(import name table);
表中存的是一些IMAGE_THUNK_DATA结构;
这个表以0结尾;
FirstThunk ->指向IAT表(import address table);
IAT表有两种方式可以找到:1】通过数据目录的第13个结构;2】导入表结构的这个属性;
2)导入表在文件加载前和文件加载后有区别
1】pe文件加载前:
.png)
IAT表和INT表中保存的结构都是IMAGE_THUNK_DATA,且都是以0结尾;
在文件加载前两张表里的内容完全一样;
IMAGE_THUNK_DATA用来保存函数名以及函数序号等信息;
IMAGE_THUNK_DATA结构中只有一个联合,占4字节的空间;
IMAGE_THUNK_DATA结构:
typedef struct _IMAGE_THUNK_DATA32 {
union {
PBYTE ForwarderString;
PDWORD Function;
DWORD Ordinal; //序号
PIMAGE_IMPORT_BY_NAME AddressOfData; //指向IMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
IMAGE_IMPORT_BY_NAME结构:
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; //可能为空,编译器决定 如果不为空 是函数在导出表中的索引
BYTE Name[1]; //函数名称,以0结尾
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
2】pe文件加载后:
.png)
文件加载后,系统会调用通过函数名或函数序号找函数地址的函数GetProcAddr() ;
得到函数地址后,用函数地址替换IAT表中的数据;
使用两张表INT、IAT的原因:
如果只有IAT表,在程序加载完后,IAT表中的函数名将替换成函数地址,就无法知道函数名,也就是留一个函数名的备份;
3.解析导入表
主要步骤:
1】定位导入表:
数据目录的第二个结构为导入表;
通过该结构,可以找到导入表rva,转成foa可以在文件中找到导入表;
每一个引用的dll都对应一个导入表,需要循环解析,直到遇到全0的导入表结构为止;
也就是sizeOf(IMAGE_IMPORT_DESCRIPTOR) 个 0 代表导入表结束;
2】解析dll名:
导入表中储存了引用的dll的名字信息;
在导入表结构的Name属性中;
3】解析OriginalFirstThunk:
OriginalFirstThunk指向INT表;
INT表中保存了IMAGE_THUNK_DATA结构,该结构只有一个联合,占4字节;
判断最高位是否为1,如果是那么除去最高位的值就是函数的导出序号;有些函数匿名导出的,不能用函数名找地址;
如果不是,那么这个值是一个RVA 指向IMAGE_IMPORT_BY_NAME ;
IMAGE_IMPORT_BY_NAME结构中有2个属性;
Hint ->是函数在导出表中的索引,并不是函数的序号,没有实际作用;
Name ->函数名,只有1个字节,也就是函数名的开始,因为字符串长度不固定,从字符串开始到0结束为一个函数名;
调用 GetProcAddr(m,函数的名字或者导出序号) 函数可以找到函数地址;
当IMAGE_THUNK_DATA结构为0时,表示该INT表结束;
4】解析FirstThunk:
FirstThunk指向IAT表,IAT表在程序加载前和INT表是一样的;
输出导入表信息:
.png)
.png)
.png)
.png)
代码:
#include "stdafx.h"
#include "PeTool.h"
#include "string.h"
#define SRC "C:\\Users\\Administrator\\Desktop\\Hello.exe"
//解析导入表
void printImport(){
//定义pe头结构指针
PIMAGE_DOS_HEADER dosHeader = NULL; //dos头指针
PIMAGE_FILE_HEADER peHeader = NULL; //pe头指针
PIMAGE_OPTIONAL_HEADER32 opHeader = NULL; //可选pe头指针
PIMAGE_DATA_DIRECTORY dataDir = NULL; //数据目录指针
PIMAGE_IMPORT_DESCRIPTOR importDir= NULL; //导入表指针
//1.将文件读入内存
LPVOID pFileBuffer = NULL;
DWORD fileSize = ReadPEFile(SRC, &pFileBuffer);
if(!pFileBuffer){
printf("读取dll文件失败\n");
return;
}
//2.初始化头结构指针
dosHeader = (PIMAGE_DOS_HEADER) pFileBuffer;
peHeader = (PIMAGE_FILE_HEADER) ((DWORD)pFileBuffer + dosHeader->e_lfanew + 4);
opHeader = (PIMAGE_OPTIONAL_HEADER32) ((DWORD)peHeader + IMAGE_SIZEOF_FILE_HEADER);
dataDir = opHeader ->DataDirectory;
importDir = (PIMAGE_IMPORT_DESCRIPTOR) ((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, (dataDir + 1)->VirtualAddress ));
//3.输出导入表信息
while(importDir->OriginalFirstThunk){
printf("按enter键显示下一个dll导入表信息:\n");
getchar();
LPSTR name = (LPSTR)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, importDir->Name ));
printf("================%s================\n", name);
//解析INT表
PDWORD thunk =(PDWORD) ((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, (DWORD)importDir->OriginalFirstThunk)) ;
while(*thunk){
if((*thunk & 0x80000000) >> 31 == 1){ //如果第一位为1,表示为序号导入
DWORD ord = *thunk & 0x7fffffff;
printf("按序号导入:%d\n", ord);
}else{
PIMAGE_IMPORT_BY_NAME pName = (PIMAGE_IMPORT_BY_NAME)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, *thunk));
printf("按函数名导入:%s\n",pName->Name);
}
//下一个INT表项
thunk++;
}
//下一张导入表
importDir++;
printf("\n");
}
}
int main(int argc, char* argv[])
{
//输出导入表信息
printImport();
getchar();
}
结果:
.png)