天天看点

PE文件和COFF文件格式分析——导入表

因为之前已经介绍了PE基本格式以及导出表相关,因此一些基本的东西就直接省略,用工具直接定位。

PE文件和COFF文件格式分析(1)

PE文件和COFF文件格式分析——导出表

本次以 kernel32.dll 为例,查看其导入表。

首先用 Stud_PE.exe 查看导入表的位置如下:

PE文件和COFF文件格式分析——导入表

也就是在文件的地址 0x084c88。跳过去看看,以红线开始

PE文件和COFF文件格式分析——导入表

查看导入表定义:

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 bound,
                                            // -1 if bound, and real date\time stamp
                                            //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
                                            // O.W. date/time stamp of DLL bound to (Old BIND)

    DWORD   ForwarderChain;                 // -1 if no forwarders
    DWORD   Name;
    DWORD   FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
           

可见大小是 0x14 个字节。上面看到Stud_pe 中展示了导入表大小是 0x744。0x744 / 0x14 = 0x5D。

因为这里是一个数组!一共有 0x5D 个结构。用 0x84C88 + 0x744 = 0x853cc

PE文件和COFF文件格式分析——导入表

看最后的 0x14 个字节是全0。也就是这个是数组结束的标志。即实际有效的是 0x5C 个导入表。

用 PE view 查看一下:

PE文件和COFF文件格式分析——导入表

右侧是根据每个DLL 分开的。可以数一数确实是那么多。

因为是数组,这里就分析一个就可以,看图:

PE文件和COFF文件格式分析——导入表

上面说到一个 IMAGE_IMPORT_DESCRIPTOR 的大小是 0x14 个字节,是上图中红色划线之间的部分。

对应每一个值就是:

PE文件和COFF文件格式分析——导入表

有关上面几个字段的含义可以参考:

PE文件学习笔记(一)—导入导出表

PE文件格式学习(四):导入表

这里简单说一下,中间2个为0 的字段暂时不管,以后理解了再补充。

Name 就是这个导入表的名称的地址,这里是 0xa18d4,计算成文件地址是 0x868d4。

PE文件和COFF文件格式分析——导入表

可以看到是一个以 0 结尾的字符串。

佐证一下:

PE文件和COFF文件格式分析——导入表

另外两个重要的字段的含义:

/*
	FirstThunk;
	OriginalFirstThunk;
	这两个值含义在文件中应该是一样的,指向一个结构体数组 IMAGE_THUNK_DATA32[N]
	但是当PE 加载进内存就有差异了,FirstThunk 指向的数组和OriginalFirstThunk指向的不再一样
	FirstThunk 指向的数组的含义是函数地址!!),而 OriginalFirstThunk 指向的是名称和序数地址,也就是 IMAGE_IMPORT_BY_NAME 的地址
	该数组结束的标志是以一个 IMAGE_THUNK_DATA32 全部为0 作为标志
	IMAGE_THUNK_DATA32 最高位如果为0,则IMAGE_THUNK_DATA32.AddressOfData是一个RVA,指向一个IMAGE_IMPORT_BY_NAME结构,用来保存名字信息
	反之如果最高位为1,则低16位是导出序数。
	*/
           

因此,简单的说,在文件里面,通常 OriginalFirstThunk 指向一个数组的首地址,这个数组是:

IMAGE_THUNK_DATA32[N]。

至于 32 和 64 位差距暂时没研究。

看一下:

PE文件和COFF文件格式分析——导入表

发现 FirstThunk = 0x816d0 ->RA = 0x666D0

OriginalFirstThunk = 0xa0fcc ->RA = 0x85fcc

PE文件和COFF文件格式分析——导入表
PE文件和COFF文件格式分析——导入表

我们发现,虽然 FirstThunk OriginalFirstThunk 的值不一样,但是对应地址写的 IMAGE_THUNK_DATA32[N]

的地址是一样的,印证了上面的说法。

由于这个地址最高位是0,一次他说一个指向一个名字结构的地址,这个名字结构就是 typedef struct

_IMAGE_IMPORT_BY_NAME {
    WORD    Hint;
    CHAR   Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
           

也就是前两个字节是导入函数在对应 DLL 中的序数,后面是一个以 0 结尾的字符串。

这个地址 0x0a1898(转换成文件地址:0x86898)。。跳过去:

PE文件和COFF文件格式分析——导入表

可以看到序数是 0x0001,函数名字是 RtlCaptureStackBackTrace。

同理,看图

PE文件和COFF文件格式分析——导入表

由于红线是 IMAGE_THUNK_DATA32[N] 数组首地址,以全0结构体结束,可以看到一共有三个值,第四个就是全0。

不妨再看第二个元素是 0x0a18B4,转换成文件地址:0x868B4:

PE文件和COFF文件格式分析——导入表

可以看到,其实就是接着上面的一个名字结构体的。该DLL 有三个导入函数:

看工具分析结果:

PE文件和COFF文件格式分析——导入表

上面说到:

发现  FirstThunk = 0x816d0 	->RA = 0x666D0 
OriginalFirstThunk = 0xa0fcc 	->RA = 0x85fcc
           

对比就可以发现:工具第一列就是 IMAGE_THUNK_DATA32 数组的每个元素的地址(文件中的地址)

这每个元素指向另一个地址(名称结构 IMAGE_IMPORT_BY_NAME 的地址),也就是 第二列 DATA 的值(这个值是RVA)。

Value 列,就是 IMAGE_IMPORT_BY_NAME 中序数和名字了。

最后,再看:

PE文件和COFF文件格式分析——导入表
PE文件和COFF文件格式分析——导入表

可以发现

FirstThunk = 0x816d0 ->RA = 0x666D0

OriginalFirstThunk = 0xa0fcc ->RA = 0x85fcc

两个字段指向的是不同的两个数组 IMAGE_THUNK_DATA32 [N] 的地址,数组以一个全0的结构体结束。

虽然这两个数组不同,但是数组的内容是一模一样的。

工具看一下:

PE文件和COFF文件格式分析——导入表
PE文件和COFF文件格式分析——导入表

可以发现两个不同的地方,对应的实际数据是一样的。

参考下图,来源: PE文件格式学习(四):导入表

PE文件和COFF文件格式分析——导入表

按照一些说法,上图中 IAT,再加载进内存中后就不是和INT 一样相同指向了。(未证实)

最后附上代码分析的结果(一共 0x5C 个导入表)~:

PE文件和COFF文件格式分析——导入表