PE文件解析(4):导入表的解析
导入表
什么是导入表?
导入表记录了一个exe或者一个dll所用到的其他模块导出的函数。
数据目录表的第二个元素记录着导入包的位置,导出表我们上节课已经解析过 了,今天我们来解析导入表。
导入表结构体解析
导入名称表: INT
导入地址表:IAT
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics;// 0 for terminating null import descriptor DWORD OriginalFirstThunk;// RVA 指向(IMAGE_THUNK_DATA)结构体数组 INT } DUMMYUNIONNAME; DWORD TimeDateStamp; // 时间戳 0 if not bound DWORD ForwarderChain; // -1 if no forwarders DWORD Name;//RVA dll文件名称 DWORD FirstThunk; // RVA to IAT 导入地址表 IMAGE_THUNK_DATA数组 } IMAGE_IMPORT_DESCRIPTOR;
重要字段:
- OriginalFirstThunk:是导入表的导入名称表(INT)的RVA。
- Name:作为导入的DLL文件的名称,RVA。
- FirstThunk:是导入表的导入地址表(IAT)的RVA。
OriginalFirstThunk-->IMAGE_THUNK_DATA 如果IMAGE_THUNK_DATA 最高位为1 则是按序号导入,否则就是按名称导入的。
根据OriginalFirstThunk或者FirstThunk可以得到导入名称表INT和IAT,表示INT和IAT的结构体如下:
typedef struct _IMAGE_THUNK_DATA32 { union { DWORD ForwarderString; // PBYTE DWORD Function; // PDWORD DWORD Ordinal; DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME } u1; } IMAGE_THUNK_DATA32;
- Ordinal:用来标记此函数是按序号导入还是按照名称导入
INT或者IAT的 AddressOfData字段:是一个RVA,通过它可以得到按名称导入的表 PIMAGE_IMPORT_BY_NAME
typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint;//导入函数索引(根据编译器的不同这个地方放的东西不一样) CHAR Name[1]; } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
- Name:每一个函数的名称
寻找导入表的位置
我们使用010editor工具来寻找导入表的位置:
-
首先找到数据目录表的第二个元素,它所存储的偏移及大小表示的是导入表的偏移和大小: 1B17Ch 和80大小。
-
然后在区段中寻找合适位置:可以看到,第五个区段的RVA是1B000h,在内存中的大小是2339d ,导入表的RVA正好位于此字段内,因此使用公式:
导入表的FOA=导入表的RVA - 区段的RVA +区段的FOA
,得到了导入表的在文件中的偏移地址: 1b17c - 1b000 + 8000 = 817C
-
得到的 817C就是我们在文件中的偏移地址,搜索可得:这就是导入表的位置(注意:817C 加上文件基址才是真正的位置,010editor工具可以省略基址直接由偏移得到,但是在代码解析或则其他工具中,我们必须加上文件基址)。
代码解析导入表
void cPE::GetImportTable() { //获取导入表的地址 IMAGE_DATA_DIRECTORY ImPortAddr = pOptionHeader->DataDirectory[1]; //得到导入表 PIMAGE_IMPORT_DESCRIPTOR ImportTable = (PIMAGE_IMPORT_DESCRIPTOR)(RvaToFoa(ImPortAddr.VirtualAddress) + FileBuff); if (ImportTable == NULL) { printf("导入表为空!\n"); return; } //导入表会有多个,导入表只要不为空,则它的字段也一定不为空,如果它的字段为空,则此导入表遍历到了最后 while (ImportTable->OriginalFirstThunk) { printf("TimeDataStamp=%d\n", ImportTable->TimeDateStamp); //RVA Name char* dllName=RvaToFoa(ImportTable->Name) + FileBuff; printf("Dll文件名称: %s\n", dllName); //得到导入名称表 INT PIMAGE_THUNK_DATA pThunkData = (PIMAGE_THUNK_DATA)(RvaToFoa(ImportTable->OriginalFirstThunk) + FileBuff); while (pThunkData->u1.Function) { //判断是否按序号导入 if (pThunkData->u1.Ordinal & IMAGE_ORDINAL_FLAG32) //0x80000000 { printf("按序号导入: %d\n", IMAGE_ORDINAL32(pThunkData->u1.Ordinal)); } else { //否则就是按名称导入 PIMAGE_IMPORT_BY_NAME importName = (PIMAGE_IMPORT_BY_NAME)(RvaToFoa(pThunkData->u1.AddressOfData) + FileBuff); printf("按名称导入: %s\n", importName->Name); } pThunkData++; } ImportTable++; } }
运行如下:打印出调用此程序的导入的所有的dll文件所加载的函数,按名称或者序号导入。
可以看到:导入表的位置和我们在010editor中解析的导入表的位置一致。
本文来自博客园,作者:hugeYlh,转载请注明原文链接:https://www.cnblogs.com/helloylh/p/17209672.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix