第三十课 导出表
这节课很多参考这位大佬的文章滴水三期:day36.1-导出表_滴水第三期 pe文件 导入导出表-CSDN博客
1.导出表结构
真正的导出表结构如下:
1、name
指向该导出表文件名字符串的RVA,比如一个DBGHELP.dll的PE文件提供函数,那么这个PE文件的Name指向的字符串为dbghelp.dll
2、base
- 导出函数起始序号(最小的序号)
比如有序号为14、6、10、8的导出函数,那么Base的值为6
3.NumberOfFunctions
- 所有导出函数的个数
注意:day35中讲过,这个值是通过导出函数的最大序号 - 最小序号 + 1算出来的;正常来说这个值是多少,那么此PE文件中导出函数的个数就是多少。但是如果使用自定义序号,序号定义时不是连续的,而是中间有空缺的序号,那么此时NumberOfFunctions的值会比实际的定义的导出函数个数多
4.NumberOfNames
以函数名字导出的函数个数:比如以动态链接库的方式导出(注意和只以序号导出函数区分)
如果导出时加了NONAME关键字,那么就不计数
5.AddressOfFunctions
导出函数地址表RVA,即这个地址指向一个表!这个表中记录了此PE文件的所有导出函数的地址
比如使用自定义序号导出函数,即.def的方式导出,定义了序号13、14、16、17、19的导出函数,那么Base的值应为13,那么序号为13的函数相对相对下标就是0,序号14的导出函数相对下标就是1,序号为15的导出函数虽然没有,但是会把位置空出来,只是地址值为NULL,即0x00000000,序号16的导出函数相对下标就是3…以此类推
6.AddressOfNames
- 导出函数名称表RVA(拉伸后的内存地址偏移,所以要先转成FOA),即这个地址指向一个表,这个表中记录的是导出函数的名称字符串地址!!不是直接存储名称(且此字符串地址也是RVA)
注意:
- 如果函数导出时添加了NONAME,即函数没有名称,那么这个表中就不会出现这个函数名地址
- 所以AddressOfNames表中元素个数可能比AddressOfFunctions表中元素个数少!(AddressOfFunctions表中不管导出函数有没有名字,都会有地址)
- 也有可能AddressOfNames表中元素个数比AddressOfFunctions表中元素个数多!因为导出函数时可以让多个不同名字的函数指向同一个函数地址
7.AddressOfNameOrdinals
- 导出函数序号表RVA,即这个地址指向一个表,表中存的是导出函数的相对序号
2.导出表获取函数地址
按函数名字查找函数地址
注意:表里的偏移地址都是RVA,需要转成FOA再去计算才能得出FileBuffer中 的地址
按序号找函数地址
给定的序号 - Base = 相对序号i
然后在AddressOfFunctions
表找出下标为i的元素值就是函数地址
所以按序号查找和序号表一点关系都没有
RVA转换FOA
RVA需要转换FOA的情况就是内存对齐和文件对齐是不一样的,所以需要对齐
1、如果RVA在文件头里面,那么RVA=FOA,因为文件头在内存和文件中展开都是一样的
2、如果RVA不在文件头里,就需要判断在哪个节里
判断节开始位置到节结束位置 我们的RVA是否在这个范围里面,总共分为三步骤:
第一步:指定节.VirtualAddress <= RVA <= 指定节.VirtualAddress + VirtualSize(当前节内存实际大小)
第二步:差值 = RVA - 指定节.VirtualAddress
第三步:FOA = 指定节.PointerToRawData + 差值
作业
1、输出导出表
2、按名字搜索函数地址
3、按序号搜索函数地址
#pragma warning(disable:4996)
#include<stdio.h>
#include<string.h>
#include<windows.h>
#include<malloc.h>
DWORD Read_File(LPVOID* ppFileBuffer)
{
FILE* fp = fopen("E:\\7-zip\\7-zip32.dll","rb");
//FILE* fp = fopen("E:\\notepad1.exe", "rb");
//FILE* fp = fopen("E:\\IPMSG2007.exe", "rb");
if (!fp)
{
printf("打开文件失败");
return 0;
}
fseek(fp, 0, 2);
int File_len = ftell(fp);
fseek(fp, 0, 0);
*ppFileBuffer = malloc(File_len);
if (!*ppFileBuffer)
{
printf("开辟空间失败");
}
size_t t = fread(*ppFileBuffer, File_len, 1, fp);
if (!t)
{
printf("复制失败");
}
fclose(fp);
return File_len;
}
DWORD RVAtoFOA(LPVOID pFileBuffer,DWORD Rva)
{
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 NextSectionHeader = NULL;
DWORD Foa = 0;
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);
if (Rva < pOptionalHeader->SizeOfHeaders)
{
printf("Rva在Header里面\n");
return Rva;
}
NextSectionHeader = pSectionHeader + 1;
for (int i = 1; i < pFileHeader->NumberOfSections; i++, pSectionHeader++, NextSectionHeader++)
{
if (Rva >= pSectionHeader->VirtualAddress && Rva < NextSectionHeader->VirtualAddress)
{
Foa = pSectionHeader->PointerToRawData + (Rva - pSectionHeader->VirtualAddress);
return Foa;
}
}
if (Rva >= pSectionHeader->VirtualAddress )
{
Foa = pSectionHeader->PointerToRawData + (Rva - pSectionHeader->VirtualAddress);
return Foa;
}
else
{
printf("Rav大于sizeofimage!!!\n");
return 0;
}
}
VOID PrintfExport(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_EXPORT_DIRECTORY pExportHeader = 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);
if (pDosHeader->e_magic!=IMAGE_DOS_SIGNATURE) {
printf("不是有效的MZ标志\n");
}
if (pNTHeader->Signature != IMAGE_NT_SIGNATURE)
{
printf("不是有效的PE标志\n");
}
pDataDirHeader = (PIMAGE_DATA_DIRECTORY)((DWORD)pOptionalHeader + 0x60);
printf("--------------导出表----------------\n");
printf("导出表size:%x\n", pDataDirHeader->Size);
printf("导出表VirtualAddress:%x\n", pDataDirHeader->VirtualAddress);
pExportHeader = (PIMAGE_EXPORT_DIRECTORY)(RVAtoFOA(pFileBuffer, (DWORD)(pDataDirHeader->VirtualAddress)) + (DWORD)pFileBuffer);
printf("------------------------------------\n");
printf("Name:%x\n", pExportHeader->Name);
printf("Base:%x\n", pExportHeader->Base);
printf("NumberOfFunctions:%x\n", pExportHeader->NumberOfFunctions);
printf("NumberOfNames:%x\n", pExportHeader->NumberOfNames);
printf("-------------AddrOfFun-------------\n");
PDWORD AddrOfFun = (PDWORD)(RVAtoFOA(pFileBuffer,(DWORD)pExportHeader->AddressOfFunctions) + (DWORD)pFileBuffer);
for (int i = 0; i < pExportHeader->NumberOfFunctions; i++, AddrOfFun++)
{
printf("下标:%d\n", i);
printf("AddressOfFunctions:%x\n", *AddrOfFun);
}
printf("-------------AddrOfNameOrdinal-------------\n");
PWORD AddrOfNameOrdinal = (PWORD)(RVAtoFOA(pFileBuffer, (DWORD)pExportHeader->AddressOfNameOrdinals) + (DWORD)pFileBuffer);
for (int t = 0; t < pExportHeader->NumberOfNames; t++, AddrOfNameOrdinal++)
{
printf("下标:%d\n", t);
printf("序号:%x\n", *(PWORD)AddrOfNameOrdinal);
}
printf("-------------AddrOfName-------------\n");
PDWORD AddrOfName = (PDWORD)(RVAtoFOA(pFileBuffer, (DWORD)pExportHeader->AddressOfNames) + (DWORD)pFileBuffer);
for (int t = 0; t < pExportHeader->NumberOfNames; t++, AddrOfName++)
{
char* name = (char*)(RVAtoFOA(pFileBuffer, *(PDWORD)AddrOfName) + (DWORD)pFileBuffer);
printf("下标:%d\n", t);
printf("AddressOfName:%x\n", AddrOfName);
printf("name:%s\n", name);
}
}
VOID GetAddrByOrdinal(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_EXPORT_DIRECTORY pExportHeader = 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 + 0x60);
pExportHeader = (PIMAGE_EXPORT_DIRECTORY)(RVAtoFOA(pFileBuffer, (DWORD)(pDataDirHeader->VirtualAddress)) + (DWORD)pFileBuffer);
if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
printf("不是有效的MZ标志\n");
}
if (pNTHeader->Signature != IMAGE_NT_SIGNATURE)
{
printf("不是有效的PE标志\n");
}
int num = 0;
printf("请输入你要查询的序号:");
scanf("%d",&num);
int Addr_num = num - pExportHeader->Base ;
PDWORD Addr_Fun = (PDWORD)(RVAtoFOA(pFileBuffer, pExportHeader->AddressOfFunctions) + (DWORD)pFileBuffer);
for (int i = 0; i < Addr_num; i++, Addr_Fun++)
{
}
printf("你查找的函数地址为:%x",*Addr_Fun);
}
VOID GetAddrByName(LPVOID pFileBuffer,const char* str)
{
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_EXPORT_DIRECTORY pExportHeader = NULL;
int flag = 0;
int t = 0;
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 + 0x60);
pExportHeader = (PIMAGE_EXPORT_DIRECTORY)(RVAtoFOA(pFileBuffer, (DWORD)(pDataDirHeader->VirtualAddress)) + (DWORD)pFileBuffer);
if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
printf("不是有效的MZ标志\n");
}
if (pNTHeader->Signature != IMAGE_NT_SIGNATURE)
{
printf("不是有效的PE标志\n");
}
PDWORD Addr_Name = (PDWORD)(RVAtoFOA(pFileBuffer, pExportHeader->AddressOfNames) + (DWORD)pFileBuffer);
for (int i = 0; i < pExportHeader->NumberOfFunctions; i++, Addr_Name++)
{
char* name = (char*)(RVAtoFOA(pFileBuffer, *Addr_Name) + (DWORD)pFileBuffer);
if (strcmp(name, str) == 0)
{
printf("下标为:%d\n", i);
flag = i;
break;
}
}
PWORD Addr_Ordinal = (PWORD)(RVAtoFOA(pFileBuffer, pExportHeader->AddressOfNameOrdinals) + (DWORD)pFileBuffer);
for (int j = 0; j < flag; j++, Addr_Ordinal++)
{
}
int Ordinal = * (PWORD)Addr_Ordinal;
PDWORD Addr_Fun = (PDWORD)(RVAtoFOA(pFileBuffer, pExportHeader->AddressOfFunctions) + (DWORD)pFileBuffer);
for (; t < Ordinal; t++, Addr_Fun++)
{
}
printf("fun下标为:%d\n", t);
printf("查找的函数地址为:%x\n", *Addr_Fun);
}
第三十一课 重定位表
一.引入重定位表
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)
};
0x00 SizeOfBlock
表示这个块的大小,包括具体项
0x01 VirtualAddress
这个值得和具体项结合起来了解,当前这一个块的数据,每一个低12位的值+VirtualAddress 才是真正需要修复的数据的RVA
真正的RVA = VirtualAddress + 具体项的低12位
0x02 具体项
- 内存中的页大小是1000H 也就是说2的12次方 就可以表示一个页内所有的偏移地址
- 具体项的宽度是16字节 高四位,代表类型:值为3 代表的是需要修改的数据 值为0代表的是用于数据对齐的数据,可以不用修改.也就是说 我们只关注高4位的值为3的就可以了.
- 具体项的原理和小表差不多,都是为了节省空间
作业
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
进去这个地址发现是直接jmp到0C71790h,这个才是内存中函数的真正地址
结论:
- 无论是在文件中还是内存中,call后面的地址都是写死的,所以程序编译后,call的函数地址就都是写死的
- 敢直接写死是因为自己写的函数本身就编译在自身程序exe里面,exe装载的时候是不会有其他PE文件来争抢位置,所以exe会按照程序的ImageBase正常装载,所以写死的地址不怕冲突
调用DLL中的函数
内存中(运行后)
- 可以看到我们不是直接call MessageBox函数的地址,而且一个间接寻址:[4070BC],而4070BC这个地址存放的才是MessageBox在内存中的绝对地址77D5050B
所以在内存中间接寻址的地址已经变成了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;
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表示绑定导入表结束