PE文件之旅 第二篇 DLL的秘密 -- 输出表

大家好,现在继续我们的PE文件之旅第二篇, DLL的秘密 -- 输出表。 首先看一下预备知识。输出表在< <加密解密ii> >等资料中并没有太多的笔墨,居我的接触,它确实比较直观,没有象输入表那样复杂的结构,所以这也可能是介绍的少的缘故。关于输出表的结构可以参考< >。 首先定位输出表。在IMAGE_OPTIONAL_HEADER.DataDirectory结构数组中,注意这也是个结构数组。它的每一个元素都是IMAGE_DATA_DIRECTORY结构。该结构定义如下: typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; DataDirectory结构数组的第一个元素描述的就是输出表,第二个元素描述的是输入表,呵呵,这个是我们下一篇研究对象。所以,这里的VirtualAddress就是输出表的偏移地址,Size就不用说了,输出表的大小。 注意:以下代码的一些变量在前一篇文章< >中已经定义过,这里不再重新定义,请大家参考前面的文章。 核心代码: // 首先定位IMAGE_OPTIONAL_HEADER32结构 IMAGE_OPTIONAL_HEADER32 myOptionalHeader; fseek(pFile, (e_lfanew + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER)), SEEK_SET); // 如此定位的原因请参考PE文件结构图 fread(&myOptionalHeader, sizeof(IMAGE_OPTIONAL_HEADER32), 1, pFile); // 数据目录表的第一个元素为输出表 printf("输出表的偏移地址:%08x/n", myOptionalHeader.DataDirectory[0].VirtualAddress); printf("输出表的大小:%04x/n", myOptionalHeader.DataDirectory[0].Size); DWORD dwFileOffset; if (myOptionalHeader.DataDirectory[0].VirtualAddress != 0) { // OnConvertVAToRawA()函数是进行VirtualAddress到文件偏移转换的函数,具体代码见下面 dwFileOffset = OnConvertVAToRawA(myOptionalHeader.DataDirectory[0].VirtualAddress); if (-1 == dwFileOffset) { printf("输出表定位错误/n"); } else { printf("输出表的文件偏移为:%08x/n", dwFileOffset); // ------------------ 这里已经获得了输出表的文件偏移,输出表是由IED数组开始 ------------------ // int nNumberOfFunctions = 0; int nNumberOfNames = 0; // 由输出表的偏移(VA)计算出文件偏移(RawAddress),然后定位到这里就是输出表了 // 输出表由IMAGE_EXPORT_DIRECTORY结构开始, IMAGE_EXPORT_DIRECTORY *pIED = (IMAGE_EXPORT_DIRECTORY *)malloc(sizeof(IMAGE_EXPORT_DIRECTORY)); // Memory 17 fseek(pFile, dwFileOffset, SEEK_SET); fread(pIED, sizeof(IMAGE_EXPORT_DIRECTORY), 1, pFile); printf("IMAGE_EXPORT_DIRECTORY结构如下: /n"); printf("Characteristics:%08x(没有用途,总是为0)/n", pIED->Characteristics); printf("TimeDateStamp:%08x(文件被产生时刻)/n", pIED->TimeDateStamp); printf("MajorVersion:%08x(没有用途,总是为0)/n", pIED->MajorVersion); printf("MinorVersion:%08x(没有用途,总是为0)/n", pIED->MinorVersion); printf("Name:%08x(RVA,指向一个DLL文件名称)/n", pIED->Name); // 读取DLL名称 char chDllName[64] = {0}; // 这里多留点空间给函数名字,防止出错,我所见过的函数名称最长也就30个char,呵呵 // 将指向DLL文件名称的VA转化为文件偏移 DWORD dwTemp = OnConvertVAToRawA(pIED->Name); // 读取DLL文件名称 fseek(pFile, dwTemp, SEEK_SET); fread(chDllName, 64, 1, pFile); printf("DLL文件名称为:%s/n", chDllName); printf("Base:%08x(起始序号)/n", pIED->Base); // 被此模块输出的函数的起始序号 WORD wBase = (WORD)pIED->Base; printf("NumberOfFunctions:%d(输出函数个数)/n", pIED->NumberOfFunctions); // AddressOfFunctions 数组(稍后描述)中的元素个数。此值同时也是输出函数的个数。 // 通常这个值和 NumberOfNames 字段相同,但也可以不同。 nNumberOfFunctions = pIED->NumberOfFunctions; printf("NumberOfNames:%d(以名称输出的函数个数)/n", pIED->NumberOfNames); // AddressOfNames 数组(稍后描述)中的元素个数。此值表示以名称输出的函数个数。 // 通常(但不总是)和输出函数的总数相同。 nNumberOfNames = pIED->NumberOfNames; // 这是一个 RVA (Relative Virtual Address )值,指向一个由函数地址所构成的数组。 // 我所谓的函数地址是指此一模块中的每一个输出函数的进入点的 RVA 值。 printf("AddressOfFunctions:%08x(RVA, 指向一个由函数地址构成的数组)/n", pIED->AddressOfFunctions); // 这里将其转化为文件的偏移地址 DWORD dwAddressOfFunctions = OnConvertVAToRawA(pIED->AddressOfFunctions); // 这是一个 RVA 值,指向一个由字符串指针所构成的数组。 // 字符串的内容是此一模块中的每一个"以名称输出的输出函数"的名称 printf("AddressOfNames:%08x(RVA, 指向一个由字符串指针所构成的数组)/n", pIED->AddressOfNames); // 转化为文件偏移地址 DWORD dwNameAddressOfFunctions = OnConvertVAToRawA(pIED->AddressOfNames); // 这是一个 RVA 值,指向一个 WORD 数组。 // WORD 的内容是此一模块中的每一个"以名称输出的输出函数"的序号。别忘了要加上 Base 字段中的起始序号。 printf("AddressOfNameOrdinals:%08x(RVA, 指向一个DWORD数组)/n", pIED->AddressOfNameOrdinals); // 转化为文件偏移地址 DWORD dwAddressOfNameOrdinals = OnConvertVAToRawA(pIED->AddressOfNameOrdinals); if (pIED != NULL) // release Memory 17 { free(pIED); pIED = NULL; } // 动态开辟内存用来存储函数的VirtualAddress DWORD *pdwAddressOfFunctions = (DWORD *)calloc(nNumberOfFunctions, sizeof(DWORD)); // Memory 18 fseek(pFile, dwAddressOfFunctions, SEEK_SET); fread(pdwAddressOfFunctions, sizeof(DWORD), nNumberOfFunctions, pFile); // 动态开辟内存用来存储函数的RawAddress DWORD *pRowAddressOfFunctions = (DWORD *)calloc(nNumberOfFunctions, sizeof(DWORD)); // Memory 19 for (int i = 0; i < nNumberOfFunctions; i++, pdwAddressOfFunctions++, pRowAddressOfFunctions++) { *pRowAddressOfFunctions = OnConvertVAToRAwA(*pdwAddressOfFunctions); } // 恢复指针 pdwAddressOfFunctions -= nNumberOfFunctions; pRowAddressOfFunctions -= nNumberOfFunctions; // 动态开辟内存用来存储函数名称地址 DWORD *pdwNameAddressOfFunctions = (DWORD *)calloc(nNumberOfNames, sizeof(DWORD)); // Memory 20 fseek(pFile, dwNameAddressOfFunctions, SEEK_SET); fread(pdwNameAddressOfFunctions, sizeof(DWORD), nNumberOfNames, pFile); // VA TO RAWA for (i = 0; i < nNumberOfNames; i++, pdwNameAddressOfFunctions++) { *pdwNameAddressOfFunctions = OnConvertVAToRowA(*pdwNameAddressOfFunctions); } // 恢复指针 pdwNameAddressOfFunctions -= nNumberOfNames; // 二重指针,用来存储函数名称指针,因为函数个数不固定 DWORD **pFunctionsName = (DWORD **)calloc(nNumberOfNames, sizeof(char *)); // Memory 21 for (i = 0; i < nNumberOfNames; i++, pFunctionsName++) { // 这块内存是用来存放函数名称的 char *pchFunctionName = (char *)malloc(sizeof(char) * 64); *pFunctionsName = (DWORD *)pchFunctionName; } // 恢复指针 pFunctionsName -= nNumberOfNames; // 读出函数名称 for (i = 0; i < nNumberOfNames; i++, pdwNameAddressOfFunctions++, pFunctionsName++) { fseek(pFile, *pdwNameAddressOfFunctions, SEEK_SET); fread(*pFunctionsName, 64, 1, pFile); } // 恢复指针 pdwNameAddressOfFunctions -= nNumberOfNames; pFunctionsName -= nNumberOfNames; if (pdwNameAddressOfFunctions != NULL) // release Memory 20 { free(pdwNameAddressOfFunctions); pdwNameAddressOfFunctions = NULL; } // 读出函数的序号 WORD *pwFunctionsOrdinals = (WORD *)calloc(nNumberOfNames, sizeof(WORD)); // Memory 22 fseek(pFile, dwAddressOfNameOrdinals, SEEK_SET); for (i = 0; i < nNumberOfNames; i++, pwFunctionsOrdinals++) { fread(pwFunctionsOrdinals, sizeof(WORD), 1, pFile); } // 恢复指针 pwFunctionsOrdinals -= nNumberOfNames; // 这里需要注意的是AddressOfFunctions所指的数组。这是一个DWORD数组,每一个DWORD都是 // 一个输出函数的RVA地址。每一个输出函数的序号都与函数在此数组中的位置一致。假使起始序号 // 为1,那么输出序号为1者其函数地址在此数组中排在第一个位置。输出序号为2者其函数地址在此 // 函数地址中则排在第二个位置,依次类推。 // 由以上可知,我们必须对AddressOfNameOrdinals所指向的WORD数组进行升序排序,同时进行的还有 // 函数名称存放位置的排序,因为Ordinals和函数名称存放的位置是对应的。 // 这里采用效率较高的快速排序(详细代码见下面) QuickSort(pwFunctionsOrdinals, pFunctionsName, nNumberOfNames); // 打印信息 for (i = 0; i < nNumberOfNames; i++, pwFunctionsOrdinals++, pFunctionsName++, pRowAddressOfFunctions++, pdwAddressOfFunctions++) { printf("Ordinals: %04x/n", *pwFunctionsOrdinals + wBase); printf("函数%d: %s/n", i + 1, *pFunctionsName); printf("RowAddress: %08x/n", *pRowAddressOfFunctions); printf("VirtulAddress: %08x/n", *pdwAddressOfFunctions); } // 恢复指针 pwFunctionsOrdinals -= nNumberOfNames; pFunctionsName -= nNumberOfNames; pRowAddressOfFunctions -= nNumberOfNames; pdwAddressOfFunctions -= nNumberOfNames; if (pdwAddressOfFunctions != NULL) // release Memory 18 { free(pdwAddressOfFunctions); pdwAddressOfFunctions = NULL; } if (pRowAddressOfFunctions != NULL) // release Memory 19 { free(pRowAddressOfFunctions); pRowAddressOfFunctions = NULL; } for (i = 0; i < nNumberOfNames; i++, pFunctionsName++) // release Memory 21 { if (*pFunctionsName != NULL) { free(*pFunctionsName); *pFunctionsName = NULL; } } pFunctionsName -= nNumberOfNames; if (pFunctionsName != NULL) // release prince 21 { free(pFunctionsName); pFunctionsName = NULL; } if (pwFunctionsOrdinals != NULL) // release prince 22 { free(pwFunctionsOrdinals); pwFunctionsOrdinals = NULL; } } } else { printf("此文件无输出表/n"); } // VA到RAWA的转换程序(原理请参考< <加密解密ii> >中关于输出表的部分) DWORD OnConvertVAToRawA(DWORD dwFileOffset) { IMAGE_SECTION_HEADER *pmySectionHeader = (IMAGE_SECTION_HEADER *)calloc(nSectionCount, sizeof(IMAGE_SECTION_HEADER)); // prince 14 fseek(pFile, (e_lfanew + 4 + sizeof(IMAGE_FILE_HEADER) + sizeof(IMAGE_OPTIONAL_HEADER32)), SEEK_SET); fread(pmySectionHeader, sizeof(IMAGE_SECTION_HEADER), nSectionCount, pFile); DWORD dwFilePos; DWORD dwOffset; DWORD *pdwVA = (DWORD *)malloc(sizeof(DWORD) * nSectionCount); // prince 15 DWORD *pdwRowA = (DWORD *)malloc(sizeof(DWORD) * nSectionCount); // prince 16 for (int i = 0; i < nSectionCount; i++, pmySectionHeader++, pdwVA++, pdwRowA++) { *pdwVA = pmySectionHeader->VirtualAddress; *pdwRowA = pmySectionHeader->PointerToRawData; } pmySectionHeader -= nSectionCount; pdwVA -= nSectionCount; pdwRowA -= nSectionCount; for (i = 0; i < nSectionCount; i++, pdwVA++, pdwRowA++) { if ((dwFileOffset >= *pdwVA) && (dwFileOffset < *(pdwVA + 1))) { dwOffset = *pdwVA - *pdwRowA; dwFilePos = dwFileOffset - dwOffset; pdwVA -= i; pdwRowA -= i; if (pdwVA != NULL) // release prince 15 { free(pdwVA); pdwVA = NULL; } if (pdwRowA != NULL) // release prince 16 { free(pdwRowA); pdwRowA = NULL; } if (pmySectionHeader != NULL) // release prince 14 { free(pmySectionHeader); pmySectionHeader = NULL; } return dwFilePos; } } pdwVA -= nSectionCount; pdwRowA -= nSectionCount; if (pmySectionHeader != NULL) // release prince 14 { free(pmySectionHeader); pmySectionHeader = NULL; } if (pdwVA != NULL) // release prince 15 { free(pdwVA); pdwVA = NULL; } if (pdwRowA != NULL) // release prince 16 { free(pdwRowA); pdwRowA = NULL; } return -1; } void QuickSort(WORD *pwSourceData1, DWORD **pdwSourceData2, int nNumberOfArray) { SortRun(pwSourceData1, pdwSourceData2, 0, nNumberOfArray - 1); } void SortRun(WORD *pwSourceData1, DWORD **pdwSourceData2, int nLeft, int nRight) { WORD wMiddle, wTemp; DWORD *pdwTemp; int i, j; wMiddle = pwSourceData1[(nLeft + nRight) / 2]; i = nLeft; j = nRight; while (i < j) { while ((pwSourceData1[i] < wMiddle) && (i < j)) { i++; } while ((pwSourceData1[j] > wMiddle) && (j > i)) { j--; } if (i <= j) { wTemp = pwSourceData1[i]; pwSourceData1[i] = pwSourceData1[j]; pwSourceData1[j] = wTemp; pdwTemp = *(pdwSourceData2 + i); *(pdwSourceData2 + i) = *(pdwSourceData2 + j); *(pdwSourceData2 + j) = pdwTemp; i++; j--; } } // 如果左边还有元素,对左边进行快速排序 if (nLeft < j) { SortRun(pwSourceData1, pdwSourceData2, 0, j); } // 如果右边还有元素,对右边进行快速排序 if (nRight > i) { SortRun(pwSourceData1, pdwSourceData2, i, nRight); } } 呼~,好累啊!也感谢你坚持看完。接下来的一篇就是大家最关心,也是最重要的输出表了,敬请关注下一篇:PE文件之旅 -- 加壳与脱壳的战场 输入表。 prince 2005.01.06
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值