参考文章:https://blog.csdn.net/chenlycly/article/details/53378427
什么是导入表:
百度百科:Import Address Table 由于导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或者多个DLL 中.
当PE 文件被装入内存的时候,Windows 装载器才将DLL 装入,并将调用导入函数的指令和函数实际所处的地址联系起来(动态连接),这操作就需要导入表完成.其中导入地址表就指示函数实际地址。
PE文件什么时候开始加载导入表:
在exe加载入内存 修复完重定位表中的数据的时候,就开始加载导入表
如何定位导入表
在扩展PE头是一个名为_IMAGE_OPTIONAL_HEADER
的结构体
其中存在一个结构体数组为IMAGE_DATA_DIRECTORY
,个数有16个,总占128字节,这里的第2个位置就是IMAGE_DATA_DIRECTORY
IMAGE_DATA_DIRECTORY
的结构如下:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //虚拟地址,存储当前导出表的地址,这里地址是RVA
DWORD Size; //存储 当前导出表的大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
因为导入表由一堆PE文件组成,所以一般导入表都是有许多个的,导入表中存储的为当前PE需要依赖的函数等
其中关于导入表的结构体的名称为:_IMAGE_IMPORT_DESCRIPTOR
,结构体如下:
导入表的具体结构为:占20个字节
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA指向IMAGE_THUNK_DATA结构数组
};
DWORD TimeDateStamp; // 时间戳
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name; // RVA,指向dll名字,该名字以0结尾
DWORD FirstThunk; // RVA,指向IMAGE_THUNK_DATA结构数组
} IMAGE_IMPORT_DESCRIPTOR;
如何判断导入表的个数?依赖6个模块
每20个字节为一个dll的导入表的相关信息,以20个0为结束符号
第一个导入表其中Name
成员为当前导入表的名字的地址,地址为014592
,如果文件对齐和内存对齐大小不一样那需要先转换为FOA再去查找!
当前导入表的模块为KERNEL32.dll
已经知道了各个导入表的名称,现在再继续了解下导入表中导入了哪些函数吧
其中有两个成员为相同的结构体数组OriginalFirstThunk
,FirstThunk
都指向IMAGE_THUNK_DATA
结构体数组
下面的图是:PE文件加载前的情况图
IMAGE_THUNK_DATA
结构体如下:发现是一个联合体,实际上该结构体的字节为4字节
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;
虽然OriginalFirstThunk
,FirstThunk
指向的结构体都一样,但是名称不一样OriginalFirstThunk
指向的表叫导入名称表(INT),FirstThunk
指向的表叫导入地址表(IAT)
当前OriginalFirstThunk
指向的地址为014310
,结束符为4字节的0结束
那么就可以说明当前PE文件就依赖当前这个DLL文件的这么多函数!
例如图中有有00014566
00014554
00014546
如果这些地址的最高位是为1的话,那么除去最高位的值就是函数的导出序号
如果这些地址的最高为不是为1的话,那么这个值就是一个RVA,指向IMAGE_IMPORT_BY_NAME
结构体,结构体如下:占4个字节
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; //可能为空,编译器决定,如果不为空,是函数在导出表的索引
BYTE Name[1]; //函数名称,标识符只显示函数的首字母,以0结尾
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
可以跟下00014566
,然后往后,最后以0结尾
那么到目前位置,就确定了两个,1、 如何分析该PE的导入表的DLL 2、该PE的导入表中的DLL的导入函数有哪些
那么还有最后一个 如果确定当前PE文件的导入表中导入的函数地址如何确定呢?
下面的图是:PE文件加载后的情况图
当PE文件执行后,当前导入表中的使用的函数地址都会添加IAT表中,也就是函数地址表中,这时候IAT和INT就开始有差异了!
问题:那为什么PE文件不直接是加载前的就好了,这样的话还可以去掉INT,直接通过IAT来寻找?
答: 如果IAT中的表的地址丢失了,就需要修正,就可以通过INT表来找到函数名称,然后通过GetProcAddress来重新获得函数地址,这个用法会在以后的注入手段中用到,比如修复IAT表就需要通过遍历INT表来实现
打印导入表:
实现的代码如下:
void PrintfImportTable(PVOID pFileBuffer){
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
PIMAGE_IMPORT_DESCRIPTOR pIMPORT_DESCRIPTOR;
PIMAGE_IMPORT_BY_NAME pImage_IMPORT_BY_NAME;
char ImportTableDllName[10] = {0};
char FunctionName[20] = {0};
PDWORD OriginalFirstThunk_INT = NULL;
PDWORD FirstThunk_IAT = NULL;
DWORD RVA = 0;
DWORD FOA = 0;
DWORD Original = 0;
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + IMAGE_SIZEOF_NT_OPTIONAL_HEADER);
//获取导入表的位置
RVA_TO_FOA(pFileBuffer,pOptionHeader->DataDirectory[1].VirtualAddress,&FOA);
//每个导入表的相关信息占20个字节
pIMPORT_DESCRIPTOR = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pFileBuffer + (DWORD)FOA);
//这里可以进行while操作,这里while的判断依据为 pIMPORT_DESCRIPTOR个数
printf("=========================================");
while(pIMPORT_DESCRIPTOR->FirstThunk && pIMPORT_DESCRIPTOR->OriginalFirstThunk){
//这里打印的是INT表
//获取当前导入表DLL的名字
strcpy(ImportTableDllName,(PVOID)((DWORD)pFileBuffer + (DWORD)pIMPORT_DESCRIPTOR->Name));
printf("当前打印的导出表的DLL为: %s
", ImportTableDllName);
printf("
");
printf("INT表打印
");
//OriginalFirstThunk转换FOA
RVA_TO_FOA(pFileBuffer,pIMPORT_DESCRIPTOR->OriginalFirstThunk,&FOA);
OriginalFirstThunk_INT = (PDWORD)((DWORD)pFileBuffer + (DWORD)FOA);
//printf("%x",*OriginalFirstThunk_INT);
printf("
");
while(*OriginalFirstThunk_INT){
//printf("%x
",*OriginalFirstThunk_INT);
if((*OriginalFirstThunk_INT) & 0X80000000){
//高位为1 则 除去最高位的值就是函数的导出序号
Original = *OriginalFirstThunk_INT & 0xFFF; //去除最高标志位。
printf("按序号导入: %08Xh -- %08dd
", Original, Original); //16进制 -- 10 进制
}else{
//高位不为1 则指向IMAGE_IMPORT_BY_NAME
RVA_TO_FOA(pFileBuffer,*OriginalFirstThunk_INT,&FOA);
pImage_IMPORT_BY_NAME = (PIMAGE_IMPORT_BY_NAME)FOA;
strcpy(FunctionName,(PVOID)((DWORD)pFileBuffer + (DWORD)&(pImage_IMPORT_BY_NAME->Name)));
printf("按函数名导入 函数名为: %s
",FunctionName);
}
OriginalFirstThunk_INT++;
}
printf("
");
//继续如上操作进行打印操作
//这里打印的是iat表
printf("IAT表打印
");
//FirstThunk转换FOA
RVA_TO_FOA(pFileBuffer,pIMPORT_DESCRIPTOR->FirstThunk,&FOA);
FirstThunk_IAT = (PDWORD)((DWORD)pFileBuffer + (DWORD)FOA);
//printf("%x",*OriginalFirstThunk_INT);
printf("
");
while(*FirstThunk_IAT){
//printf("%x
",*OriginalFirstThunk_INT);
if((*FirstThunk_IAT) & 0X80000000){
//高位为1 则 除去最高位的值就是函数的导出序号
Original = *FirstThunk_IAT & 0xFFF; //去除最高标志位。
printf("按序号导入: %08Xh -- %08dd
", Original, Original); //16进制 -- 10 进制
}else{
//高位不为1 则指向IMAGE_IMPORT_BY_NAME
RVA_TO_FOA(pFileBuffer,*FirstThunk_IAT,&FOA);
pImage_IMPORT_BY_NAME = (PIMAGE_IMPORT_BY_NAME)FOA;
strcpy(FunctionName,(PVOID)((DWORD)pFileBuffer + (DWORD)&(pImage_IMPORT_BY_NAME->Name)));
printf("按函数名导入 函数名为: %s
",FunctionName);
}
FirstThunk_IAT++;
}
printf("=========================================");
printf("
");
pIMPORT_DESCRIPTOR++;
}
}