菜单

[PE结构分析] 八.输入表结构和输入地址表(IAT)

2019年4月7日 - www6165com

在 PE文件头的 IMAGE_OPTIONAL_HEADE宝马X3 结构中的 DataDirectory(数据目录表)
的第一个成员正是指向输入表的。各样被链接进来的 DLL文件都各自对应二个IMAGE_IMPORT_DESC奇骏IPTO库罗德 (简称IID) 数组结构。

在 PE文件头的 IMAGE_OPTIONAL_HEADE奥迪Q3 结构中的 DataDirectory(数据目录表)
的第叁个分子就是指向输入表的。各种被链接进来的 DLL文件都分别对应二个IMAGE_IMPORT_DESCKugaIPTO福睿斯 (简称IID) 数组结构。

【pker / CVC.GB】 
5、关于FASM 
———– 
上边大家用FASM来编排大家的首先个程序。大家得以编写制定如下代码: 
format  PE GUI 4.0 
entry   __start 
section ‘.text’ code    readable executable 
    __start: 
            ret 
笔者们把那一个文件存为test.asm并编译它: 
fasm test.asm test.exe 
从没其余烦人的参数,很便宜,不是么? 😛 
作者们先来看一下以此顺序的布局。第一句是format提示字,它钦定了程序的花色,PE表示作者 
们编写的是贰个PE文件,前面包车型客车GUI提醒编写翻译器大家将应用Windows图形界面。假使要编写1 
个控制台应用程序则足以钦点为CONSOLE。要是要写一个水源驱动,能够钦点为NATIVE,表示 
不须要子系统帮助。最终的四.0钦定了子系统的版本号(还记得前边的MajorSubsystemVersion 
和MinorSubsystemVersion么?)。 
上边一行钦命了程序的入口为__start。 
section指示字表示我们要初始三个新节。大家的次序唯有三个节,即代码节,大家将其命名 
为.text,并点名节属性为只读(readable)和可实施(executable)。 
随后正是我们的代码了,我们只是用一条ret指令回到系统,那时堆栈里的回来地址为Exit- 
Thread,所以程序间接退出。 
上面运维它,程序只是简短地淡出了,我们成功地用FASM编写了1个顺序!我们曾经迈出了 
率先步,下边要让大家的主次能够做点什么。大家想要调用四个API,大家要如何是好啊?让 
我们再来充充电吧 😀 

原版的书文链接地址:

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;
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;

5.1、导入表 
———– 
我们编辑如下代码并用TASM编写翻译: 

; tasm32 /ml /m5 test.asm 
; tlink32 -Tpe -aa test.obj ,,, import32.lib 

        ideal 
        p586 
        model   use32 flat 
extrn   MessageBoxA:near 
        dataseg 
str_hello       db      ‘Hello’,0 
        codeseg 
__start: 
        push    0 
        push    offset str_hello 
        push    offset str_hello 
        push    0 
        call    MessageBoxA 
        ret 
        end     __start 
