>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
在可选PE头偏移0x60即得到数据目录项,其结构如下:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
以上结构中的VirtualAddress是后面的十六个表结构。
数据目录项的第一个结构,就是导出表,结构如下:
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; // 未使用
DWORD TimeDateStamp; // 时间戳
WORD MajorVersion; // 未使用
WORD MinorVersion; // 未使用
DWORD Name; // 指向该导出表文件名字符串
DWORD Base; // 导出函数起始序号
DWORD NumberOfFunctions; // 所有导出函数的个数
DWORD NumberOfNames; // 以函数名字导出的函数个数
DWORD AddressOfFunctions; // 导出函数地址表RVA
DWORD AddressOfNames; // 导出函数名称表RVA
DWORD AddressOfNameOrdinals; // 导出函数序号表RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
Characteristics 00 00 00 00 ,未使用
TimeDateStamp 指示创建日期的时间戳
MajorVersion 00,未定义
MinorVersion 00,未定义
Name 包含这个DLL的名称的ASCII字符串的RVA
Base 导出函数的起始序数。在后面要获得某个函数的导出序数,需要把这个域的值与AddressOfNameOrdinals数组中相应的元素的值相加
NunberOfFunctions AddressOfFunction数组中的元素的数目。这个值也是这个模块导出的函数的数目。理论上,这个值可能与NumberOfNames域中的值不同,但在实际上它们总是相同
NumberOfNames AddressOfNames数组中的元素数目
*AddressOfFunctions 这个域是一个RVA,并且指向一个函数地址数组。每个导入函数的入口地址(RVA)
*AddressOfNames 这个域是一个RVA,并且指向一个字符串指针数组。导出函数的名称的字符创
*AddressOfNameOrdinals 这个域是一个RVA,并且指向一个WORD类型的数组。导出函数的序号
函数导出的个数与函数名的个数未必一样.所以要将函数地址表和函数名称表分开.。其次,导出一个函数需要函数的名称,相应的地址和导出序数这三部分的内容。总共有三个这样的数组(AddressOfFunctions , AddressOfNames , AddressOfOrdinals),他们是并列的。如下图:
下面来说说查找某个函数的过程,有两种方式
1).先查AddressOfNames数组下找到函数名所在的索引位置,记为P。在AddressOfOrdinals数组中索引为P的位置得到存放的值,如4,。最后,在AddressOfFunctions数组中索引下标为4中存放的地址即为要找的函数地址(RVA)
2).函数编号-Base域中的值,如2。再到AddressOfFunctions数组中索引下标为2中存放的地址即为要找的函数地址(RVA)
理论部分就到这里,下面用手动的方法来定位导入表以及其相对的域
1.定位_IMAGE_DATA_DIRECTORY DataDirectory
因为_IMAGE_DATA_DIRECTORY DataDirectory在可选PE头偏移0x60出的位置,我们可以偏移得到。但是,可以使用节表紧接着可选PE头的特点,定位_IMAGE_DATA_DIRECTORY DataDirectory。
_IMAGE_DATA_DIRECTORY DataDirectory中有16个表目,一个表目占8个字节,OK。算一下,在16进制编辑器中上移8行即可定位。如图:
2.前8个字节对应的就是导入表部分,可得导入表的RVA为0002AD80。下面开始进行转换,将RVA转为FOA,即相对虚拟地址到文件偏移地址的转换
有图可知,RVA0002AD80处于.rdata节表,偏移为2AD80-29000=1D80,则在文件中的偏移(FOA)为17600+1D80=19380,。好了,得到了导入表在文件中的偏移地址,如图:
3.到这里,得到了导入表。要铭记的就是,下面的3个DWORD所对应的地址是RVA,也要进行上面的转换,转换后,即可得到相应的数组在文件中的偏移地址。
(RVA)0002ADA8 -> *AddressOfFunctions ->(FOA)193A8
(RVA)0002ADB8 -> *AddressOfNames ->(FOA)193D8
(RVA)0002ADC8 -> *AddressOfNameOrdials ->(FOA)193C8
相应的在文件中的位置如图:(图中的位置有误,AddressOfNames蓝色部分)
绿色(193A8):对应*AddressOfFunctions,可以看见其中的4个DWORD数据,分别为
[0] 00001019
[1] 00001014
[2] 0000100F
[3] 00001005
黄色(193C8):对应*AddressOfNameOrdinals,可以看到其中的4个WORD类型的数据,分别为
[0] 0000
[1] 0001
[2] 0002
[3] 0003
蓝色(193D8):对应*AddressOfNames,在后面可以看到,就是相应的函数名所对应的ASCII
[0] _Div@8
[1] _Mul@8
[2] _Plus@8
[3] _Sub@8
差不多手动解析完了吧!
在上面找到的结果中,如果要查找名为 _Plus@8 的函数的地址,其对应在数组中的索引为2,则在AddressOfNameOrdinals对应索引为2中的到0002,
再到AddressOfFunctions下查找索引2中所存的值100F即为所找函数名的地址(RVA)
下面是相应的代码实现部分
RVAtoFOA:
VOID RVAtoFOA(IN LPVOID pFileBuffer, IN DWORD dwRva, OUT LPVOID* pFOA)
{
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER pOptionalHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
LPVOID pTempNewBuffer = NULL;
DWORD dwRawValue = 0;
if (IsPEFile(pFileBuffer) == FALSE) {
printf("Error: is not a PE file!\n");
}
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
pNTHeader = (PIMAGE_NT_HEADERS)((PBYTE)pFileBuffer + pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)((PBYTE)pNTHeader + sizeof(pNTHeader->Signature));
pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((PBYTE)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((PBYTE)pOptionalHeader + pPEHeader->SizeOfOptionalHeader);
PIMAGE_SECTION_HEADER ptempSectionHeader = pSectionHeader;
bool flag = FALSE;
for (int i = 1; i <= pPEHeader->NumberOfSections; i++, ptempSectionHeader++) {
if (dwRva > ptempSectionHeader->VirtualAddress && \
dwRva < (ptempSectionHeader->VirtualAddress + ptempSectionHeader->Misc.VirtualSize)) {
flag = TRUE;
break;
}
}
if (!flag) {
printf("address error\n");
return;
}
dwRawValue = dwRva - ptempSectionHeader->VirtualAddress + ptempSectionHeader->PointerToRawData;
*pFOA = (PBYTE)pFileBuffer + dwRawValue;
}
main函数相关代码段:
///
printf(";>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
PIMAGE_DATA_DIRECTORY pED = GetOptionalHeader(stMapFile.ImageBase)->DataDirectory;
DWORD addr = pED[0].VirtualAddress;
RVAtoFOA(stMapFile.ImageBase, pED[0].VirtualAddress, &pFOA);
PIMAGE_EXPORT_DIRECTORY pEPT = (PIMAGE_EXPORT_DIRECTORY)pFOA;
RVAtoFOA(stMapFile.ImageBase, pEPT->Name, &pFOA);
printf(";\t\t\tCharacteristics:[%s] \t\t\t;\n",(char*)pFOA);
printf(";\t\t\tTimeDateStamp:[0x%X] \t\t\t;\n",pEPT->TimeDateStamp);
printf(";\t\t\tNumberOfFunctions:[%d] \t\t\t\t;\n",pEPT->NumberOfFunctions);
printf(";\t\t\tNumberOfNames:[%d] \t\t\t\t;\n",pEPT->NumberOfNames);
printf(";\t\t\tBase:[%d] \t\t\t\t\t;\n",pEPT->Base);
printf(";\t\t\t\t\t\t\t\t\t;\n");
// AddressOfFunctions
printf(";\t\t\tAddressOfFunctions: \t\t\t\t;\n");
for (unsigned int i = 0; i < pEPT->NumberOfFunctions; ++i) {
RVAtoFOA(stMapFile.ImageBase, (DWORD)((PDWORD)pEPT->AddressOfFunctions + i), &pFOA);
printf(";\t\t\t[%d : 0x%08X]\t\t\t\t;\n", i, *(PDWORD)pFOA);
}
printf(";\t\t\t\t\t\t\t\t\t;\n");
// AddressOfNames
printf(";\t\t\tAddressOfNames: \t\t\t\t;\n");
for (unsigned int j = 0; j < pEPT->NumberOfNames; ++j) {
RVAtoFOA(stMapFile.ImageBase, (DWORD)((PDWORD)pEPT->AddressOfNames + j), &pFOA);
RVAtoFOA(stMapFile.ImageBase,*(PDWORD)pFOA,&pFOA);
printf(";\t\t\t[%d : 0x%08s]\t\t\t\t;\n", j, (LPSTR)pFOA);
}
printf(";\t\t\t\t\t\t\t\t\t;\n");
// AddressOfNameOrdinals
printf(";\t\t\tAddressOfNameOrdinals: \t\t\t\t;\n");
for (unsigned int K = 0; K < pEPT->NumberOfFunctions; ++K) {
RVAtoFOA(stMapFile.ImageBase, (DWORD)((PWORD)pEPT->AddressOfNameOrdinals + K), &pFOA);
//RVAtoFOA(stMapFile.ImageBase,*(PDWORD)pFOA,&pFOA);
printf(";\t\t\t[%d : 0x%d]\t\t\t\t\t;\n", K, *(PWORD)pFOA);
}
printf(";>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
结果输出:
END