滴水逆向笔记系列-PE总结4-31.重定位表-32.IAT表_导入表-33.绑定导入表

第三十一课 重定位表

一.引入重定位表

1.程序加载过程

程序加载后,操作系统会给程序分4GB虚拟内存,

  • 先装载自身的.exe:如先把ipmsg.exe拉伸贴到ImageBase(0x00400000),分配空间大小为SizeOfImage(0x3D000)

但并不是所有文件的ImageBase都是0x400000,这个值是可以修改的:打开VC->右键你的项目->setting->选择Link->Category设置为Output->在Base address选项中就可以自定义ImageBase,之后这个程序编译以后,ImageBase就变了

  • 再装载需要用到的.dll:如把ws2help.dll拉伸贴到它的ImageBase(0x71A10000),分配空间大小为SizeofImage(0x8000)。其他.dll也是如此

最后把EIP指向EOP(AddressOfEntryPoint),这个程序就可以执行了

2.问题一:dll装载地址

一般情况下一个PE文件自身的.exe的ImageBase很少会和别的PE文件ImageBase发生冲突
但是.dll就不一定了:默认情况下DLL的ImageBase为0x10000000,所以如果一个程序要用的DLL没有合理的修改分配装载起始地址,就可能出现多个DLL的ImageBase都是同一个地址,造成装载冲突
但装载冲突时,会根据对齐往后存放,此时地址和他的ImageBase已经不一样了

3.问题二:编译后的绝对地址

PE文件中的全局变量都是编译后就写死在,他们的地址:ImageBase + RVA,
但如果这个PE文件装载时没有按照预定的ImageBase装入,出现问题一,这时候按照绝对地址去寻址就会找不到全局变量,dll对外提供的函数在编译完函数地址也是写死的

4.引入

  • 但PE文件出现问题一时,就需要重定位表记录下来,那些地方的数据需要修改,重新定位,确保系统能够正确找到这些数据
  • 为什么exe很多不提供重定位表,dll会提供?因为一个PE文件的exe一般只有一个,且是最先装载的,所以装载位置一般都是ImageBase,但是dll有很多,这时候就经常需要重定位表确保不出错

二.重定位表

1.重定位表结构

struct _IMAGE_BASE_RELOCATION{
	DWORD VirtualAddress;
	DWORD SizeOfBlock;
    //一堆数据...(如果是最后一个这种结构,就只有RVA和SizeOfBlock,且都为0)
};

image.png

0x00 SizeOfBlock

表示这个块的大小,包括具体项

0x01 VirtualAddress

这个值得和具体项结合起来了解,当前这一个块的数据,每一个低12位的值+VirtualAddress 才是真正需要修复的数据的RVA
真正的RVA = VirtualAddress + 具体项的低12位

0x02 具体项

  • 内存中的页大小是1000H 也就是说2的12次方 就可以表示一个页内所有的偏移地址
  • 具体项的宽度是16字节 高四位,代表类型:值为3 代表的是需要修改的数据 值为0代表的是用于数据对齐的数据,可以不用修改.也就是说 我们只关注高4位的值为3的就可以了.
  • 具体项的原理和小表差不多,都是为了节省空间

2.修复重定位表

#include "Currency.h"
#include "windows.h"
#include "stdio.h"
 
