9159金沙游艺场

图片 4
IT行业发展前景,IT行业工资一般多少?

windows用户用VMware 虚拟机安装黑苹果Mac.OS.X操作系统

Win32病毒入门(二)

FirstThunk

它包含由IMAGE_THUNK_DATA定义的 first
thunk数组的虚地址,通过loader用函数虚地址初始化thunk。

在Orignal First Thunk缺席下,它指向first thunk:Hints和The Function
names的thunks。

 

下面来解释下OriginalFirstThunk和FirstThunk。就个人理解而言:

1.
在文件中时,他们都分别指向一个RVA地址。这个地址转换到文件中,分别对应两个以
IMAGE_THUNK_DATA 为元素的的数组,这两个数组是以一个填充为 0
的IMAGE_THUNK_DATA作为结束标识符。虽然他们这两个表位置不同,但实际内容是一模一样的。此时,每个
IMAGE_THUNK_DATA 元素指向的是一个记录了函数名和相对应的DLL文件名的
IMAGE_IMPORT_BY_NAME结构体。

  1. 为什么会有两个一模一样的数组呢?是有原因的:

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

重写前:

图片 1

重写后:

 图片 2

(以上两张图片来自:)

typedef struct _IMAGE_THUNK_DATA32 {
    union {
        DWORD ForwarderString;      // PBYTE  指向一个转向者字符串的RVA
        DWORD Function;             // PDWORD 被输入的函数的内存地址
         DWORD Ordinal;              // 被输入的 API 的序数值
         DWORD AddressOfData;        // PIMAGE_IMPORT_BY_NAME   指向 IMAGE_IMPORT_BY_NAME
    } u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;

根据 _IMAGE_THUNK_DATA32 所指虚拟地址转到文件地址可以得到实际的
_IMAGE_IMPORT_BY_NAME 数据

typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD   Hint;     // 序号 

    CHAR   Name[1];  // 实际上是一个可变长的以0为结尾的字符串

} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

 

例如有程序:

图片 3

文字版:

#include <windows.h>
int WINAPI WinMain(_In_ HINSTANCE hInstance, 
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPSTR lpCmdLine,
    _In_ int nShowCmd)
{
    MessageBoxA(0, "hello", "my message", MB_OK);
    SetWindowTextA(0, "Si Wang");

    return 0;
}

此程序使用了两个 Windows API : MessageBoxA 和 SetWindowTextA

编译得到程序(为简化说明,区段位置由软件计算出):

图片 4

图片 5

我们试着找出 MessageBoxA。首先分析 PE 头文件,找到导出表在文件中的位置:

图片 6

输入表位置在 .rdata 区段内, 0x2264 – 0x2000 = 0x0264
得到偏移量。加上文件地址 0x0E00 得到实际文件偏移量(0x0E00 + 0x264 =
0x1064):0x1064。

接下来查看 0x1064 处:

图片 7

可以得到三个 DLL 的描述,最后一个_IMAGE_IMPORT_DESCRIPTOR
以0填充表示结束:

那么只要一个个查看每个DLL对应的数据就能找到,不过之前我把所有的数据都看了下,在第一个DLL中

根据第一个DLL描述的 OriginalFirstThunk 的 0x2350
转换可以知道,_IMAGE_THUNK_DATA32 在文件的 0x1150处,FirstThunk
指向的数据相同:

图片 8

于是就得到了文件中的 MessageBoxA 的信息。

最后,在内存中 FirstThunk 所指位置上的_IMAGE_THUNK_DATA32 数组被
Windows 加载后被重写后就成了传说中的 IAT ,Import Address
Table,输入地址表。使用 OllyDbg 查看运行时情况:

图片 9


; data section… 

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

