1、定位导出表
可选pe头中的最后一个字段:数据目录项
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
如何找到这个结构前面的文章已经说过。而且这个 Size 字段不一定是真实的。
上面 VirtualAddress 字段 作为 RVA, 需转换成 FOA 然后加上 我们以文件方式打开的 pFileBuffer ,才能定位到真正的 导出表。(这种 RVA 寻址前面也用到不少了,应该很熟悉了)
下文及以后在数据目录表中出现的所有地址,全都为 RVA
2、真正的导出表结构(滴水三期课件)
- 其中 最左的 AddressOfFunctions 就是记录了前面动态库用到的 每个导出函数 的
所在地址,但仍然是RVA,找到这个绝对地址就能调用到函数。因为是地址,所以表宽为4字节,表项数为 NumberOfFunctions - 最右的 AddressOfNames 表 记录了 导出函数名字的 RVA ,需要将该 RVA
转换成绝对地址(先转FOA在加上当前FileBuffer基址)才能找到真正的函数名的字符串。即前面动态库被名称粉碎或没有粉碎的名字,就存在这个RVA 指向的地方。具体如下图:
- 要注意:函数的真正的名字在文件中如上图所示,位置是不确定的。但函数名称表中是按名字排序的,即A开头的函数在AddressOfNames 排在最前面。但AXX这个名字字符串的位置,可能排在BXX后面.
动态库中提到 可以导出无名字函数,如果无名字那就不会存在名称表中,即不包括在 NumberOfNames 里,名称表不会存它的数据也不会有位置给它。 - 中间的序号表 AddressOfNameOrdinals ,表项宽度为2字节;该表中存储的内容 + Base =
函数的导出序号;Base 即是 导出表_IMAGE_EXPORT_DIRECTORY 结构体中的一个字段。记录了导出函数起始序号。
如何根据函数名获取到函数地址?(这些事都是操作系统做的事,当然我们懂了原理即可以绕开操作系统自己做)
见下图,遍历名称表内的字符串,逐一匹配,比如匹配到下标为 2 的名称,则同样取下标为 2 的序号表元素。该元素是4,则直接以4为下标,取出函数地址表中的函数地址。(注意这全部的操作都和 base 字段无关!全部都只是以这三个表记录内容来操作)
3、问题总结
1)为什么要分成3张表?
函数导出的个数与函数名的个数未必一样(存在不以名字导出的函数),所以要把函数地址表和函数名称表分开;
2)函数地址表是不是一定大于函数名称表?
未必,一个相同的函数地址可能有多个名字;
3)如何根据函数名字获取一个函数的地址?
定位到函数名称表 --》循环对比,并记录对应的序号 --》找到后根据第二步记录的序号,遍历序号表 --》根据序号表对应的值,取地址表中对应的函数地址;
4)如何根据函数的导出序号获取一个函数的地址?
已知序号减去Base,然后查询函数地址表即可获得;
4、代码实现:
- 遍历导出表
void ExportTablePrint(PVOID pFileBuffer)
{
//指针定义:pDos,pNT
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pFileBuffer;
PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDos->e_lfanew);
//定位导出表:
PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)(
(DWORD)pFileBuffer + RvaToFoa(pFileBuffer, pNT->OptionalHeader.DataDirectory[0].VirtualAddress)
);//pNT->OptionalHeader.DataDirectory
//定位地址表,名称表,序号表,每打印一个函数名,要连同他的序号和地址都打印出来
DWORD* addrFunctions = (DWORD*)(
(DWORD)pFileBuffer + RvaToFoa(pFileBuffer, pExportDir->AddressOfFunctions)
);
DWORD* addrNames = (DWORD*)((DWORD)pFileBuffer + RvaToFoa(pFileBuffer, pExportDir->AddressOfNames));
WORD* addrOrdinals = (WORD*)((DWORD)pFileBuffer + RvaToFoa(pFileBuffer, pExportDir->AddressOfNameOrdinals));
cout << "\n********输出函数序号表的值********" << endl;
for (int i = 0; i < pExportDir->NumberOfFunctions; i++)
{
printf("\t%04x\n", addrOrdinals[i]);
}
cout << "\n**********输出函数名称表************\n" << endl;
for (int i = 0; i < pExportDir->NumberOfNames; i++)
{
printf("\t%s\n", (DWORD)pFileBuffer + RvaToFoa(pFileBuffer, addrNames[i]));
}
cout << "\n**********输出函数地址表************\n" << endl;
for (int i = 0; i < pExportDir->NumberOfFunctions; i++)
{
printf("\t%08x\n", addrFunctions[i]);
}
cout << "\n**********序号表-地址表-名称表************\n" << endl;
DWORD i = 0;
for (; i < pExportDir->NumberOfFunctions; i++)
{
DWORD j = 0;
if (addrFunctions[i] == 0)//这里跳过了地址为0的函数
continue;
//i每增加1都要遍历NumberOfNames长度找到序号表中的对应的值,此时序号表的值仅仅是为了验证地址表中的序号是否在序号表中有位置
for (; j < pExportDir->NumberOfNames; j++)//内循环从序号表中找到等于地址表第i个函数的序号的值
{
if (addrOrdinals[j] == i)
{//找到了序号表中等于地址表序号的值
printf("\t%04x\t%08x\t%s\n", i, addrFunctions[i], (DWORD)pFileBuffer + RvaToFoa(pFileBuffer, addrNames[j]));
break;
}
}
}
}
- 根据函数名找到导出表中对应的函数地址
//**************************************************************************
//GetFunctionAddrByName:根据名字找到导出表中的函数地址
//参数说明:
//pFileBuffer:FileBuffer指针
//str: 函数名指针
//AddressOfNamesFOA:名称表的文件偏移
//nameFoa:名称表的值--具体函数名的RVA
//返回值说明:
//返回导出表中的函数地址
//**************************************************************************
PVOID GetFunctionAddrByName(PVOID pFileBuffer, char str[])
{
PIMAGE_DOS_HEADER pDOS = (PIMAGE_DOS_HEADER)pFileBuffer;
PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDOS->e_lfanew);
PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pNT + 4);
PIMAGE_OPTIONAL_HEADER pOptional = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFileHeader + sizeof(IMAGE_FILE_HEADER));
PIMAGE_EXPORT_DIRECTORY pExportFunctionTable = (PIMAGE_EXPORT_DIRECTORY)(
(DWORD)pFileBuffer + RvaToFoa(pFileBuffer, pNT->OptionalHeader.DataDirectory[0].VirtualAddress)
);
DWORD AddressOfNamesFOA = RvaToFoa(pFileBuffer, pExportFunctionTable->AddressOfNames);//导出表的FOA
DWORD nameFoa = 0;
char* name = NULL;
int i = 0;
for (; i < pExportFunctionTable->NumberOfNames; i++)
{ //名称表里面存储的是函数名的RVA,进行轮询比较
nameFoa = RvaToFoa(pFileBuffer, *(PDWORD)((DWORD)pFileBuffer + AddressOfNamesFOA));
name = (char*)((DWORD)pFileBuffer + nameFoa);
if (!strcmp(str, name))
break;
AddressOfNamesFOA += 4;//地址表元素宽度为4,每加4跳到下一个地址
}
if (i == pExportFunctionTable->NumberOfNames)
{
printf("找不到名字为:%s的函数!\n", str);
return 0;
}
/*
以下操作意味着已经找到了对应的函数名,其中i记录的是名称表的第几个,
再用i的值去序号表查询,最后用序号表的值寻到函数地址
*/
DWORD AddressOfNameOrdinalsFOA = RvaToFoa(pFileBuffer, pExportFunctionTable->AddressOfNameOrdinals);
WORD ordinal = *(PWORD)((DWORD)pFileBuffer + AddressOfNameOrdinalsFOA + i * 2);
printf("函数:%s 在名称表的位置:%d 在序号表的值:%d\n", str, i, ordinal);
DWORD AddressOfFunctionsFOA = RvaToFoa(pFileBuffer, pExportFunctionTable->AddressOfFunctions);
DWORD* AddressOfFunctions = (DWORD*)((DWORD)pFileBuffer + AddressOfFunctionsFOA);
printf("函数%s的地址(rva)是%x\n", str, AddressOfFunctions[ordinal]);
return (PVOID)*(PDWORD)((DWORD)pFileBuffer + AddressOfFunctionsFOA + ordinal * 4);
}
- 通过导出序号找到函数地址
//GetFunctionAddrByOrdinals(pFileBuffer,函数名导出序号)
//**************************************************************************
//GetFunctionAddrByOrdinals:根据序号找到导出表中的函数地址
//参数说明:
//pFileBuffer:FileBuffer指针
//ord:函数序号
//AddressOfNameOrdinalsFOA:序号表的文件偏移
// AddressOfFunctionsFOA:地址表的文件偏移
//返回值说明:
//返回导出表中的函数地址
//**************************************************************************
PVOID GetFunctionAddressByOrdinals(PVOID pFileBuffer, WORD ord)
{
PIMAGE_DOS_HEADER pDOS = (PIMAGE_DOS_HEADER)pFileBuffer;
PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDOS->e_lfanew);
PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pNT + 4);
PIMAGE_OPTIONAL_HEADER pOptional = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFileHeader + sizeof(IMAGE_FILE_HEADER));
PIMAGE_EXPORT_DIRECTORY pExportFunctionTable = (PIMAGE_EXPORT_DIRECTORY)(
(DWORD)pFileBuffer + RvaToFoa(pFileBuffer, pNT->OptionalHeader.DataDirectory[0].VirtualAddress)
);
DWORD AddressOfNameOrdinalsFOA = NULL;
DWORD AddressOfFunctionsFOA = NULL;
//判断传入的序号是否在序号表的范围之内,不在则结束;在则继续往下走
if (ord - pExportFunctionTable->Base >= pExportFunctionTable->NumberOfNames || ord - pExportFunctionTable->Base < 0)
{
printf("找不到序号为%d的函数\n", ord);
return 0;
}
//获得函数地址表的文件偏移
AddressOfFunctionsFOA = RvaToFoa(pFileBuffer, pExportFunctionTable->AddressOfFunctions);
printf("序号表Base:%d\n", pExportFunctionTable->Base);
printf("导出序号为:%d的导出函数地址为%08x\n", ord, (PVOID) * (PDWORD)((DWORD)pFileBuffer + AddressOfFunctionsFOA + (ord - pExportFunctionTable->Base) * 4));
return (PVOID) * (PDWORD)((DWORD)pFileBuffer + AddressOfFunctionsFOA + (ord - pExportFunctionTable->Base) * 4);
}