VOID h3263()		//修改ImageBase值,再修改重定位表,使得修改后的文件仍然好使
{
	char FilePath[] = "CRACKME.EXE";	//CRACKME.EXE        CrackHead.exe     Dll1.dll		R.DLL	LoadDll.dll
	char CopyFilePath[] = "CRACKMEcopy.EXE";	//CRACKMEcopy.EXE       CrackHeadcopy.exe
	LPVOID pFileBuffer = NULL;				//会被函数改变的 函数输出之一
	LPVOID* ppFileBuffer = &pFileBuffer;	//传进函数的形参
	int SizeOfFileBuffer;
	PIMAGE_DOS_HEADER pDosHeader = NULL;
	PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = NULL;
	PIMAGE_BASE_RELOCATION pRelocationTable = NULL;
	DWORD nameFOA = NULL;
	DWORD AddressOfNamesFOA = NULL;
 
	//增量
	int increment = 0x100000;
 
	SizeOfFileBuffer = ReadPEFile(FilePath, ppFileBuffer);	//pFileBuffer即指向已装载到内存中的exe首部
											/*pFileBuffer = *ppFileBuffer;*/
	if (!SizeOfFileBuffer)
	{
		printf("文件读取失败\n");
		return;
	}
	//Dos头
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;	// 强转 DOS_HEADER 结构体指针
	//可选PE头	  简化后的处理
	pOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pFileBuffer + pDosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
	//ImageBase
	printf("ImageBase:%x", pOptionalHeader->ImageBase);
	//重定向表
	pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pFileBuffer + RVA2FOA(pFileBuffer, pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress));
	printf("RelocationTable VirtualAddress:%x\n", pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
	if (!pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress)
	{
		printf("该文件没有重定向表!\n");
		return;
	}
	int i = 1;
	while (pRelocationTable->VirtualAddress && pRelocationTable->SizeOfBlock)
	{
		printf("第%d个块VirtualAddress:%x\n", i, pRelocationTable->VirtualAddress);
		printf("第%d个块SizeOfBlock:%x\n", i, pRelocationTable->SizeOfBlock);
		printf("第%d个块项数:%d\n", i, (pRelocationTable->SizeOfBlock - 8) / 2);
		pRelocationTable = (PIMAGE_BASE_RELOCATION)(pRelocationTable->SizeOfBlock + (DWORD)pRelocationTable);
		i++;
	}
	//修改ImageBase
	pOptionalHeader->ImageBase = pOptionalHeader->ImageBase + increment;
	printf("ImageBase:%x", pOptionalHeader->ImageBase);
 
	//修改重定位表中的值并输出
	i = 1;
	PWORD pItem;		//重定向表块中的项指针,2字节不断移动
	int NumberOfItems;	//重定向表一块中的项数
	int ItemAdd;		//重定向表一项中表示的地址变量,后续会不断变换为Rva FOA
	//这里pRelocationTable已经是当前绝对地址了
	pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pFileBuffer + RVA2FOA(pFileBuffer, pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress));
	printf("RelocationTable VirtualAddress:%x\n", pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
	while (pRelocationTable->VirtualAddress && pRelocationTable->SizeOfBlock)
	{
		//pRelocationTable->VirtualAddress += increment;
		printf("第%d个块VirtualAddress:%x\n", i, pRelocationTable->VirtualAddress);
		printf("第%d个块SizeOfBlock:%x\n", i, pRelocationTable->SizeOfBlock);
		NumberOfItems = (pRelocationTable->SizeOfBlock - 8) / 2;
		pItem = (PWORD)((DWORD)pRelocationTable + 8);	//这里取出来的是还没加上重定向块中的VirtualAddress的Rva
		
		printf("第%d个块项数:%d\n", i, NumberOfItems);
		for (int j = 0; j < NumberOfItems; j++)
		{
				  //按位与      1234123456789012     //1234123456789012
			//if (((*pItem) & 0b1111000000000000) == 0b0011000000000000)
			if (((*pItem) & 0xF000) == 0x3000)		//这里的3可以用IMAGE_REL_BASED_HIGHLOW代替,不过需要移位而不是与
			{	//F000 3000
				printf("项:%x   ", *pItem);
				ItemAdd = (*pItem) & 0x0FFF;	//这里取出来的是还没加上重定向块中的VirtualAddress的Rva
				ItemAdd = pRelocationTable->VirtualAddress + ItemAdd;	//到这里才是真正的Rva
				printf("项Rva:%x\n", ItemAdd);
				ItemAdd = (DWORD)pFileBuffer + RVA2FOA(pFileBuffer, ItemAdd);	//变为FOA再变为绝对地址才能找到真正的值
				*((PDWORD)ItemAdd) += increment;			//修复这个值
			}
			pItem++;
		}
		pRelocationTable = (PIMAGE_BASE_RELOCATION)(pRelocationTable->SizeOfBlock + (DWORD)pRelocationTable);
		i++;
	}
 
	//保存文件
	MemeryToFile(pFileBuffer, SizeOfFileBuffer , CopyFilePath);
	free(pFileBuffer);
}

作业

1、打印重定位表

  • 第19和20行的下标本来很疑惑为什么有的需要是7,有的是5,结果是因为查找的PE文件是64位的话下标就应该是7,32位就是5
