一、导出表解析
输出表位置,落在了.rdata段,
16000【5200】
17D70【6F70】
从而,可以知道17D70,输出表在磁盘中的偏移是6F70
在010里,Ctrl + G,输入6F70
这里,先看下导出表的数据结构,40B,
typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; DWORD Name; // DLL的名称地址 DWORD Base; // 索引基数 DWORD NumberOfFunctions; // 函数地址表大小 DWORD NumberOfNames; // 函数名表大小 == 函数序号表大小 DWORD AddressOfFunctions; // 函数地址表——首地址 DWORD AddressOfNames; // 函数名表——首地址 DWORD AddressOfNameOrdinals; // 函数序号表——首地址 } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;那就从6F70的位置,开始,找40B,如下:
00 00 00 00
71 DB 67 5A 【时间戳】
00 00 【主版本】
00 00 【次版本】
C0 7D 01 00【DLL名称地址】
01 00 00 00 【索引基数】
04 00 00 00 【函数地址表大小】
04 00 00 00 【函数名表大小 == 函数序号表大小】
98 7D 01 00【函数地址表——首地址】
A8 7D 01 00 【函数名表——首地址】
B8 7D 01 00【函数序号表——首地址】
1、看DLL的名称是啥:地址17DC0【6FC0】,找到了我们自己的库dll_00.dll
2、再看下函数地址表中的元素,首地址17D98【6F98】,共有4个,地址,4B/个
如下所示:
3、再来看函数名表中的元素,首地址17DA8【6FA8】,共有4个,地址4B/个
这些都是地址值,要找到真正的函数名:
17DD0【6FD0】
17DD5【6FD5】
17DDA【6FDA】
17DCB【6FCB】
特别注意:函数名表,其实存放的也是地址值,RVA,这个只是我们自己找到的名称,方便起见,直接写的名字
4、接下来,看下函数序号表,首地址17DB8【6FB8】,4个,序号,2B/个
5、接下来,就分析分析:从这里,也可以看到,序号表里的值,并没有加上索引基数
最终,会得到如下结果:
6、验证下,我们的结果:成功了;
至于,索引基数,还没看到效果呢,————注意看下刚刚的LoadPe里的Ordinal那一列
部分代码:
#pragma once #define WIN32DLL_EXPORTS #ifdef __cplusplus extern "C" { #endif #ifdef WIN32DLL_EXPORTS #define WIN32DLL_API __declspec(dllexport) #else #define WIN32DLL_API __declspec(dllimport) #endif WIN32DLL_API void Fun1(); WIN32DLL_API void Fun2(); WIN32DLL_API void Fun3(); #ifdef __cplusplus } #endifdef文件
LIBRARY; EXPORTS; Fun4;
二、导入表解析
写一个测试程序,查看导入表RVA
输入表位置,落在了.idata段
1A000【7400】
1A1E8【75E8】
共20B
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; // 0 for terminating null import descriptor DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) } DUMMYUNIONNAME; DWORD TimeDateStamp; // 0 if not DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; // RVA to IAT } IMAGE_IMPORT_DESCRIPTOR;
2C A3 01 00 【OriginalFirstThunk:INT(Import Name Table)导入名称表地址RVA】
00 00 00 00
00 00 00 00
54 A4 01 00【DLL名称(地址值)】
E0 A0 01 00【IAT(Import Address Table)导入地址表地址RVA】
1、首先看下DLL的名字,1A454【7854】
2、看下INT(OriginalFirstThunk):1A32C【772C】,全0结尾
函数名数组:
44 A4 01 00 ————1A444【7844】——————最高位为0,说明是名称导入的,不是序号导入的;
3C A4 01 00 ————1A43C【783C】——————
4C A4 01 00 ————1A44C【784C】——————
34 A4 01 00 ————1A434【7834】——————
注意:IAT和INT都指向下面的数据结构,4B
typedef struct _IMAGE_THUNK_DATA32 { union { DWORD ForwarderString; // PBYTE DWORD Function; // PDWORD,导入函数的地址,在加载到内存后,这里才起作用 DWORD Ordinal; // 假如是序号导入的,会用到这里 DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME,假如是函数名导入的,用到这里,它指向另外一个结构体:PIMGE_IMPORT_BY_NAME } u1; } IMAGE_THUNK_DATA32; // 如果是函数名导入的,AddressOfData会指向下面这个结构体 typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; CHAR Name[1]; } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
由上可知,函数名导入的,因此,上面的地址值,就会指向一个PIMAGE_IMPORT_BY_NAME的结构体:
【7844】Fun3
【783C】Fun2
【784C】Fun4
【7834】Fun1
3、看下IAT,1A0E0【74E0】全0结束,IAT和INT一样,都指向IMAGE_THUNK_DATA32结构体,4B
可见,最高位都是0,所以,也是名称导入的,另外,还可以发现,这个位置的值,和INT的值是一样的,因此,不再赘述了;
44 A4 01 00
3C A4 01 00
4C A4 01 00
34 A4 01 00
#include <stdio.h> extern "C" __declspec(dllimport) void Fun1(); extern "C" __declspec(dllimport) void Fun2(); extern "C" __declspec(dllimport) void Fun3(); // 如果是在def中导出的,需要如下声明 void Fun4(); #pragma comment(lib, "../Debug/dll_00.lib") int main(int argc, char** argv) { Fun1(); Fun2(); Fun3(); Fun4(); getchar(); return 0; }三、如果,修改def为
LIBRARY; EXPORTS; Fun4 @1;
看下导入表里的INT/IAT:
可见,这里的一项,最高位为1,序号导入,这个序号,就是dll export的那个序号
至此,PE结构中,导入/导出表的介绍结束;
PS:I Dare to do sth I feared,作为一枚奋斗青年,也是一枚小白,最近在学习PE结构相关的知识,这篇帖子也算是自己的一个总结;希望能对需要的人以帮助;也期待大神们的更多指导;