PE输出表及地址转换

Scroll Down

VA <----> FileOffset

术语:

  • RVA:RVA 是相对虚拟地址(Relative Virtual Address)的缩写,它是文件映射到内存中的“相对地址”。
  • FOA:FOA是文件偏移地址(File Offest Address)的缩写,它是文件在磁盘上存放时相对文件开头的偏移地址。

LordPE的位置偏移功能的确很方便,但始终还是要熟悉原理,才能写出操作PE的代码。

有一条公式可以帮助我们很方便的计算出文件偏移的位置。

`文件偏移(磁盘文件的位置 FOA)``=``相对虚拟地址(任意RVA)``-``该区段相对虚拟地址(RVA)``+``该区段的文件偏移(offset)`

要转换的相对虚拟地址会落在一个区段中是因为每个偏移,不管是在文件中,还是在内存中,它们距离区段开始位置的距离总是相等的。

该区段相对虚拟地址(RVA) <--对应--> IMAGE_SECTION_HEADER.VirtualAddress

该区段的文件偏移(offset) <--对应--> IMAGE_SECTION_HEADER.PointerToRawData

在写代码前我们首先弄清楚基本概念。

PEFileToRVA

根据上图看出,区段装入内存之后的偏移与文件偏移是存在差异的。所以当我们进行文件偏移与虚拟内存地址之间换算时,首先要得出所转换的地址在第几区段内。每个区段的含义如下。

输入表(IAT)

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 指向的数组通常叫做 hint-name table,即 INT ,他在 PE 加载到内存中时被保留了下来且永远不会被修改。但是在 Windows 加载过 PE 到内存之后,PE装载器会重写 FirstThunk 所指向的数组元素中的内容,使得数组中每个 IMAGE_THUNK_DATA 不再表示指向带有函数描述的 IMAGE_THUNK_DATA 元素,而是直接指向了函数地址。此时,FirstThunk 所指向的数组就称之为输入地址表(Import Address Table ,即经常说的 IAT)。

Before

AFTER

IMAGE_THUNK_DATA STRUC
    union u1
    ForwarderString      DWORD  ?        ; 指向一个转向者字符串的RVA
    Function             DWORD  ?        ; 被输入的函数的内存地址
    Ordinal              DWORD  ?        ; 被输入的API 的序数值
    AddressOfData        DWORD  ?        ; 指向 IMAGE_IMPORT_BY_NAME
    ends
IMAGE_THUNK_DATA ENDS

当 IMAGE_THUNK_DATA 值的最高位为 1时,表示函数以序号方式输入,这时候低 31位被看作一个函数序号。

当 IMAGE_THUNK_DATA 值的最高位为 0时,表示函数以字符串类型的函数名方式输入,这时双字的值是一个 RVA,指向一个 IMAGE_IMPORT_BY_NAME 结构。(演示请看小甲鱼解密系列视频讲座)

IMAGE_IMPORT_BY_NAME STRUCT
    Hint      WORD      ?
    Name      BYTE      ?
IMAGE_IMPORT_BY_NAME ENDS

结构中的 Hint 字段也表示函数的序号,不过这个字段是可选的,有些编译器总是将它设置为 0。

Name 字段定义了导入函数的名称字符串,这是一个以 0 为结尾的字符串。

输入表(IAT)绑定

在程序装在运行之前就讲IAT表进行填充为真实的函数地址
并将IDD(IMAGE_DATADIRTORY)的第12个成员IMAGE_BOUND_IMPORT_DESCRIPTOR结构,该结构有被绑定的DLL信息的修改时间戳等验证信息,如果真实的DLL信息与此结构信息不一致时则重新生成IAT输入表。如DLL文件无改动则直接使用IAT指向的函数,以此来达到加快程序初始化阶段。