VOID Relocation_Printf(LPVOID pFileBuffer)
{
	PIMAGE_DOS_HEADER pDosHeader = NULL;
	PIMAGE_NT_HEADERS pNTHeader = NULL;
	PIMAGE_FILE_HEADER pFileHeader = NULL;
	PIMAGE_OPTIONAL_HEADER pOptionalHeader = NULL;
	PIMAGE_SECTION_HEADER pSectionHeader = NULL;
	PIMAGE_DATA_DIRECTORY pDataDirHeader = NULL;
	PIMAGE_BASE_RELOCATION pRelocation = NULL;
	DWORD pRelocationTable_RVA = NULL;
	DWORD pRelocationTable_Size = NULL;

	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
	pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4);
	pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFileHeader + 20);
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);
	pDataDirHeader = (PIMAGE_DATA_DIRECTORY)((DWORD)pOptionalHeader->DataDirectory);
	pRelocationTable_RVA = pDataDirHeader[5].VirtualAddress;
	pRelocationTable_Size = pDataDirHeader[5].Size;
	DWORD foa = RVAtoFOA(pFileBuffer, pRelocationTable_RVA);
	pRelocation = (PIMAGE_BASE_RELOCATION)(foa + (DWORD)pFileBuffer);
	
	for (int i = 0; pRelocation->VirtualAddress != 0; i++)
	{
		printf("----------第%d块---------\n", i + 1);
		printf("VirtuallAddress:%08x\n", pRelocation->VirtualAddress);
		printf("SizeOfBlock:%x\n", pRelocation->SizeOfBlock);
		int num = ((DWORD)pRelocation->SizeOfBlock - 0x8) / 2;
		for (int j = 0; j < num; j++)
		{
			printf("第%d项:\n", j + 1);
			printf("属性:%x\n", (*(PWORD)((DWORD)pRelocation + 0x8 + j * 0x2) >> 12));
			printf("重定位具体数:%x\n", (*(PWORD)((DWORD)pRelocation + 0x8 + j * 0x2) & 0x0FFF));
		}
		pRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)pRelocation + pRelocation->SizeOfBlock);
	}

}

2、移动重定位表到新加节

VOID AddSection(LPVOID pFileBuffer)
{
	PIMAGE_DOS_HEADER pDosHeader = NULL;
	PIMAGE_NT_HEADERS pNTHeader = NULL;
	PIMAGE_FILE_HEADER pFileHeader = NULL;
	PIMAGE_OPTIONAL_HEADER pOptionalHeader = NULL;
	PIMAGE_SECTION_HEADER pSectionHeader = NULL;
	PIMAGE_SECTION_HEADER pFirstSection_header = NULL;
	PIMAGE_SECTION_HEADER pLastSection_header = NULL;
	PIMAGE_SECTION_HEADER pNewSection_header = NULL;
	LPVOID pLastDos = NULL;
	BOOL flag = false;
	PIMAGE_DATA_DIRECTORY pDataDirHeader = NULL;
	PIMAGE_BASE_RELOCATION pRelocation = NULL;
	DWORD pRelocationTable_RVA = NULL;
	DWORD pRelocationTable_Size = NULL;

	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
	pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4);
	pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFileHeader + 20);
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);
	pFirstSection_header = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);
	pLastDos = (LPVOID)((DWORD)pFileBuffer + sizeof(IMAGE_DOS_HEADER));
	pDataDirHeader = (PIMAGE_DATA_DIRECTORY)((DWORD)pOptionalHeader->DataDirectory);
	pRelocationTable_RVA = pDataDirHeader[5].VirtualAddress;
	pRelocationTable_Size = pDataDirHeader[5].Size;
	DWORD foa = RVAtoFOA(pFileBuffer, pRelocationTable_RVA);
	pRelocation = (PIMAGE_BASE_RELOCATION)(foa + (DWORD)pFileBuffer);

	//判断头抬升
	for (int i = 0; i < pFileHeader->NumberOfSections; i++, pSectionHeader++)
	{
	}
	pLastSection_header = pSectionHeader;
	pNewSection_header = pLastSection_header + 1;
	PBYTE temp = (PBYTE)pNewSection_header;
	if ((DWORD)pNewSection_header + IMAGE_SIZEOF_FILE_HEADER * 2 <= (DWORD)pFileBuffer + pOptionalHeader->SizeOfHeaders)
	{
		for (int j = 0; j < 80; j++, temp++)
		{
			if (*temp)
			{
				printf("需要进行头抬升\n");
				flag = TRUE;
				break;
			}
		}
	}
	else
	{
		printf("需要进行头抬升\n");
		flag = TRUE;
	}

	//头抬升
	if (flag)
	{
		memcpy(pLastDos,pNTHeader,pOptionalHeader->SizeOfHeaders - ((DWORD)pNTHeader - (DWORD)pFileBuffer));

		pDosHeader->e_lfanew = sizeof(IMAGE_DOS_HEADER);

		//更新头信息
		pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
		pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4);
		pOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);
		pFirstSection_header = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);
		pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);
		for (int i = 0; i < pFileHeader->NumberOfSections; i++, pSectionHeader++)
		{
		}
		pNewSection_header = pSectionHeader;

		memset(pNewSection_header,0,IMAGE_SIZEOF_SECTION_HEADER * 2);
	}
	//修改NumberOfSection
	++pFileHeader->NumberOfSections;

	//修改SizeOfImage
	pOptionalHeader->SizeOfImage = (DWORD)pOptionalHeader->SizeOfImage + 0x1000;

	//复制.text
	memcpy(pNewSection_header, pFirstSection_header, IMAGE_SIZEOF_SECTION_HEADER);

	//修改新节表的属性
	strcpy((char*)pNewSection_header->Name, (char*)".NewT");
	pNewSection_header->Misc.VirtualSize = pOptionalHeader->SectionAlignment;
	DWORD RawSize = pSectionHeader->SizeOfRawData;
	for (int i = 1; RawSize % pOptionalHeader->SectionAlignment != 0; i++, RawSize++)
	{
	}
	pSectionHeader--;
	pNewSection_header->VirtualAddress = pSectionHeader->VirtualAddress + RawSize;
	pNewSection_header->SizeOfRawData = pOptionalHeader->SectionAlignment;
	pNewSection_header->PointerToRawData = pSectionHeader->PointerToRawData + pSectionHeader->SizeOfRawData;
	printf("新节表添加成功\n");

	//------------移动重定位表-----------
	DWORD NewSection_Addr = pNewSection_header->PointerToRawData + (DWORD)pFileBuffer;
	while (pRelocation->VirtualAddress != 0 && pRelocation->SizeOfBlock != 0)
	{
		memcpy((LPVOID)NewSection_Addr, pRelocation, pRelocation->SizeOfBlock);
		NewSection_Addr = ((DWORD)NewSection_Addr + pRelocation->SizeOfBlock);
		pRelocation = (PIMAGE_BASE_RELOCATION)((char*)pRelocation + pRelocation->SizeOfBlock);
		
	}
	memcpy((PVOID)NewSection_Addr,pRelocation,0x8);

}

