滴水三期:day40.1-绑定导入表

一、引入绑定导入表

  • 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

      image-20230419195635001

    • TimeDataStamp

      • 0(即0x00000000):表示这个导入表结构对应的DLL中的函数绝对地址没有绑定到IAT表中

      • 为**-1**(即0xFFFFFFFF):表示这个导入表结构对应的DLL中函数绝对地址已经绑定到IAT表中

        所以可以通过导入表的时间戳,判断是否有绑定导入表

  • 如果一个导入表结构的TimeDataStamp为**-1**,说明绝对地址已经绑定到IAT表中,那如何知道对应DLL中函数的绝对地址什么时间绑定的?就需要另一个表----绑定导入表,绑定导入表中的TimeDataStamp时间戳,才表示函数地址真正的绑定时间

二、绑定导入表

1.定位

  • 数据目录的第12个结构,就是绑定导入表数据目录项;再通过绑定导入表数据目录中VirtualAddress字段,RVA转成FOA,定位到绑定导入表地址

    201545

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名字字符串紧跟在绑定导入表后面!(这就解决了以前学新增节时,为什么说节表后面如果有数据,不要随便动!)

    233255
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表示绑定导入表结束

    image-20230419205008853

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.dllKERNEL32.dll对应的绑定导入表结构进行验证)

235457
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值