上面大家用w3贰dasm反汇编,获得: 
:00401000   6A00                    push    00000000 
:00401002   6800204000              push    00402000 
:00401007   6800204000              push    00402000 
:0040100C   6A00                    push    00000000 
:0040100E   E801000000              call    00401014 
:00401013   C3                      ret 
:00401014   FF2530304000            jmp     dword ptr [00403030] 
能够看来代码中的call Message博克斯A被翻译成了call 0040十14,在这么些地方处是贰个跳转 
指令jmp dword ptr [00403030],大家能够规定在地址00403030处存放的是MessageBoxA的 
真正地址。 
实质上那些地点是放在PE文件的导入表中的。下边大家后续大家的PE文件的学习。我们先来看 
须臾间导入表的布局。导入表是由一文山会海的IMAGE_IMPORT_DESC奥迪Q伍IPTOLX570结构重组的。结构的个 
数由文件引用的DLL个数控制,文件引用了不怎么个DLL就有个别许个IMAGE_IMPORT_DESCRIPTOR 
布局,最终还有一个全为零的IMAGE_IMPORT_DESCRAV4IPTO奇骏作为完毕。 
typedef struct _IMAGE_IMPORT_DESCRIPTOR { 
    union { 
        DWORD   Characteristics; 
        DWORD   OriginalFirstThunk; 
    }; 
    DWORD   TimeDateStamp; 
    DWORD   ForwarderChain; 
    DWORD   Name; 
    DWORD   FirstThunk; 
} IMAGE_IMPORT_DESCRIPTOR; 
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR; 
Name字段是四个卡宴VA,钦定了引进的DLL的名字。 
OriginalFirstThunk和FirstThunk在八个PE未有加载到内部存款和储蓄器中的时候是同等的,都以指向1 
个IMAGE_THUNK_DATA结构数组。最后以多个内容为0的布局结束。其实那么些布局就是三个双 
字。这些结构很有趣,因为在分裂的时候那一个协会意味着着差异的意思。当以此双字的最高 
位为一时,表示函数是以序号的艺术导入的;当最高位为0时,表示函数是以名称方式导入的, 
那是其一双字是1个卡宴VA,指向三个IMAGE_IMPORT_BY_NAME结构,这么些体协会会用来内定导入函数 
名称。 
typedef struct _IMAGE_IMPORT_BY_NAME { 
    WORD    Hint; 
    BYTE    Name[1]; 
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME; 
Hint字段表示多少个序号,可是因为是按名称导入,所以那些序号一般为零。 
Name字段是函数的名称。 
上边大家用一张图来表达那一个纷纭的进度。假若二个PE引用了kernel32.dll中的LoadLibraryA 
和GetProcAddress,还有七个按序号导入的函数8001000贰h。 
IMAGE_IMPORT_DESCRIPTOR                                  IMAGE_IMPORT_BY_NAME 
+——————–+   +–> +——————+     +———————–+ 
| OriginalFirstThunk | –+    | IMAGE_THUNK_DATA | –> | 023B |  ExitProcess   | <–+ 
+——————–+        +——————+     +———————–+    | 
|   TimeDataStamp    |        | IMAGE_THUNK_DATA | –> | 0191 | GetProcAddress | <–+–+ 
+——————–+        +——————+     +———————–+    |  | 
|   ForwarderChain   |        |     80010002h    |                                  |  | 
+——————–+        +——————+    +—> +——————+    |  | 
|        Name        | –+    |         0        |    |     | IMAGE_THUNK_DATA | —+  | 
+——————–+   |    +——————+    |     +——————+       | 
|     FirstThunk     |-+ |                            |     | IMAGE_THUNK_DATA | ——+ 
+——————–+ | |    +——————+    |     +——————+ 
                       | +–> |   kernel32.dll   |    |     |     80010002h    | 
                       |      +——————+    |     +——————+ 
                       |                              |     |         0        | 
                       +——————————+     +——————+ 
还记得前边大家说过在叁个PE未有被加载到内部存款和储蓄器中的时候IMAGE_IMPORT_DESCRIPTOR中的 
OriginalFirstThunk和FirstThunk是①样的,那么为何Windows要私吞多少个字段呢?其实 
是那般的,在PE文件被PE加载器加载到内部存款和储蓄器中的时候那么些加载器会自动把FirstThunk的值替 
换为API函数的真的入口,约等于老大前边jmp的实在地址,而OriginalFirstThunk只不过是 
用来反向查找函数名而已。 
好了,又讲了那般多是要做什么样吗?你当时就会看到。上面大家就来布局大家的导入表。 
大家用来下代码来起头我们的引进节: 
section ‘.idata’ import data    readable 
section提醒字表示咱们要从头三个新节。.idata是以此新节的称谓。import data表示那是 
3个引进节。readable代表这些节的节属性是只读的。 
设若大家的次第只须要引进user32.dll中的MessageBoxA函数,那么我们的引进节唯有二个 
讲述那个dll的IMAGE_IMPORT_DESC奥迪Q⑤IPTOGL450和1个全0的结构。考虑如下代码: 
    dd      0                   ; 我们并不要求OriginalFirstThunk 
    dd      0                   ; 咱们也不必要管这几个时间戳 
    dd      0                   ; 大家也不尊敬这几个链 
    dd      RVA usr_dll         ; 指向大家的DLL名称的TucsonVA 
    dd      RVA usr_thunk       ; 指向大家的IMAGE_IMPORT_BY_NAME数组的RVA 
                                ; 注意这几个数组也是以0尾声的 
    dd      0,0,0,0,0           ; 结束标志 
上面用到了2个中华VVA伪指令,它钦点的地址在编写翻译时被机关写为相应的卡宴VA值。上面定义大家 
要引进的动态链接库的名字,那是1个以0聊到底的字符串: 
金沙网投网站,    usr_dll     db      ‘user32.dll’,0 
再有我们的IMAGE_THUNK_DATA: 
    usr_thunk: 
金沙电子游戏中心,        MessageBox      dd      RVA __imp_MessageBox 
                        dd      0                   ; 停止标志 
上面的__imp_MessageBox在编写翻译时出于前边有安德拉VA提醒,所以表示是IMAGE_IMPORT_BY_NAME的 
WranglerVA。上边大家定义这一个组织: 
    __imp_MessageBox    dw      0                   ; 大家不按序号导入,所以可以 
澳门金沙在线官网,                                                    ; 不难地置0 
                        db      ‘MessageBoxA’,0     ; 导入的函数名 
好了,大家完毕了导入表的建立。下边大家来看一个全体的次第,看看2个完好无缺的FASM程序 
是何等的非凡 😛 
format  PE GUI 4.0 
entry   __start 

PE文件定义

PE 文件(”Portable executable”,
可移植的可执行文件)文件格式,是微软Windows NT,
中Win32、Win3贰s中的可实施的二进制的文件格式。 包涵:.exe, .dll, .sys,
.com, .ocs. PE文件最要紧的七个成分:

一.磁盘上的可执行文件和它被映射到windows内部存款和储蓄器之后的格式万分相似。

2.对于Win32 来讲,
模块中Dolly用的保有代码、数据、财富、导入表、和其余供给的模块数据结构都在三个总是的内部存储器块中。由此,只必要领会PE
Loader把可执行文件映射到了内部存款和储蓄器的什么样地点(基址),通过作为影象的一有的指针,就可以找到那么些模块的持有区别的块。

PE文件总览:

金沙网投网站 1

在这些IID数组中,并未提议有稍许个项(正是未有显著指明有微微个链接文件),但它聊到底是以一个全为NULL(0)
的 IID 作为完成的表明。

在那些IID数组中,并未有建议有稍许个项(正是从未明确性指明某些许个链接文件),但它谈起底是以三个全为NULL(0)
的 IID 作为实现的标志。


; data section… 

section ‘.data’ data    readable 
    pszText         db      ‘Hello, FASM world!’,0 
    pszCaption      db      ‘Flat Assembler’,0 

放另一张图:

金沙网投网站 2

再放一张:

金沙网投网站 3

上面只摘录相比首要的字段:

下边只摘录相比较根本的字段:


; code section… 

section ‘.text’ code    readable executable 
    __start: 
            push    0 
            push    pszCaption 
            push    pszText 
            push    0 
            call    [MessageBox] 
            push    0 
            call    [ExitProcess] 

1. DOS Header: (size:64byte)

_IMAGE_DOS_HEADER结构体:

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

结构体中有三个关键的数量成员。第三个为e_magic,那个必须为MZ,即0x伍CrossD。另三个要害的多少成员是终极一个成员e_lfanew,这些成员的值为IMAGE_NT_HEADERS的偏移。其中,*e_lfanew那个字段的值:  
PE Header 在磁盘文件中相对于文本初叶的偏移地址.

实例截图:

金沙网投网站 4

2.     PE Header: (size: 248bytes)

IMAGE_NT_HEADERS 紧接在DOS Stub之后,位置有e_lfanew所指

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature; //4 bytes PE文件头标志:(e_lfanew)->‘PE’
    IMAGE_FILE_HEADER FileHeader;//20 bytes PE文件物理分布的信息
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;//224bytes PE文件逻辑分布的信息
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

金沙网投网站 5

PE Header 总览

金沙网投网站 6

IMAGE_NT_HEADEEscortS结构体成员解析:

OriginalFirstThunk

它指向first thunk,IMAGE_THUNK_DATA,该 thunk 拥有 Hint 和 Function
name 的地址。

OriginalFirstThunk

它指向first thunk,IMAGE_THUNK_DATA,该 thunk 拥有 Hint 和 Function
name 的地址。


; import section… 

section ‘.idata’ import data    readable 
    ; image import descriptor 
    dd      0,0,0,RVA usr_dll,RVA usr_thunk 
    dd      0,0,0,RVA krnl_dll,RVA krnl_thunk 
    dd      0,0,0,0,0 
    ; dll name 
    usr_dll     db      ‘user32.dll’,0 
    krnl_dll    db      ‘kernel32.dll’,0 
    ; image thunk data 
    usr_thunk: 
        MessageBox      dd      RVA __imp_MessageBox 
                        dd      0 
    krnl_thunk: 
        ExitProcess     dd      RVA __imp_ExitProcess 
                        dd      0 
    ; image import by name 
    __imp_MessageBox    dw      0 
                        db      ‘MessageBoxA’,0 
    __imp_ExitProcess   dw      0 
                        db      ‘ExitProcess’,0 
见状此间自个儿深信不疑大家都对FASM那一个编写翻译器有了1个上马的认识,也肯定有那几个读者会说:“ 
如此那般劳累啊,干吧要用这些编写翻译器呢?”。是的,可能上边的代码看起来很复杂,编写起来 
也很费劲,但FASM的2个益处在于大家得以更积极地控制我们转变的PE文件结构,同时能对 
PE文件有更理性的认识。可是各种人的脾胃分裂,嘿嘿,可能下面的理由还不够说服各位读 
者,没提到,采取①款符合你的编写翻译器吧,它们都一模一样杰出 😛 

2.1.Signature: (4 bytes)

金沙网投网站 7

Name

它表示DLL
名称的争论虚地址(译注:相对2个用null作为完毕符的ASCII字符串的二个GL450VA,该字符串是该导入DLL文件的称号。如:KE途达NEL3二.DLL)。

Name

它意味着DLL
名称的绝对虚地址(译注:相对三个用null作为实现符的ASCII字符串的3个汉兰达VA,该字符串是该导入DLL文件的称呼。如:KE帕杰罗NEL3二.DLL)。

5.2、导出表 
———– 
经过导入表的求学,笔者想各位读者已经对PE文件的学习进度有了团结认识和艺术,所以上边 
有关导出表的1节自笔者将加紧局地速度。“朋友们注意啦!!! @#$%$%&#^”  😀 
在导出表的序曲地点是1个IMAGE_EXPORT_DIRECTOMuranoY结构,但与引进表分化的是在导出表中 
唯有贰个以此布局。上面大家来看一下那一个结构的概念: 
typedef struct _IMAGE_EXPORT_DIRECTORY { 
    DWORD   Characteristics; 
    DWORD   TimeDateStamp; 
    WORD    MajorVersion; 
    WORD    MinorVersion; 
    DWORD   Name; 
    DWORD   Base; 
    DWORD   NumberOfFunctions; 
    DWORD   NumberOfNames; 
    DWORD   AddressOfFunctions;     // RVA from base of image 
    DWORD   AddressOfNames;         // RVA from base of image 
    DWORD   AddressOfNameOrdinals;  // RVA from base of image 
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY; 
Characteristics、MajorVersion和MinorVersion不使用,一般为0。 
TimeDataStamp是光阴戳。 
Name字段是多个冠道VA值,它指向了这么些模块的原始名称。这些称呼与编写翻译后的公文名毫无干系。 
Base字段钦命了导出函数序号的胚胎序号。假诺Base的值为n,那么导出函数入口地址表中 
的率先个函数的序号正是n,第二个正是n+一… 
NumberOfFunctions钦命了导出函数的总额。 
NumberOfNames钦命了按名称导出的函数的总和。按序号导出的函数总数正是这些值与各市 
总数NumberOfFunctions的差。 
AddressOfFunctions字段是一个大切诺基VA值,指向1个RVA数组,数组中的每一种奥迪Q3VA均指向三个导 
出函数的输入地址。数组的项数等于NumberOfFuntions。 
AddressOfNames字段是一个BMWX三VA值,同样指向三个RAV4VA数组,数组中的每种双字是1个针对性函 
数名字符串的LacrosseVA。数组的项数等于NumberOfNames。 
AddressOfNameOrdinals字段是2个RVA值,它指向1个篇幅组,注意那里不再是双字了!! 
本条数组起着很要紧的职能,它的项数等于NumberOfNames,并与AddressOfNames指向的数组 
逐一对应。其各个类其余值代表了那一个函数在输入地址表中索引。今后大家来看多少个例子, 
一旦八个导出函数Foo在导出入口地址表中居于第m个职分,大家寻找Ordinal数组的第m项, 
假设那些值为x,大家把这么些值与导出序号的初阶值Base的值n相加获得的值正是函数在入口 
地点表中索引。 
下图表示了导出表的组织和上述进度: 
+———————–+         +—————–+ 
|    Characteristics    |  +—-> | ‘dlltest.dll’,0 | 
+———————–+  |      +—————–+ 
|     TimeDataStamp     |  | 
+———————–+  |  +-> +—————–+ 
|      MajorVersion     |  |  | 0 | 函数入口地址牧马人VA | ==> 函数Foo,序号n+0    <–+ 
+———————–+  |  |   +—————–+                            | 
|      MinorVersion     |  |  |   |       …       |                            | 
+———————–+  |  |   +—————–+                            | 
|         Name          | -+  | x | 函数进口地址CRUISERVA | ==> 按序号导出,序号为n+x  | 
+———————–+     |   +—————–+                            | 
|    Base(借使值为n)  |     |   |       …       |                            | 
+———————–+     |   +—————–+                            | 
|   NumberOfFunctions   |     |                                                  | 
+———————–+     |  +-> +—–+     +———-+      +—–+ <-+   | 
|     NumberOfNames     |     |  |   | RVA | –> | ‘_foo’,0 | <==> |  0  | –+—+ 
+———————–+     |  |   +—–+     +———-+      +—–+   | 
|   AddressOfFunctions  | —-+  |   | … |                       | … |   | 
+———————–+        |   +—–+                       +—–+   | 
|     AddressOfNames    | ——-+                                           | 
+———————–+                                                    | 
| AddressOfNameOrdinals | —————————————————+ 
+———————–+ 
好了,上面大家来看构键大家的导出表。假如大家按名称导出二个函数_foo。大家以如下代 
码开始: 
section ‘.edata’ export data    readable 
接着是IMAGE_EXPORT_DIRECTORY结构: 
    dd      0                   ; Characteristics 
    dd      0                   ; TimeDataStamp 
    dw      0                   ; MajorVersion 
    dw      0                   ; MinorVersion 
    dd      RVA dll_name        ; RVA,指向DLL名称 
    dd      0                   ; 开首序号为0 
    dd      1                   ; 只导出一个函数 
    dd      壹                   ; 那几个函数是按名称方式导出的 
    dd      RVA addr_tab        ; BMWX5VA,指向导出函数入口地址表 
    dd      RVA name_tab        ; 揽胜VA,指向函数名称地址表 
    dd      RVA ordinal_tab     ; 昂科威VA,指向函数索引表 
下边大家定义DLL名称: 
    dll_name    db      ‘foo.dll’,0     ; DLL名称,编写翻译的文书名可以与它差异 
接下去是导出函数入口地址表和函数名称地址表,我们要导出二个叫_foo的函数: 
    addr_tab    dd      RVA _foo        ; 函数输入地址 
    name_tab    dd      RVA func_name 
    func_name   db      ‘_foo’,0        ; 函数名称 
末段是函数索引表: 
    ordinal_tab     dw      0           ; 唯有1个按名称导出函数,序号为0 
上面大家看3个总体的程序: 
format  PE GUI 4.0 DLL at 76000000h 
entry   _dll_entry 

2.2.IMAGE_FILE_HEADER(20 bytes)

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;                //运行平台
    WORD    NumberOfSections;        //文件区块数目
    DWORD   TimeDateStamp;            //文件创建日期和时间
    DWORD   PointerToSymbolTable;    //指向符号表(主要用于调试)
    DWORD   NumberOfSymbols;        //符号表中符号个数
    WORD    SizeOfOptionalHeader;        //IMAGE_OPTIONAL_HEADER32 结构大小
    WORD    Characteristics;            //文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

IMAGE_FILE_HEADE奔驰M级结构体成员解析:

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图