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

最近在看《黑卡免杀攻防》,对讲解的PE文件导入表、导出表的作用与原理有了更深刻的理解,特此记录。
首先,要知道什么是导入表?
导入表机制是PE文件从其他第三方程序(一般是DLL动态链接库)中导入API,以提供本程序调用的机制。而在Windows平台下,PE文件中的导入表结构就承担了完成这一工作的引导者角色。


IMAGE_IMPORT_DESCRIPTOR结构

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;

一般来说,对于导入表,我们只需要关注它的两个字段,分别是OriginalFirstThunk与FirstThunk,这两个字段分别指向了包含导出名称和导出地址的IMAGE_THUNK_DATA结构数组,这个数组以空的IMAGE_THUNK_DATA结构结尾。

IMAGE_THUNK_DATA结构

​

typedef struct _IMAGE_THUNK_DATA32 {
    union {
        PBYTE ForwarderString;      // 转发字符串的RAV
        PDWORD Function;             // 被导入函数的地址
        DWORD Ordinal;
        PIMAGE_IMPORT_BY_NAME AddressOfData;        // 指向输入名称表
    } u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;

ForwarderString是转发用的,暂时不用考虑,Function表示函数地址,如果是按序号导入Ordinal就有用了,若是按名字导入AddressOfData便指向名字信息。可以看出这个结构体就是一个大的union,大家都知道union虽包含多个域但是在不同时刻代表不同的意义那到底应该是名字还是序号,该如何区分呢?可以通过Ordinal判断,如果Ordinal的最高位是1,就是按序号导入的,这时候,低16位就是导入序号,如果最高位是0,则AddressOfData是一个RVA,指向一个IMAGE_IMPORT_BY_NAME结构,用来保存名字信息,由于Ordinal和AddressOfData实际上是同一个内存空间,所以AddressOfData其实只有低31位可以表示RVA,但是一个PE文件不可能超过2G,所以最高位永远为0,这样设计很合理的利用了空间。实际编写代码的时候微软提供两个宏定义处理序号导入:IMAGE_SNAP_BY_ORDINAL判断是否按序号导入,IMAGE_ORDINAL用来获取导入序号。

了解一下IMAGE_IMPORT_BY_NAME结构。

typedef struct _IMAGE_THUNK_DATA32 {
    	union { 
		PBYTE ForwarderString;
            	PDWORD Function;
            	DWORD Ordinal;
            	PIMAGE_IMPORT_BY_NAME AddressOfData;
         } u1;
} IMAGE_THUNK_DATA32;

Hint: 保存着需要导入函数的序号
Name: 保存着需要导入函数的名称

知道了基本的组成,那么导入表又是怎样尽职尽责的工作呢?
记录一个实例流程。

借助LordPE工具,快速定位导入表的位置。

PE文件学习笔记(一)---导入导出表_第1张图片

Importtable的RVA地址是0x0000233C;那么它的文件OFFSET是多少?
首先查看各个段的RVA和OFFSET:

PE文件学习笔记(一)---导入导出表_第2张图片

 显然,Importtable在.rdata区段
.rdata区段的RVA为0x2000,.rdata区段的起始Offset是0x00001000
RVA到Offset的转化:
导出表Offset = 导出表RVA - 导出表所在区段的RVA + 导出表所在区段的 Offset
求得导出表Offset = 0x0000133c
导出表地址计算参照值 d = 导出表RVA - 导出表Offset = 0x00001000    (便于计算各个字段的Offset)

得到Offset就可以在winhex里面找到importtable的内容了:

PE文件学习笔记(一)---导入导出表_第3张图片

PE文件学习笔记(一)---导入导出表_第4张图片

 PE文件学习笔记(一)---导入导出表_第5张图片

 由于是以空的结构体结尾的,很清楚的看到是有三个IMAGE_IMPORT_DESCRIPTOR结构:
TABLE1:

字段 OriginalFirstThunk TimeDataStamp ForwarderChain Name FirstThunk
值(RVA) 0x0000238c 0x00000000 0x00000000 0x000023dc 0x00002000
转化为Offset 0x0000138c     0x000013dc 0x00001000


TABLE2:

字段 OriginalFirstThunk TimeDataStamp ForwarderChain Name FirstThunk
值(RVA) 0x00002450 0x00000000 0x00000000 0x000024f8 0x000020c4
转化为Offset 0x00001450     0x000013dc 0x000010c4


TABLE3:

字段 OriginalFirstThunk TimeDataStamp ForwarderChain Name FirstThunk
值(RVA) 0x000023cc 0x00000000 0x00000000 0x0000252e 0x00002040
转化为Offset 0x000013cc     0x0000152e 0x00001040

由这些信息(INT、IAT、映像名的起始Offset);就可以找它们的具体结构了

                       映像名、INT、IAT

NAME OriginalFirstThunk1 OriginalFirstThunk2   FirstThunk1 FirstThunk2
KERNEL32.dll 0x00002458 0x00002466   0x00002458 0x00002466
USER32.dll 0x000024ea NULL   0x000024ea NULL
MSVCR110.dll 0x00002558 0x00002568   0x00002558 0x00002568

 根据上述的导入信息,转化为Offset;再从文件中找到导入函数和倒入序号:

NAME 序号1 函数名1   序号2 函数名2
KERNEL32.dll 0x016d ExitProcess   0x0223 GetCurrentProcess
USER32.dll 0X010a FindWindowW   NULL NULL
MSVCR110.dll 0x01a4 __getmainorgs   0x01e0 __set_app_type

 总结:

导入表的工作原理步骤:

1、根据IMANE_IMPORT_DESCRIPTOR的字段(NAME,OriginalFirstThunk,FirstThunk),来找到映像名、INT、IAT;

2、根据INT表的信息(OriginalFirstThunk字段),找到_IMAGE_IMPORT_BY_NAME结构的位置,从而得出函数的函数名、函数序号。

你可能感兴趣的:(新操作,安全)