写一篇关于导入表的文章。
逆向脱壳,找到入口点之后,dump。一般都会遇到要修复导入表的问题,因而了解导入表就有必要。
首先说一下什么是导入表,百度百科给出的解释是
Import Address Table 由于导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或者多个DLL 中.当PE 文件被装入内存的时候,Windows 装载器才将DLL 装入,并将调用导入函数的指令和函数实际所处的地址联系起来(动态连接),这操作就需要导入表完成.其中导入地址表就指示函数实际地址。
换言之,就是windows给我们调用其dll的一个实现方法。
对于每一个引入的可执行文件,就比如dll,有一个导入表,在winnt.h中可以查看到它的形式为
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;
};
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
可以看到结构体中有五个DWORD,其中我们重点看的是union中的一个DWORD,还有FirstThunk,从名字来看OriginalFirstThunk和FirstThunk应该有某种联系。
实际上他们两个都是指针,其中存放的是两个RVA地址。
通过字段找到导入表,第一眼看到的就是结构体了,OriginalFirstThunk应该就是第一个数据,可是我们知道在c语言中引用结构体的方法是(p).x 之所以强调这一步是因为,结构体并非是按地址直接存放,这个.操作符就是带我们走向x的实际地址,所以我们的OriginalFirstThunk应该是第一个Dword所指的地址,这个地址怎么求呢,实际上很简单,因为不可能把p和x放的太远,你甚至可以直接往下翻找,但我们说更正确的办法那就是Dword所指的虚拟地址转成物理地址,我们可以用dword中存放的虚拟地址减去这个section的虚拟地址,得到的偏移量再加上Dword的物理地址(因为OriginalFirstThunk就是第一个,所以Dword的物理地址就是section的物理地址)。好的,假设我们现在已经到达OriginalFirstThunk的实际空间,前面说过他是一个指针,所以应该存放着一个RVA地址,关于这个RVA地址的物理地址转换方法同上,往下走,我们跟着这个指针到了其所指的地址,其中的数据是一个双字节的hint和函数名字,然后是下一个双字节hint和函数名字……
除了通过OriginalFirstThunk可以找到,也可以用Name找到,方法就简单很多了,直接通过结构体中Name所占Dword作为指针,就可以跳到函数所在地址。
再说FirstThunk,他实际上是一个指向数组的指针,这个数组的类型名字有点长,叫做IMAGE_THUNK_DATA,实际上OriginalFirstThunk也是指向这个数组,具体来看看IMAGE_THUNK_DATA长什么样子
typedef struct _IMAGE_THUNK_DATA32 {
union {
PBYTE ForwarderString;
PDWORD Function;
DWORD Ordinal;
PIMAGE_IMPORT_BY_NAME AddressOfData;//这个东西也可以用来指向函数
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
看起来是一个四选一的东东,每个这种东东都能拿来指向函数,其中第四个是一个结构体
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; ///该函数的导出序数
BYTE Name[1]; ///该函数的名字
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
看到这里我们就能明白,我们从OriginalFirstThunk中走过去的那些Hint和函数名字是为什么了吧。
没错就是 _IMAGE_IMPORT_BY_NAME ,其他三个东东我们这里不细讲,因为一般看到的就是这种形式(第三个ordinal也有使用)