第三十二课 IAT表和导入表

1.引入IAT表

调用自己写的函数

发现调用自己写的函数后面跟的是一个跳板地址0C710F5h
image.pngimage.png
进去这个地址发现是直接jmp到0C71790h,这个才是内存中函数的真正地址
image.png
image.png

结论:

  • 无论是在文件中还是内存中,call后面的地址都是写死的,所以程序编译后,call的函数地址就都是写死的
  • 敢直接写死是因为自己写的函数本身就编译在自身程序exe里面,exe装载的时候是不会有其他PE文件来争抢位置,所以exe会按照程序的ImageBase正常装载,所以写死的地址不怕冲突

调用DLL中的函数

内存中(运行后)

  • 可以看到我们不是直接call MessageBox函数的地址,而且一个间接寻址:[4070BC],而4070BC这个地址存放的才是MessageBox在内存中的绝对地址77D5050B

image.png
所以在内存中间接寻址的地址已经变成了dll中MessageBox函数的绝对地址

在文件中(运行前)

我们可以把0x4070BC这个地址转换成FOA在文件中中看看程序运行前这个地址存的是什么?如果内存对齐和文件对齐颗粒一样,ImageBase是400000,那么在文件中的偏移就是70BC,在文件中可以看到70BC这个地址存的是MessageBoxA的函数名称字符串的ASCII码

结论:

在使用DLL的函数时,
运行前间接寻址的地址存的是MessageBoxA的函数名称字符串的ASCII码
运行后间接寻址的地址存的是MessageBox函数的绝对地址
程序装载过程:
程序先装载自己的exe到程序的虚拟内存里面,在依次装载各个DLL到程序的虚拟内存中,当DLL在虚拟内存中的位置已经固定了,操作系统才会把call后面间接寻址的地址改成DLL函数在内存中的绝对地址
IAT表的内容在程序执行前和执行后是不一样的

2.导入表

