导出表

一个loader怎么找到DLL文件的导出函数的地址呢?

首先定位dll文件的PE文件结构,然后找到导出表的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; // 不重要
DWORD TimeDateStamp; //时间戳. 编译的时间. 把秒转为时间.可以知道这个DLL是什么时候编译出来的.
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;           //指向该导出表文件名的字符串,也就是这个DLL的名称 辅助信息.修改不影响 存储的RVA 如果想在文件中查看.自己计算一下FOA即可.
DWORD Base;           // 导出函数的起始序号
DWORD NumberOfFunctions; //所有的导出函数的个数
DWORD NumberOfNames; //以名字导出的函数的个数
DWORD AddressOfFunctions; // 导出的函数地址的地址表RVA 也就是 函数地址表
DWORD AddressOfNames; // 导出的函数名称表的RVA 也就是 函数名称表
DWORD AddressOfNameOrdinals; // 导出函数序号表的RVA 也就是 函数序号表
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

目前就知道最后三个AddressOfFunctionsAddressOfNamesAddressOfNameOrdinals指向的是什么就行

dll导出函数可以两种方式,名称导出或者序号导出

middle_PEIET1.PNG

如果是函数名,loader就会先依次匹配AddressNames里的函数名,如果有,再到AddressOfNameOrdinals里找到对应的序号,再拿着序号去AddressOfFunctions里找到对应的函数

如果是序号,loader先找到Base,然后计算出真正的序号,直接找到AddressOfFunctions里的对应的函数

demo

middle_PEIET2.PNG

可以去0x090068的位置找到Name字段

middle_PEIET3.PNG

middle_PEIET4.PNG

可以看到里面的数据都是对的上的

middle_PEIET5.PNG

middle_PEIET6.PNG

AddressOfNameOrdinals

middle_PEIET7.PNG

里面有地址特别高的函数地址,这些函数是引用外部dll的函数,在 .rdata 段而不是其他低地址在.text段,PE文件在识别的时候是检查这个地址是否在Export Directory的范围内,如果是,那么这个函数就是字符串而不是真正的代码

middle_PEIET8.PNG

middle_PEIET9.PNG

用cmd也能看

1
dumpbin /exports C:\Windows\System32\kernel32.dll

导入表

1
2
3
4
5
6
7
8
9
10
11
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA of Import Lookup table
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 时间戳.
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;                //指向DLL名字的 RVA
DWORD FirstThunk; // RVA to Import Address table
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

PE loader 会解析IAT表,找到 IMAGE_IMPORT_DESCRIPTOR[] 数组

只关心三个,OriginalFirstThunk ,指向了Import Lookup tableFirstThunk ,指向了 Import Address tableName ,指向了DLL的名字

middle_PEIET10.PNG

如图,Import Lookup tableImport Address table 都存着Hint/Name Table的RVA,Hint/Name Table 存着导入的函数名和序号,其中 Import Address table 会在PE文件加载时填充上函数地址

PE loader先找到DLL Name,然后把该DLL文件加载到PE文件的内存空间,然后解析DLL文件的导出表,然后开始解析 Hint/Name Table ,然后取出里面的Hint(也就是函数序号),如果Hint匹配上,就会从导出表里提取出该函数的地址,然后把地址填入Import Address table 里,如果通过序号没找到,就拿着函数名找

demo

middle_PEIET11.PNG

计算有多少个导入函数,每个_IMAGE_IMPORT_DESCRIPTOR结构体是5个DWORD,也就是20字节,这里是208(hex),也即是26个导入函数,但是只有25个,因为最后一个结构体是全0(框框有误)

middle_PEIET12.PNG

数据都是对的上的

middle_PEIET16.PNG

middle_PEIET13.PNG

Name字段

middle_PEIET14.PNG

两个位置指向的地址的内容是一样的,这里的内容存储的是个地址,这个地址指向的就是 Hint/Name Table ,如果是64位,就是8字节,32位就是4字节

middle_PEIET15.PNG

动态调试一下,在x64dbg的options->settings里勾选上DLL load,让dll在加载的时候就被断住

middle_PEIET17.PNG

找到一个call函数的地方,右键Follow in Dump,然后进入

middle_PEIET18.PNG

可以发现此时IAT表里没有内容

middle_PEIET19.PNG

再多run程序几次,直到进入OEP,可以看到此时IAT表里才被填入了地址

middle_PEIET20.PNG

右键一段地址,Follow QWORD in Disassemler,可以看到是在ntdll的地址空间里

middle_PEIET21.PNG

middle_PEIET22.PNG

再提一句,IAT表的地址可以有两种,一种是直接就是代码,一种是需要jmp到真正的代码