导入表的结构导入表的结构

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;
  • Name
    导入函数所在dll的名称
  • OriginalFirstThunk FirstThunk
    [PE结构]导入表与IAT表_多表
    [PE结构]导入表与IAT表_加载_02
    可以看到两个分别指向INT(Import Name Table)和IAT(Import Name Table)。
    表中的每一项数据存放的需要的函数名或函数序号

那么怎么区分存放的是函数序号还是函数名呢?
如果最高位为1,则表示是存储的序号,序号的值为去掉最高位后其余位数的值
如果为0,则表示是函数名,它指向一个IMAGE_IMPORT_BY_NAME,其中存放着函数名

搞这么多表,看着头晕,设计者当时是嫌不够乱?

其实东西在那儿自然有在的意义,我们要做的就是找出有什么作用。

  • 为什么要有IAT表或者INT表?
    有人会想,导入表不就是告诉利用的外部函数地址吗?那为什么不可以将函数地址直接写入呢?其实是因为程序在编译链接时,最终都需要硬编码,并存盘。硬编码就会将函数地址固定了,而dll在加载时的地址可能会变化,如果此时函数名被硬编码,那么在调用时就可能失败,所以才会有张表,而调用一般是采用 call [0x111111]这种方式,最终函数地址可以动态化。
  • 为什么同时要有IAT表和INT表?
    是因为如果在程序加载后,IAT表中的就会变成函数地址,那么我们怎么区分哪个是哪个的函数地址呢?这时就可以通过INT表来确定了。可是编译后已经被硬编码了啊,完成函数调用已经没问题了,所以INT表应该是提供给需要知道函数名的需求者。