0x00 导入表是什么

  • 导入表包含了IAT表,他们相关性很大
  • 一个记录使用了其他PE文件的函数信息的list
  • 程序通过导入表的信息来装载DLL到虚拟内存中(通过导入表的Name可以知道要装载哪些dll;通过导入表的OriginalFirstThunk和FirstThunk都为0可以找到系统不会加载这个DLL,因为我们没有使用这个dll的任何函数
  • 导入表在数据目录是第二个数据目录结构体

0x01 导入表结构

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;									

image.png
image.png
1)OriginalFirstThunk
RVA,指向INT表(导入名称表),如果想得到内存地址,即用ImageBase + RVA即可;如果想得到文件地址,即把RVA转FOA即可
2)Name
RVA,指向DLL名字字符串所在地址;比如Name指向的DLL名称为“user32.dll\0”,那么这个结构中记录的就是user32.dll中函数的相关信息;所以一个PE文件的导入表有多少中这种结构,就使用了多少个其他DLL中的函数
3)FirstThunk
RVA,指向IAT表(导入地址表)
4)TimeDataStamp
为0(即0x00000000):表示这个导入表结构对应的DLL中的函数绝对地址没有绑定到IAT表中
-1(即0xFFFFFFFF):表示这个导入表结构对应的DLL中函数绝对地址已经绑定到IAT表中

0x02 INT表(导入名称表)

  • 如果INT表的元素最高位为1:那么除去最高位剩下31位的值,就是函数的导出序号
  • 如果INT表的元素最高位为0:那么这整个值是一个RVA指向IMAGE_IMPORT_BY_NAME表中对应的函数名称结构体
struct _IMAGE_THUNK_DATA32{								
    union{								
        BYTE ForwarderString;								
        DWORD Function;								
        DWORD Ordinal;  //序号		
        _IMAGE_IMPORT_BY_NAME* AddressOfData;  //RVA,指向IMAGE_IMPORT_BY_NAME		
    };								
};						

struct _IMAGE_IMPORT_BY_NAME{							
    WORD Hint;  //可能为空(编译器决定);如果不为空,表示函数在导出表中的索引	
    BYTE Name[1];  //函数名称,以0结尾	
};							

_IMAGE_IMPORT_BY_NAME结构体的Name为什么是BYTE呢?因为一般函数名称的宽度是不确定的,所以我们干脆只定义一个字符大小,然后往后遍历,直到遇到字符串结束字符\0就结束

0x03 IAT表(导入地址表)

  • 程序运行前:IAT表的内容和INT表是一样的
  • 程序运行后:IAT表就变成了对应DLL中函数的内存绝对地址
  • IAT表修复过程:遍历到INT表的函数名或者序号后,把其中一个作为参数传给系统函数GetProcAddress(),返回了对应函数名或函数序号在内存中的绝对地址后,传给IAT表对应的位置

作业

1、打印出导入表和IAT表

  • 第48和54行都可以使用>>或者&去取最高位和低31位