下面只摘录比较重要的字段:

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 
下面我们用w32dasm反汇编,得到: 
: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 MessageBoxA被翻译成了call 00401014,在这个地址处是一个跳转 
指令jmp dword ptr [00403030],我们可以确定在地址00403030处存放的是MessageBoxA的 
真正地址。 
其实这个地址是位于PE文件的导入表中的。下面我们继续我们的PE文件的学习。我们先来看 
一下导入表的结构。导入表是由一系列的IMAGE_IMPORT_DESCRIPTOR结构组成的。结构的个 
数由文件引用的DLL个数决定,文件引用了多少个DLL就有多少个IMAGE_IMPORT_DESCRIPTOR 
结构,最后还有一个全为零的IMAGE_IMPORT_DESCRIPTOR作为结束。 
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字段是一个RVA,指定了引入的DLL的名字。 
OriginalFirstThunk和FirstThunk在一个PE没有加载到内存中的时候是一样的,都是指向一 
个IMAGE_THUNK_DATA结构数组。最后以一个内容为0的结构结束。其实这个结构就是一个双 
字。这个结构很有意思,因为在不同的时候这个结构代表着不同的含义。当这个双字的最高 
位为1时,表示函数是以序号的方式导入的;当最高位为0时,表示函数是以名称方式导入的, 
这是这个双字是一个RVA,指向一个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,还有一个按序号导入的函数80010002h。 
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表示这是 
一个引入节。readable表示这个节的节属性是只读的。 
假设我们的程序只需要引入user32.dll中的MessageBoxA函数,那么我们的引入节只有一个 
描述这个dll的IMAGE_IMPORT_DESCRIPTOR和一个全0的结构。考虑如下代码: 
    dd      0                   ; 我们并不需要OriginalFirstThunk 
    dd      0                   ; 我们也不需要管这个时间戳 
    dd      0                   ; 我们也不关心这个链 
    dd      RVA usr_dll         ; 指向我们的DLL名称的RVA 
    dd      RVA usr_thunk       ; 指向我们的IMAGE_IMPORT_BY_NAME数组的RVA 
                                ; 注意这个数组也是以0结尾的 
    dd      0,0,0,0,0           ; 结束标志 
上面用到了一个RVA伪指令,它指定的地址在编译时被自动写为对应的RVA值。下面定义我们 
要引入的动态链接库的名字,这是一个以0结尾的字符串: 
    usr_dll     db      ‘user32.dll’,0 
还有我们的IMAGE_THUNK_DATA: 
    usr_thunk: 
        MessageBox      dd      RVA __imp_MessageBox 
                        dd      0                   ; 结束标志 
上面的__imp_MessageBox在编译时由于前面有RVA指示,所以表示是IMAGE_IMPORT_BY_NAME的 
RVA。下面我们定义这个结构: 
    __imp_MessageBox    dw      0                   ; 我们不按序号导入,所以可以 
                                                    ; 简单地置0 
                        db      ‘MessageBoxA’,0     ; 导入的函数名 
好了,我们完成了导入表的建立。下面我们来看一个完整的程序,看看一个完整的FASM程序 
是多么的漂亮 😛 
format  PE GUI 4.0 
entry   __start 

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


; data section… 

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

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这个编译器有了一个初步的认识,也一定有很多读者会说:“ 
这么麻烦啊,干吗要用这个编译器呢?”。是的,也许上面的代码看起来很复杂,编写起来 
也很麻烦,但FASM的一个好处在于我们可以更主动地控制我们生成的PE文件结构,同时能对 
PE文件有更理性的认识。不过每个人的口味不同,嘿嘿,也许上面的理由还不够说服各位读 
者,没关系,选择一款适合你的编译器吧,它们都同样出色 😛 

在 PE文件头的 IMAGE_OPTIONAL_HEADER 结构中的 DataDirectory(数据目录表)
的第二个成员就是指向输入表的。每个被链接进来的 DLL文件都分别对应一个
IMAGE_IMPORT_DESCRIPTOR (简称IID) 数组结构。


; code section… 

section ‘.text’ code    readable executable 
    _foo: 
            push    0 
            push    pszCaption 
            push    pszText 
            push    0 
            call    [MessageBox] 
            ret 
    _dll_entry: 
            xor     eax,eax 
            inc     eax 
            ret     0ch 

Name

它表示DLL
名称的相对虚地址(译注:相对一个用null作为结束符的ASCII字符串的一个RVA,该字符串是该导入DLL文件的名称。如:KERNEL32.DLL)。


; 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 

相关文章

No Comments, Be The First!
近期评论
    功能
    网站地图xml地图