一、引入绑定导入表
-
在day39.1-IAT表和导入表的作业中,发现
notepad.exe
在运行前IAT表和INT表中的内容不一样,且通过观察,运行前notepad.exe的IAT表中存储的数据直接就是函数在虚拟内存中的绝对地址,而不是像ipmsg.exe程序:在程序运行时----程序的.exe和使用到的DLL加载完成后,再把使用到的DLL中函数的绝对地址写到IAT表中 -
为什么Windows32位系统自带的一些程序在运行前就把使用到的DLL中的函数的绝对地址绑定到IAT表中呢?
-
优点:程序启动速度变快
因为双击程序后,系统会给程序分配一个4GB虚拟内存,接着把程序的.exe和使用的.dll装载到虚拟内存中,装载完成后再遍历INT表,通过系统API得到对应函数的绝对地址,再写到IAT表中;而如果在程序运行前就已经把函数绝对地址绑定到IAT表中,程序启动时就无需上述修改IAT表的步骤,节省时间
-
缺点:如果程序使用到的DLL装载时没有按照ImageBase位置装载;或者当程序使用到的DLL中的函数被修改了导致函数地址发生变化,那么IAT表中的地址数据就不准确了
-
综上:系统自带程序之所以敢这样做,可能就是因为这些程序使用的DLL都是设计好的,会按照ImageBase装载,不会出现冲突;且这些DLL不经常修改,函数地址不经常发生变化
-
-
程序加载后,系统如何判断一个IAT表中数据是已经绑定好的绝对地址呢?还是指向对应函数名字符串地址的RVA或序号呢?
-
因为导入表有多个
_IMAGE_IMPORT_DESCRIPTOR
这种结构,每一个结构对应一个程序使用到的DLL;比如这个导入表结构对应的DLL是user32.dll,那么根据这个导入表结构中的**TimeDataStamp
**字段,就可以判断此导入表结构的IAT表中的数据是绝对地址还是RVA -
TimeDataStamp
-
为0(即0x00000000):表示这个导入表结构对应的DLL中的函数绝对地址没有绑定到IAT表中
-
为**-1**(即0xFFFFFFFF):表示这个导入表结构对应的DLL中函数绝对地址已经绑定到IAT表中
所以可以通过导入表的时间戳,判断是否有绑定导入表
-
-
-
如果一个导入表结构的
TimeDataStamp
为**-1**,说明绝对地址已经绑定到IAT表中,那如何知道对应DLL中函数的绝对地址什么时间绑定的?就需要另一个表----绑定导入表,绑定导入表中的TimeDataStamp时间戳,才表示函数地址真正的绑定时间
二、绑定导入表
1.定位
-
数据目录的第12个结构,就是绑定导入表数据目录项;再通过绑定导入表数据目录中VirtualAddress字段,RVA转成FOA,定位到绑定导入表地址
2.绑定导入表结构
-
结构如下:
struct _IMAGE_BOUND_IMPORT_DESCRIPTOR{ DWORD TimeDateStamp; //时间戳 WORD OffsetModuleName; //DLL的名字RVA(加第一个结构中RVA才是字符串真正RVA,详见下面) WORD NumberOfModuleForwarderRefs; //这个绑定导入表结构后面还有几个_IMAGE_BOUND_FORWARDER_REF这种结构 }; //绑定导入表有很多这种结构或者_IMAGE_BOUND_FORWARDER_REF这种结构,最后如果有sizeof(_IMAGE_BOUND_IMPORT_DESCRIPTOR)个0,表示绑定导入表结束
1)TimeDateStamp
- 绑定导入表的时间戳:表示程序使用到的DLL中的函数绝对地址真正绑定到IAT表中的时间
- 作用:系统通过程序使用到的DLL对应的**绑定导入表结构中
TimeDateStamp
和该DLL的可选PE头中的TimeDateStamp
**对比- 如果两个时间戳一样:表明DLL的创建时间和把DLL中的函数绝对地址绑定到IAT表的时间是一样,则在绑定以后,DLL没有更新或者修改
- 如果两个时间戳不一样:表明在绑定以后,此DLL又被更新或者修改了,那么绑定到IAT表中的地址可能不准确了!(因为DLL中的函数地址可能变了,但绑定到IAT中的数据可能是以前的函数地址)
2)OffsetModuleName
-
对应DLL的名字:因为一个程序的导入表结构对应一个使用到的DLL;一个程序的绑定导入表结构也对应一个程序使用到的DLL,这个绑定导入表结构记录了该DLL中函数绝对地址绑定到IAT表的时间戳、该DLL的名字、还有该DLL使用到别的DLL的个数
-
注意:不管是
_IMAGE_BOUND_IMPORT_DESCRIPTOR
结构中的OffsetModuleName
、还是后面要讲的_IMAGE_BOUND_FORWARDER_REF
结构中的OffsetModuleName
,都==必须加上绑定导入表起始RVA值==,才是这个结构对应DLL名字的真正RVA因为通过Winhex打开notepad.exe发现:绑定导入表的起始地址是在节表后面!!而上述DLL名字字符串紧跟在绑定导入表后面!(这就解决了以前学新增节时,为什么说节表后面如果有数据,不要随便动!)
3)NumberOfModuleForwarderRefs
-
因为一个DLL可能还会使用到别的DLL中的函数,所以
NumberOfModuleForwarderRefs
字段的值是多少,就表明当前绑定导入表对应的DLL还使用了多少个别的DLL,同样表示这个绑定导入表结构后面跟了多少个_IMAGE_BOUND_FORWARDER_REF
这种结构 -
_IMAGE_BOUND_FORWARDER_REF
结构如下:struct _IMAGE_BOUND_FORWARDER_REF { DWORD TimeDateStamp; //时间戳 WORD OffsetModuleName; //对应DLL的名字 WORD Reserved; //保留(未使用) };
- TimeDataStamp:和绑定导入表结构中的时间戳含义和用途是一样的
- OffsetModuleName:加上绑定导入表起始RVA,才是真正对应DLL的名字
- Reserved:保留字段(未使用),没有任何含义
-
具体结构关系如下:比如一个程序的绑定导入表先是一个
_IMAGE_BOUND_IMPORT_DESCRIPTOR
结构,该结构中NumberOfModuleForwarderRefs
字段值为2(表示该DLL使用了另外两个DLL中的函数),则该结构后面跟了两个_IMAGE_BOUND_FORWARDER_REF
结构(每一个REF结构对应一个DLL);后面以此类推,直到有8字节个0x00表示绑定导入表结束
3.作用
- 综上:当
IMAGE_BOUND_IMPORT_DESCRIPTOR
结构中的TimeDateStamp
与DLL文件标准PE头中的TimeDateStamp
值不相符时(或者DLL需要重定位的时候),程序在装载完成后才会修改对应DLL的导入表结构中的IAT表中的数据
三、作业
1.打印绑定导入表
#include "stdafx.h"
#include <stdlib.h>
#include <string.h>
typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned char BYTE;
#define MZ 0x5A4D
#define PE 0x4550
#define IMAGE_SIZEOF_SHORT_NAME 8
//DOS头
struct _IMAGE_DOS_HEADER {
WORD e_magic; //MZ标记
WORD e_cblp;
WORD e_cp;
WORD e_crlc;
WORD e_cparhdr;
WORD e_minalloc;
WORD e_maxalloc;
WORD e_ss;
WORD e_sp;
WORD e_csum;
WORD e_ip;
WORD e_cs;
WORD e_lfarlc;
WORD e_ovno;
WORD e_res[4];
WORD e_oemid;
WORD e_oeminfo;
WORD e_res2[10];
DWORD e_lfanew; //PE文件真正开始的偏移地址
};
//标准PE头
struct _IMAGE_FILE_HEADER {
WORD Machine; //文件运行平台
WORD NumberOfSections; //节数量
DWORD TimeDateStamp; //时间戳
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader; //可选PE头大小
WORD Characteristics; //特征值
};
//数据目录
struct _IMAGE_DATA_DIRECTORY{
DWORD VirtualAddress;
DWORD Size;
};
//可选PE头
struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; //文件类型
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode; //代码节文件对齐后的大小
DWORD SizeOfInitializedData; //初始化数据文件对齐后的大小
DWORD SizeOfUninitializedData; //未初始化数据文件对齐后大小
DWORD AddressOfEntryPoint; //程序入口点(偏移量)
DWORD BaseOfCode; //代码基址
DWORD BaseOfData; //数据基址
DWORD ImageBase; //内存镜像基址
DWORD SectionAlignment; //内存对齐粒度
DWORD FileAlignment; //文件对齐粒度
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage; //文件装入虚拟内存后大小
DWORD SizeOfHeaders; //DOS、NT头和节表大小
DWORD CheckSum; //校验和
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve; //预留堆栈大小
DWORD SizeOfStackCommit; //实际分配堆栈大小
DWORD SizeOfHeapReserve; //预留堆大小
DWORD SizeOfHeapCommit; //实际分配堆大小
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes; //目录项数目
_IMAGE_DATA_DIRECTORY DataDirectory[16]; //数据目录
};
//NT头
struct _IMAGE_NT_HEADERS {
DWORD Signature; //PE签名
_IMAGE_FILE_HEADER FileHeader;
_IMAGE_OPTIONAL_HEADER OptionalHeader;
};
//节表
struct _IMAGE_SECTION_HEADER{
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //节表名
union{
DWORD PhysicalAddress;
DWORD VirtualSize; //内存中未对齐大小
}Misc;
DWORD VirtualAddress; //该节在内存中偏移地址
DWORD SizeOfRawData; //该节在硬盘上文件对齐后大小
DWORD PointerToRawData; //该节在硬盘上文件对齐后偏移地址
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics; //该节特征属性
};
//绑定导入表
struct _IMAGE_BOUND_IMPORT_DESCRIPTOR{
DWORD TimeDateStamp; //时间戳
WORD OffsetModuleName; //DLL的名字RVA(加第一个结构中RVA才是字符串真正RVA,详见下面)
WORD NumberOfModuleForwarderRefs; //这个绑定导入表结构后面还有几个_IMAGE_BOUND_FORWARDER_REF这种结构
}; //绑定导入表有很多这种结构或者_IMAGE_BOUND_FORWARDER_REF这种结构,最后如果有sizeof(_IMAGE_BOUND_IMPORT_DESCRIPTOR)个0,表示绑定导入表结束
struct _IMAGE_BOUND_FORWARDER_REF{
DWORD TimeDateStamp; //时间戳
WORD OffsetModuleName; //对应DLL的名字
WORD Reserved; //保留(未使用)
};
/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
int compute_file_size(char* filePath){
FILE* fp = fopen(filePath,"rb");
if(!fp){
printf("打开文件失败");
exit(0);
}
fseek(fp,0,2);
int size = ftell(fp);
//fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了
fclose(fp);
return size;
}
/*将文件读入FileBuffer函数
参数:文件绝对路径
返回值:FileBuffer起始地址
*/
char* to_FileBuffer(char* filePath){
FILE* fp = fopen(filePath,"rb");
if(!fp){
printf("打开文件失败");
exit(0);
}
int size = compute_file_size(filePath);
char* mp = (char*)malloc(sizeof(char) * size); //分配内存空间
if(!mp){
printf("分配空间失败");
fclose(fp);
exit(0);
}
int isSucceed = fread(mp,size,1,fp);
if(!isSucceed){
printf("读取数据失败");
free(mp);
fclose(fp);
exit(0);
}
fclose(fp);
return mp;
}
/*虚拟内存偏移地址->文件偏移地址函数
参数:FileBuffer起始地址,RVA地址值
返回值:RVA对应的FOA,返回0则表示非法RVA
*/
DWORD RVA_to_FOA(char* fileBufferp,DWORD RVA){
_IMAGE_DOS_HEADER* _image_dos_header = NULL;
_IMAGE_FILE_HEADER* _image_file_header = NULL;
_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
_IMAGE_SECTION_HEADER* _image_section_header = NULL;
_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
_image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header +
_image_file_header->SizeOfOptionalHeader);
bool flag = 0; //用来判断最终RVA值是否在节中的非空白区
if(RVA < _image_section_header->VirtualAddress)
return RVA;
for(int i = 0;i < _image_file_header->NumberOfSections;i++){
if(RVA >= _image_section_header->VirtualAddress
&& RVA < _image_section_header->VirtualAddress + _image_section_header->Misc.VirtualSize){
flag = 1;
break;
}else{
_image_section_header++;
}
}
if(!flag)
return 0;
DWORD mem_offset_from_section = RVA - _image_section_header->VirtualAddress;
return _image_section_header->PointerToRawData + mem_offset_from_section;
}
/*打印绑定导入表
参数:需要打印的PE文件绝对路径
返回值:无
*/
void boundImportTable_print(char* filePath){
char* fileBufferp = to_FileBuffer(filePath);
_IMAGE_DOS_HEADER* _image_dos_header = NULL;
_IMAGE_FILE_HEADER* _image_file_header = NULL;
_IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL;
_IMAGE_DATA_DIRECTORY* _image_data_directory = NULL;
_IMAGE_BOUND_IMPORT_DESCRIPTOR* _image_bound_import_descriptor = NULL;
_IMAGE_BOUND_FORWARDER_REF* _image_bound_forwarder_ref = NULL;
_image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp;
_image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4);
_image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20);
_image_data_directory = (_IMAGE_DATA_DIRECTORY*)(_image_optional_header->DataDirectory + 11); //指向绑定导入表数据目录项
if(_image_data_directory->VirtualAddress == 0){
printf("此文件没有绑定导入表");
getchar();
exit(0);
}
_image_bound_import_descriptor = (_IMAGE_BOUND_IMPORT_DESCRIPTOR*)(fileBufferp + RVA_to_FOA(fileBufferp,_image_data_directory->VirtualAddress));
DWORD bound_import_table_RVA = (DWORD)_image_data_directory->VirtualAddress; //记录下绑定导入表起始RVA
//打印绑定导入表
printf("\n***************绑定导入表***************\n");
while(1){
//根据绑定导入表结构中是否为全0判断绑定导入表是否结束
for(int i = 0;i < sizeof(*_image_bound_import_descriptor);i++){
if(*((char*)_image_bound_import_descriptor) != 0){
break;
}
}
if(i == sizeof(*_image_bound_import_descriptor)){
break;
}
printf("OffsetModuleName:%04X--->%s\n",_image_bound_import_descriptor->OffsetModuleName,fileBufferp + RVA_to_FOA(fileBufferp,_image_bound_import_descriptor->OffsetModuleName + bound_import_table_RVA));
printf("TimeDateStamp:%08X\n",_image_bound_import_descriptor->TimeDateStamp);
printf("NumberOfModuleForwarderRefs:%04X\n",_image_bound_import_descriptor->NumberOfModuleForwarderRefs);
//打印_IMAGE_BOUND_FORWARDER_REF结构
if(_image_bound_import_descriptor->NumberOfModuleForwarderRefs != 0){
printf("-----ModuleForwarders-----\n");
for(i = 0;(WORD)i < _image_bound_import_descriptor->NumberOfModuleForwarderRefs;i++){
_image_bound_forwarder_ref = (_IMAGE_BOUND_FORWARDER_REF*)(_image_bound_import_descriptor + 1 + i);
printf("OffsetModuleName:%04X--->%s\n",_image_bound_forwarder_ref->OffsetModuleName,fileBufferp + RVA_to_FOA(fileBufferp,_image_bound_forwarder_ref->OffsetModuleName + bound_import_table_RVA));
printf("TimeDateStamp:%08X\n",_image_bound_forwarder_ref->TimeDateStamp);
printf("Reserved(未使用):%04X\n\n",_image_bound_forwarder_ref->Reserved);
}
}
printf("\n");
_image_bound_import_descriptor = (_IMAGE_BOUND_IMPORT_DESCRIPTOR*)(_image_bound_import_descriptor + 1 + _image_bound_import_descriptor->NumberOfModuleForwarderRefs);
}
}
int main(int argc, char* argv[])
{
char* filePath = "D:/C-language/file/notepad.exe";
boundImportTable_print(filePath);
return 0;
}
验证一下:使用LordPE打开
notepad.exe
,打开绑定导入表,与VC6输出结果比对:(由于截图受限,这里选择ADVAPI32.dll
和KERNEL32.dll
对应的绑定导入表结构进行验证)