VOID ImportTable_Printf(LPVOID pFileBuffer)
{
	PIMAGE_DOS_HEADER pDosHeader = NULL;
	PIMAGE_NT_HEADERS pNTHeader = NULL;
	PIMAGE_FILE_HEADER pFileHeader = NULL;
	PIMAGE_OPTIONAL_HEADER pOptionalHeader = NULL;
	PIMAGE_SECTION_HEADER pSectionHeader = NULL;
	PIMAGE_DATA_DIRECTORY pDataDirHeader = NULL;
	PIMAGE_IMPORT_DESCRIPTOR pImportTbale = NULL;
	BOOL flag = true;
	PIMAGE_THUNK_DATA32 image_thunk_data = NULL;
	PIMAGE_IMPORT_BY_NAME Import_By_Name = NULL;


	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
	pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4);
	pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFileHeader + 20);
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);
	pDataDirHeader = (PIMAGE_DATA_DIRECTORY)(pOptionalHeader->DataDirectory + 1);
	pImportTbale = (PIMAGE_IMPORT_DESCRIPTOR)(RVAtoFOA(pFileBuffer, pDataDirHeader->VirtualAddress) + (DWORD)pFileBuffer);

	
	if (pDataDirHeader->VirtualAddress == 0)
	{
		printf("此exe没有导入表\n");
	}

	printf("-----------导入表-----------\n");
	while (flag)
	{
		
		//Import_By_Name = (PIMAGE_IMPORT_BY_NAME)(RVAtoFOA(pFileBuffer,image_thunk_data->u1.AddressOfData) + (DWORD)pFileBuffer);
		if (pImportTbale->FirstThunk == 0 && pImportTbale->OriginalFirstThunk == 0)
		{
			flag = false;
			break;
		}
		image_thunk_data = (PIMAGE_THUNK_DATA32)(RVAtoFOA(pFileBuffer, pImportTbale->OriginalFirstThunk) + (DWORD)pFileBuffer);

		printf("%s\n", (RVAtoFOA(pFileBuffer, pImportTbale->Name) + (DWORD)pFileBuffer));
		printf("TimeDataStamp:%x\n",pImportTbale->TimeDateStamp);
		printf("OriginalFirstThunk:%x\n", pImportTbale->OriginalFirstThunk);
		printf("FirstThunk:%x\n", pImportTbale->FirstThunk);
		printf("----------INT表---------\n");
		while ((DWORD)image_thunk_data->u1.AddressOfData != 0 && (DWORD)image_thunk_data->u1.Ordinal != 0)
		{
			if (((DWORD)image_thunk_data->u1.Ordinal & 0x80000000) == 0x0)
			{
				Import_By_Name = (PIMAGE_IMPORT_BY_NAME)(RVAtoFOA(pFileBuffer, image_thunk_data->u1.AddressOfData) + (DWORD)pFileBuffer);
				printf("(以函数名方式导出)函数名RVA:%x--->%s\n",image_thunk_data->u1.AddressOfData,Import_By_Name->Name );
				image_thunk_data++;
			}
			if (((DWORD)image_thunk_data->u1.Ordinal >> 31) == 0x1)
			{
				printf("(以序号方式导出)序号:%x\n", (image_thunk_data->u1.Ordinal & 0x7FFFFFFF));
				image_thunk_data++;
			}
		}
		printf("--------------------------\n");

		pImportTbale++;
	}
}

第三十三课 绑定导入表

1.引入绑定导入表

  • 前面引入IAT表时我们说到运行前和运行后的IAT表是不一样的,运行前IAT表和INT表一样,运行后IAT表就是函数绝对地址,但是通过观察其他exe,发现xp的notepad.exe在运行前就已经是直接存入函数在虚拟内存中的绝对地址,这就是绑定导入表

2.优缺点:

  • 优点:程序启动速度快,在程序装载启动时省去了修改IAT表等操作
  • 缺点:程序使用DLL装载时没有按照ImageBase装载,使用到DLL的函数则会出问题

所以一般只有系统自带的程序才敢绑定导入表,因为这些程序使用的DLL都说设计好的,按照ImageBase装载,不会发生冲突,不过win10之后的程序一般也不会有这种东西了,现在计算机的计算能力已经够了

3.绑定导入表

0x00 定位

  • 数据目录的第12个结构,就是绑定导入表数据目录项;再通过绑定导入表数据目录中VirtualAddress字段,RVA转成FOA,定位到绑定导入表地址
  • 绑定导入表的位置就在节表的后面,这就解决了以前新增节的时候为了不动节表后面的数据而去头抬升的坑

0x01 判断

导入表使用到的每个DLL都有一个导入表结构_IMAGE_IMPORT_DESCRIPTOR,里面的时间戳字段TimeDateStamp可以判断这个DLL是否绑定了导入表

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

如果已经绑定了导入表,就需要看另外一个结构**绑定导入表**,里面的TimeDataStamp时间戳,才表示函数地址真正的绑定时间

0x02绑定导入表结构

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表的地址可能就不准确,需要重新获取

2)OffsetModuleName
对应DLL的名字:因为一个程序的导入表结构对应一个使用到的DLL;一个程序的绑定导入表结构也对应一个程序使用到的DLL,这个绑定导入表结构记录了该DLL中函数绝对地址绑定到IAT表的时间戳、该DLL的名字、还有该DLL使用到别的DLL的个数
注意:不管是_IMAGE_BOUND_IMPORT_DESCRIPTOR结构中的OffsetModuleName、还是后面要讲的_IMAGE_BOUND_FORWARDER_REF 结构中的OffsetModuleName,都必须加上绑定导入表起始RVA值,才是这个结构对应DLL名字的真正RVA

3)NumberOfModuleForwarderRefs
因为一个DLL可能还会使用到别的DLL中的函数,所以NumberOfModuleForwarderRefs字段的值是多少,就表明当前绑定导入表对应的DLL还使用了多少个别的DLL,同样表示这个绑定导入表结构后面跟了多少个_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.png

posted @ 2024-03-16 10:39  小新07  阅读(64)  评论(0编辑  收藏  举报