Table of Contents

Reference


文件编写思路


一个 PE 文件可以大致分为一下四个部分:

  1. DOS 部分:MS-DOS MZ 头部和 MS-DOS 实模式残余程序
  2. PE 头部分:PE 文件标志、PE 文件头、PE 文件可选头
  3. 节表:各段头部
  4. 节数据:节的实体部分

在这次文件编写中我们也要完成每一个部分的编写,其中大部分在文章再写手工打造可执行程序中已讲的很详细,其中我纠结较久的部分为涉及到函数导入部分的编写

下面是完成效果,一个简单的 Hello, world!

函数导入


首先是在可选头的IMAGE_DATA_DIRECTORY_ARRAYImport元素处指定导入表的地址和大小

WinNT.h中对IMAGE_DATA_DIRECTORY结构的定义如下:

typedef struct _IMAGE_DATA_DIRECTORY
    {
        DWORD VirtualAddress;   //若是对应Import table,则这里对应导入表地址
        DWORD Size;             //这里对应导入表大小
    } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

这个 Import Table 其实是一个数组,元素个数为导入的 DLL 个数,数组中的元素结构如下(同样来自WinNT.h文件中):

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;

这里我们只看DWORD OriginalFirstThunk, DWORD Name, DWORD FirstThunk这三个结构:

  • OriginalFirstThunk: 指向 INT(Import Name Table)
  • Name: 指向数组元素对应的 DLL 字符串
  • FirstThunk: 指向 IAT(Import Address Table)

后面再解释一下 INT 与 IAT 的区别

这里OriginalFirstThunkFirstThunk虽然指向地址的意义不同,但指向的却是同一个结构:IMAGE_THUNK_DATA

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

由于这里是一个 union 结构体,所以实际上使用的时候只用到了一个

如果是第一次看的话肯定晕了,反正我第一次看导入表相关结构的时候是被绕晕了,于是我画了一张图(图太小看不清可以右键在新标签中打开):

下面来说一下我对 INT 和 IAT 区别的理解:

  • 在程序加载到内存中前,INT 与 IAT 中的内容一模一样,都指向了对应 DLL 中的函数名
  • 程序加载到内存并导入函数后,INT 表内容不变,IAT 表中的每个IMAGE_THUNK_DATA结构就指向了函数在虚拟内存中的真实入口点

WinDBG 动调


先推荐一个查询常用 WinDbg 命令的网站:Common WinDbg Commands (Thematically Grouped)

进入 WinDBG 加载程序后用lm看一下Loaded modules and image information,可见我们的程序加载基址为0x400000(其实不用看也能从编写 PE 文件的时候知道)

然后根据对再写手工打造可执行程序文章的理解,知道.text会被加载到0x401000处,接着直接在.text节下一个断点bp 0x401000然后再运行到断点p,这时我们开始查看 Import Table 及其相关结构:

先用 010Editor 看一下IMAGE_DATA_DIRECTORY Import中导入表的 RVA 为:2010h找到 Import Table 所在位置是0x402010h

dc 0x402010查看对应地址内容

对照着结构体再结合上面我画的图,能很清晰的知道这些个内容的关系,这个图中占展现了 INT 表,我们下面再观察一下 IAT 表

IMAGE_IMPORT_DESCRIPTOR -> DWORD FirstThunk,即这里地址0x402020处指向计算后为0x402008,没猜错按照参考文章中的汇编代码来看的话这个对应的应该是指向MessageBoxA

可见,指向了0x7596ed60,而根据我们之前用lm查看的信息,这个空间正好是user32.dll加载的空间,我们再反汇编一下这个地址的机器码:

果然是该函数

但是好像在再写手工打造可执行程序中给的程序和文章中写的程序关于 Import Table 这部分不太一样,所以在理解程序的时候要稍微注意一下