Win32病毒入门,PE结构分析
分类:pc28.am神测网

在 PE文件头的 IMAGE_OPTIONAL_HEADELAND 结构中的 DataDirectory(数据目录表) 的第贰个成员便是指向输入表的。每一个被链接进来的 DLL文件都各自对应三个IMAGE_IMPORT_DESC安德拉IPTO中华V (简称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 
尚未别的烦人的参数,很有益,不是么? :P 
大家先来看一下以此顺序的构造。第一句是format提醒字,它钦定了程序的品种,PE表示自身 
们编写的是贰个PE文件,前边的GUI提醒编写翻译器大家将使用Windows图形分界面。借使要编写制定一 
个调整台应用程序则足以钦定为CONSOLE。纵然要写四个水源驱动,可以内定为NATIVE,表示 
无需子系统协理。最终的4.0点名了子系统的版本号(还记得前边的MajorSubsystemVersion 
和MinorSubsystemVersion么?)。 
上面一行钦赐了程序的输入为__start。 
section提示字表示大家要起来三个新节。大家的前后相继独有多个节,即代码节,我们将其命名 
为.text,并点名节属性为只读(readable)和可实行(executable)。 
自此正是大家的代码了,我们无非用一条ret指令回到系统,这时仓库里的回来地址为Exit- 
Thread,所以程序直接退出。 
上边运转它,程序只是轻便地退出了,大家成功地用FASM编写了二个顺序!大家曾经迈出了 
率先步,上面要让我们的次第能够做点什么。大家想要调用一个API,我们要如何做啊?让 
我们再来充充电吧 :D 

不赖猴的笔记,转发请注解出处。  

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;

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_DESC普拉多IPTOSportage结构组成的。结构的个 
数由文件引用的DLL个数调节,文件引用了有一些个DLL就有个别许个IMAGE_IMPORT_DESCRIPTOR 
布局,最终还会有三个全为零的IMAGE_IMPORT_DESC中华VIPTO宝马X5作为达成。 
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字段是贰个奥迪Q3VA,钦命了引进的DLL的名字。 
OriginalFirstThunk和FirstThunk在叁个PE没有加载到内部存款和储蓄器中的时候是同一的,都以指向一 
个IMAGE_THUNK_DATA结构数组。最终以二个内容为0的布局截止。其实那么些组织正是四个双 
字。那个布局很有意思,因为在不相同的时候那些结构意味着着分裂的意义。当以此双字的参天 
位为1时,表示函数是以序号的不二诀要导入的;当最高位为0时,表示函数是以名称形式导入的, 
这是那些双字是一个ENVISIONVA,指向贰个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,还应该有一个按序号导入的函数800壹仟2h。 
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_DESC福特ExplorerIPTO揽胜极光和二个全0的布局。思索如下代码: 
    dd      0                   ; 大家并无需OriginalFirstThunk 
    dd      0                   ; 我们也没有须要管那个小时戳 
    dd      0                   ; 大家也不关怀这么些链 
    dd      RVA usr_dll         ; 指向我们的DLL名称的PRADOVA 
    dd      RVA usr_thunk       ; 指向大家的IMAGE_IMPORT_BY_NAME数组的RVA 
                                ; 注意这些数组也是以0最后的 
    dd      0,0,0,0,0           ; 截至标记 
上边用到了一个大切诺基VA伪指令,它钦定的地点在编写翻译时被活动写为相应的君越VA值。上面定义大家 
要引进的动态链接库的名字,那是三个以0末尾的字符串: 
    usr_dll     db      'user32.dll',0 
还会有大家的IMAGE_THUNK_DATA: 
    usr_thunk: 
        MessageBox      dd      RVA __imp_MessageBox 
                        dd      0                   ; 截至标识 
上面的__imp_Message博克斯在编写翻译时由于后边有途乐VA提醒,所以表示是IMAGE_IMPORT_BY_NAME的 
CRUISERVA。下边大家定义这么些布局: 
    __imp_MessageBox    dw      0                   ; 我们不按序号导入,所以能够 
                                                    ; 轻松地置0 
                        db      'MessageBoxA',0     ; 导入的函数名 
好了,我们做到了导入表的确立。上面大家来看一个完好无缺的次序,看看四个安然无恙的FASM程序 
是多么的雅观 :P 
format  PE GUI 4.0 
entry   __start 

深刻剖判PE文件

在这么些IID数组中,并未有提议某些许个项(正是未有鲜明指明有多少个链接文件),但它谈到底是以四个全为NULL(0) 的 IID 作为完成的表明。


; data section... 

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

PE文件是Win32的原生文件格式.每二个Win32可实行文件都遵循PE文件格式.对PE文件格式的刺探能够加深你对Win32连串的尖锐了然.

上边只摘录相比较重要的字段:


; code section... 

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

 

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文件有更理性的认知。可是每种人的脾胃不一,嘿嘿,恐怕上边的理由还缺乏说服各位读 
者,没涉及,选拔一款符合你的编写翻译器吧,它们都同样能够 :P 

一、        基本构造。

Name

它意味着DLL 名称的周旋虚地址(译注:相对三个用null作为完结符的ASCII字符串的多个本田CR-VVA,该字符串是该导入DLL文件的称号。如:KECR-VNEL32.DLL)。

5.2、导出表 
----------- 
经过导入表的学习,笔者想各位读者已经对PE文件的读书进度有了温馨认知和措施,所以上边 
关于导出表的一节自己将加快局地进程。“朋友们注意啊!!! @#$%$%&#^”  :D 
在导出表的初步地方是八个IMAGE_EXPORT_DIRECTO奇骏Y结构,但与引进表不一样的是在导出表中 
独有二个以此布局。上面我们来看一下那一个结构的定义: 
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, *Win32病毒入门,PE结构分析。PIMAGE_EXPORT_DIRECTORY; 
Characteristics、MajorVersion和MinorVersion不使用,一般为0。 
TimeDataStamp是时间戳。 
Name字段是八个揽胜VA值,它指向了那些模块的原始名称。那一个称呼与编写翻译后的文书名毫无干系。 
Base字段钦赐了导出函数序号的开场序号。假设Base的值为n,那么导出函数入口地址表中 
的第八个函数的序号就是n,第一个就是n 1... 
NumberOfFunctions钦点了导出函数的总和。 
NumberOfNames钦定了按名称导出的函数的总量。按序号导出的函数总量便是其一值与各省 
总数NumberOfFunctions的差。 
AddressOfFunctions字段是四个奥迪Q7VA值,指向三个奥迪Q5VA数组,数组中的每一种LacrosseVA均指向一个导 
出函数的入口地址。数组的项数等于NumberOfFuntions。 
AddressOfNames字段是八个奇骏VA值,同样指向一个LANDVA数组,数组中的各样双字是贰个对准函 
数名字符串的RubiconVA。数组的项数等于NumberOfNames。 
AddressOfNameOrdinals字段是一个WranglerVA值,它指向四个篇幅组,注意这里不再是双字了!! 
以此数组起着很要紧的功用,它的项数等于NumberOfNames,并与AddressOfNames指向的数组 
依次对应。其种种品种的值代表了那几个函数在进口地址表中索引。未来我们来看一个例证, 
一旦贰个导出函数Foo在导出入口地址表中处于第m个地点,我们探寻Ordinal数组的第m项, 
假使这么些值为x,大家把那一个值与导出序号的开首值Base的值n相加获得的值便是函数在入口 
地点表中索引。 
下图表示了导出表的布局和上述进程: 
-----------------------           -----------------  
|    Characteristics    |   ----> | 'dlltest.dll',0 | 
-----------------------   |       -----------------  
|     TimeDataStamp     |  | 
-----------------------   |   ->  -----------------  
|      MajorVersion     |  |  | 0 | 函数入口地址QX56VA | ==> 函数Foo,序号n 0    <--  
-----------------------   |  |    -----------------                             | 
|      MinorVersion     |  |  |   |       ...       |                            | 
-----------------------   |  |    -----------------                             | 
|         Name          | -   | x | 函数进口地址RubiconVA | ==> 按序号导出,序号为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      1                   ; 这些函数是按名称格局导出的 
    dd      RVA addr_tab        ; 途达VA,指向导出函数进口地址表 
    dd      RVA name_tab        ; 大切诺基VA,指向函数名称地址表 
    dd      RVA ordinal_tab     ; 奥迪Q5VA,指向函数索引表 
上边大家定义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           ; 唯有三个按名称导出函数,序号为0 
上边大家看一个总体的主次: 
format  PE GUI 4.0 DLL at 76000000h 
entry   _dll_entry 

 

FirstThunk

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

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

 

下边来声明下OriginalFirstThunk和FirstThunk。就个人领悟来说:

1. 在文件中时,他们都各自指向一个索罗德VA地址。这么些地点转变来文件中,分别对应七个以 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 – 0x3000 = 0x0264 获得偏移量。加上文件地方 0x0E00 得到实际文件偏移量(0x0E00 0x264 = 0x1064):0x1064。

接下去查看 0x1064 处:

图片 7

能够赢得四个 DLL 的叙说,最终二个_IMAGE_IMPORT_DESCENVISIONIPTO福特Explorer以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 

 


; 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 

 


; 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 

上海体育场地正是PE文件的宗旨结构。(注意:DOS MZ Header和局部PE header的大小是不改变的;DOS stub部分的轻重缓急是可变的。)


; export section... 

section '.edata' export data    readable 
    ; image export directory 
    dd      0,0,0,RVA dll_name,0,1,1 
    dd      RVA addr_tab 
    dd      RVA name_tab 
    dd      RVA ordinal_tab 
    ; dll name 
    dll_name        db      'foo.dll',0 
    ; function address table 
    addr_tab        dd      RVA _foo 
    ; function name table 
    name_tab        dd      RVA ex_foo 
    ; export name table 
    ex_foo          db      '_foo',0 
    ; ordinal table 
    ordinal_tab     dw      0 

四个PE文件至少供给七个Section,贰个是贮存代码,三个寄放数据。NT上的PE文件基本上有9个预约义的Section。分别是:.text, .bss, .rdata, .data, .rsrc, .edata, .idata, .pdata, 和 .debug。一些PE文件中只供给中间的一局地Section.以下是一般的归类:


; relocation section... 

section '.reloc' fixups data     discardable 
前后相继的一开端用format钦命了PE和GUI,在子系统版本号的末尾大家应用了DLL提醒字,表示 
这是叁个DLL文件。最终还大概有三个at关键字,提示了文本的image base。 
次第的最终一个节是重定位节,对于重定位表我不做过多解释,有野趣的读者能够参照他事他说加以考察别的 
图书或文章。我们能够把刚刚的顺序编写翻译成二个DLL: 
fasm foo.asm foo.dll 
上边我们编辑八个测验程序核算程序的不利: 
#include <windows.h> 
int __stdcall WinMain (HINSTANCE,HINSTANCE,LPTSTR,int) 

    HMODULE hFoo=LoadLibrary ("foo.dll"); 
    FARPROC _foo=GetProcAddress (hFoo,"_foo"); 
    _foo (); 
    FreeLibrary (hFoo); 
    return 0; 

我们把编写翻译后的exe和刚刚的dll放在同三个索引下并运营,看看程序运营是或不是正确 :P 

l         试行代码Section , 经常命名字为: .text (MS) or CODE (Borland)

5.3、强大的宏 
------------- 
关于FASM,还会有七个精锐的法力就是宏。我们对宏一定都不面生,上边大家来看看在FASM中 
如何定义宏。假诺大家要编写制定多少个复制字符串的宏,当中源、指标串由ESI和EDI钦定,我们 
可以: 
macro @copysz 

        local   next_char 
    next_char: 
        lodsb 
        stosb 
        or      al,al 
        jnz     next_char 

上面大家再来看一个带参数的宏定义: 
macro @stosd _dword 

    mov     eax,_dword 
    stosd 

即使大家要频仍存入几个例外的双字我们得以归纳地在概念宏时把参数用中括号括起来,比 
如: 
macro @stosd [_dword] 

    mov     eax,_dword 
    stosd 

如此那般当大家调用@stosd 1,2,3的时候,大家的代码被编写翻译成: 
mov     eax,1 
stosd 
mov     eax,2 
stosd 
mov     eax,3 
stosd 
对此这种多参数的宏,FASM提供了八个伪指令common、forward和reverse。他们把宏代码分 
成块并各自管理。上面笔者分别来介绍: 
forward限定的块象征指令块对参数举办逐项管理,例如上面的宏,固然把上边包车型大巴代码定义在 
forward块中,大家得以拿走平等的结果。对于forward块我们能够如此定义 
macro @stosd [_dword] 

    forward 
        mov     eax,_dword 
        stosd 

reverse和forward正好相反,表示指令块对参数进行反向管理。对于地点的命令块假若用 
reverse限定,那么大家的参数将被根据相反的次第存入内存。 
macro @stosd [_dword] 

    reverse 
        mov     eax,_dword 
        stosd 

此时当我们调用@stosd 1,2,3的时候,我们的代码被编写翻译成: 
mov     eax,3 
stosd 
mov     eax,2 
stosd 
mov     eax,1 
stosd 
common限定的块将仅被拍卖管理壹次。大家前天编写一个调用API的宏@invoke: 
macro @invoke _api,[_argv] 

    reverse 
        push    _argv 
    common 
        call    [_api] 

现行反革命我们能够运用那些宏来调用API了,譬如: 
@invoke     MessageBox,0,pszText,pszCaption,0 
对于宏的使用大家就介绍这几个,越来越多的代码可以参照笔者的useful.inc(当中有相当多29A的宏, 
tnx 29a :P)

l         数据Section, 平常命名叫:.data, .rdata, 或 .bss(MS) 或 DATA(Borland).

l         财富Section, 日常命名字为:.edata

l         输入数据Section, 平常命名称为:.idata

l         调节和测试信息Section,平时命名字为:.debug

那个只是命名方式,便于分辨。平时与系统并无一向关乎。常常,一个PE文件在磁盘上的影像跟内部存款和储蓄器中的基本一致。但并非全然的正片。Windows加载器会决定加载哪些部分,哪些部分没有需求加载。并且由于磁盘对齐与内部存款和储蓄器对齐的不一样样,加载到内部存款和储蓄器的PE文件与磁盘上的PE文件相继部分的布满都会有反差。

 

 

 

当叁个PE文件被加载到内部存款和储蓄器后,就是大家常说的模块(Module),其开始地址正是所谓的HModule.

 

二、        DOS头结构。

具备的PE文件都以以二个64字节的DOS头初始。那几个DOS头只是为了协作前期的DOS操作系统。这里不做详细解说。只须要领会一下里面多少个有效的数目。

 

 

 

1.       e_magic:DOS头的标志,为4Dh和5Ah。分别为字母MZ。

2.       e_lfanew:两个双字数据,为PE头的离文件底部的偏移量。Windows加载器通过它能够跳过DOS Stub部分直接找到PE头。

3.       DOS头后跟二个DOS Stub数据,是链接器链接执行文书的时候进入的有的数据,一般是“This program must be run under Microsoft Windows”。这些能够因而修改链接器的设置来修改成温馨定义的数额。

 

三、        PE头结构。

PE头的数据结构被定义为IMAGE_NT_HEADEKoleosS。包蕴三局地:

 

 

1.       Signature:PE头的标记。双字结构。为50h, 45h, 00h, 00h. 即“PE”。

2.       FileHeader:20字节的数目。包涵了文本的物理层消息及文件属性。

 

 

 

 

此地最首要注意三项。

l         NumberOfSections:定义PE文件Section的个数。若是对PE文件新增添或删除Section的话,绝对要记的改动此域。

l         SizeOfOptionalHeader:定义OptionHeader结构的高低。

l         Characteristics:主要用来标志当前的PE文件是实行文书或许DLL。其各位都有实际的意思。

数据位

Windows.inc的预定义

为1时的含义

0

IMAGE_FILE_RELOCS_STRIPPED

文件中不存在重定位信息

1

IMAGE_FILE_EXECUTABLE_IMAGE

文件是可执行的

2

IMAGE_FILE_LINE_NUMS_STRIPPED

不存在行信息

3

IMAGE_FILE_LOCAL_SYMS_STRIPPED

不存在符号信息

7

IMAGE_FILE_BYTES_REVERSED_LO

小尾方式

8

IMAGE_FILE_32BIT_MACHINE

只在32位平台运行

9

IMAGE_FILE_DEBUG_STRIPPED

不包含调试信息

10

IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP

不能从可移动盘运行

11

IMAGE_FILE_NET_RUN_FROM_SWAP

不能从网络运行

12

IMAGE_FILE_SYSTEM

系统文件。不能直接运行

13

IMAGE_FILE_DLL

DLL文件

14

IMAGE_FILE_UP_SYSTEM_ONLY

文件不能在多处理器上运行

15

IMAGE_FILE_BYTES_REVERSED_HI

大尾方式

 

3.       OptionalHeader:总共2二十多个字节。最终129个字节为多少目录(Data Directory)。

 

 

 

 

以下是字段的认证:

l         AddressOfEntryPoint:程序入口点地址。但加载器要运维加载的PE文件时要进行的率先个指令的地址。它是三个TucsonVA(相对设想地址)地址。一些对PE文件插入代码的顺序正是修改此处的地点为要运维的代码,然后再跳转回此处原来的地址。

l         ImageBase:PE文件被加载到内部存款和储蓄器的指望的营地址。对于EXE文件,通常加载后的地点就目的在于的地点。可是DLL却恐怕是任何的。因为假诺这几个地址被占,系统就能重新分配一块新的内部存款和储蓄器,同时会修改此处加载后的地方。EXE文件一般是四千00h.

l         SectionAlignment:每四个Section的内存对齐粒度。比方:此值为4096(1000h),那么每三个Section的开始地址都应有是4096(一千h)的大背头倍。要是第1个Section的地点是40一千h,大小为九十九个字节。那么下一个Section的苗头地址为40两千h.。七个Section之间的长空超过一半是空的,未用的。

l         FileAlignment:每三个Section的磁盘对齐粒度。譬如,此值为512(200h),那么每一个Section在文书内的摇曳地点都以512(200h)的整数倍。与SectionAlignment同理。

l         SizeOfImage:PE文件在内部存款和储蓄器空间整个印象的深浅。富含全体的头及按SectinAlignment对齐的具备的Section。

l         SizeOfHeaders:富有的头加上Section表的大大小小。也便是文件大小减去文件中有着Section的分寸。能够用那个值获取PE文件中首先Section的地方。

l         DataDiretory:16个IMAGE_DATA_DIRECTO奥迪TT RSY结构的数组。每三个分子都对应几个主要的数据结构,例如输入表,输出表等。

 

有七个地方供给留心:

l         假若PE header里的尾声五个字段被给予三个冒牌的值的话,举个例子:

n         LoaderFlags = ABDBFFFFh    (其默许值为0)

n         NumberOfMuranovaAndSizes = DFFDEEEEh (其默许值为10h)

一部分调弄整理工科具或反编写翻译工具会感到这几个PE文件是磨损的。有的会间接实行,假使是病毒的话,就能够被平昔感染;有的则会重启工具。所以最棒在查看调节和测验贰个PE文件前,先看一下那边的取值是不是被人予以多少个冒充的非常大的值。倘若是的话,先修改成默许的值。

l         有人大概注意到在有的PE文件(MS的链接器链接的PE文件)的DOS Stub部分跟PE header部分之间存在部分污源数据。标记为其尾数第二非0的双字节是叁个“Rich ”。这一部分数据包涵了部分加密数据,来标识编写翻译那么些PE文件的机件。可用来举报有些病毒程序所编写翻译的次第来自哪台机器。

 

 

四、        数据目录结构(Data Directory)。

DataDirectory是OptionalHeader的最后128个字节,也是IMAGE_NT_HEADE中华VS的末尾一部分数据。它由15个IMAGE_DATA_DIRECTO奥迪Q5Y结构重组的数组构成。IMAGE_DATA_DIRECTO纳瓦拉Y的组织如下:

 

 

每一个IMAGE_DATA_DIRECTO大切诺基Y都以对应一个PE文件器重的数据结构。他们各自如下:

 

 

VirtualAddress指的是对应数据结构的凯雷德VA地址;iSize指的是对应数据结构的轻重缓急(字节单位)。叁个PE文件一般只含有在那之中的一部分,也等于在那之中有个别数据结构是有多少的;另一有个别则皆以0。比如,EXE文件一般都设有IMAGE_DIRECTORY_ENTRY_IMPORT(输入表),而一纸空文IMAGE_DIRECTORY_ENTRY_EXPORT(输出表)。而DLL则两个都满含。下图正是某一个PE文件的数量目录:

 

 

 

五、        Section表。

Section表紧跟在PE header后边。由IMAGE_SECTION_HEADE本田UR-V数据结构组成的数组。每三个带有了对应Section在PE文件中的属性和摇头地点。

 

 

此处不是有所的分子都以立见成效的。

l         Name1: 块名,那是三个8位ASCII码名,用来定义块名。非常多块名以一个"."早先(如.text),纵然相当多PE文书档案都感到那一个"."实际上并不是必需的。值得注意的是,假使块名超越8位,则最终的NULL不设有。带有贰个"$"的区块名字会从链接器这里获得极度的对待,后边带"$"的大同小异名字的区块被合併,在统一后的区块中它们是按"$"后边的字符字母顺序实行联合的。

 

l         Misc.VirtualSize : 提出实际的、被使用的区块大小。纵然VirtualSize大于SizeOfRawData,那么SizeOfRawData来自于可试行文件初始化数据的轻重,与VirtualSize相差的字节用0填充。那么些字段在OBJ文件中设为0。

 

l         VirtualAddress : 该块装载到内部存款和储蓄器中的科雷傲VA。这么些地址是遵循内部存款和储蓄器页对齐的,它的数值总是SectionAlignment的板寸倍。在MS工具中,第一块的暗中认可智跑VA为一千H.在OBJ中,该字段没意义。要是该值为一千H, PE文件被加载到五千00H,那么该Section的胚胎地址为40一千H。

 

l         SizeOfRawData : 该块在磁盘文件中所占的尺寸。在可实施文件中,那一个值必得是PE尾部钦点的文本对齐大小的倍数。要是是0,则表达区块中的数据是未最早化的。该块在磁盘文件中所占的高低,那几个数值等于VirtualSize字段的值依照FileAlignment的值对齐未来的大小。比如,FileAlignment的轻重缓急为一千H,若是VirtualSize中的块长度为2911,则SizeOfRawData为三千H}

 

l         PointerToRawData : 该块在磁盘文件中的偏移。对于可施行文件,那么些值必得是PE底部钦赐的文本对齐大小的翻番。

 

l         PointerToRelocations : 这一部分在EXE文件中无意义。在OBJ文件中,表示本块重定位消息的偏移量。在OBJ文件中如果不是零,则会针对叁个IMAGE_RELOCATION的数据结构。

 

l         NumberOfRelocations : 由PointerToRelocations指向的重定位的多寡。

 

l         NumberOfLinenumbers : 由NumberOfRelocations指向的行号的数目,只在COFF样式的行号被指按时利用。

 

l         Characteristics :  块属性,该字段是一组建议块属性(如代码/数据/可读/可写等)的标识。八个标记值通过O传祺操作产生Characteristics的值。这么些标识相当多都足以通过链接器/SECTION选项设置。

数据位在Windows.inc中的预定义

为1时的含义

IMAGE_SCN_CNT_CODE (00000020H)

节中包含代码

IMAGE_SCN_CNT_INITIALIZED_DATA (00000040H)

节中包含已初始化数据

IMAGE_SCN_CNT_UNINITIALIZED_DATA (00000080H)

节中包含未初始化数据

25

IMAGE_SCN_MEM_DISCARDABLE (02000000H)

节中的数据在进程开始后将被丢弃

26

IMAGE_SCN_MEM_NOT_CACHED (04000000H)

节中的数据不会经过缓存

27

IMAGE_SCN_MEM_NOT_PAGED (08000000H)

节中的数据不会被交换到磁盘

28

IMAGE_SCN_MEM_SHARED (10000000H)

节中的数据将被不同的进程所共享

29

IMAGE_SCN_MEM_EXECUTE (20000000H)

映射到内存后的页面包含可执行属性

30

IMAGE_SCN_MEM_READ (40000000H)

映射到内存后的页面包含可读属性

31

IMAGE_SCN_MEM_WRITE (80000000H)

映射到内存后的页面包含可写属性

 

 

六、        PE文件相继Section。

PE文件的Sections部分含有了文件的剧情。满含代码,数据,财富和别的可实施新闻。每叁个Section由五个头顶和一个数量部分构成。全数的尾部都存放在紧跟PE header后的Section表内。

1.       实践代码。

在NT Windows系统内,全部的PE文件的代码段都寄存在贰个Section内,平时命名称叫.text(MS)或CODE(Borland)。这一段蕴涵了从前说起的AddressOfEntryPoint多指地址的吩咐及输入表中的jump thunk table。

2.       数据。

l         .bss段贮存未最早化的数码,包蕴函数内或源模块内表明的静态变量。

l         .rdata段存放只读数据,举例常字符串,常量,调节和测验提醒音讯。

l         .data 段存放别的兼具的数额(除了自动化变量,其贮存在栈中)。例如程序的全局变量。

3.       资源。

.rsrc段包蕴了三个模块的资讯。以财富树的结构贮存数据。要求用工具来查看。

4.       输出数据。

.edata段包涵了PE文件的输出目录(Export Directory)。

5.       输入数据。

.idata富含了PE文件的输入目录和输入地址表。

6.       调节和测量试验新闻。

调节和测量检验音讯贮存在.debug段。PE文件也帮忙单独的调治文件。Debug段满含调节和测量试验音信,不过调试目录却寄存在.rdata内。

7.       线程局地存储。(TLS)

Windows帮助每二个进程包涵两个线程。每一个线程有其个人的蕴藏空间(TLS)去存放线程自个儿的数量。链接器都会为经过成立一个.tls段来寄存TLS模板。当进度创建一个线程时,系统就能依照那几个模板创设一个线程私有的一对存储空间。

8.       基重定位。

当加载器加载PE文件到内部存款和储蓄器的时候,有时候不分明是其预期的营地址。那么就供给调治之中指令的对峙地址。全体必要调动的地址都存放在.reloc段内。

 

七、输出Section.

 

其一Section跟DLL关系异常细致。DLL一般定义二种函数,内部使用的,和输出到表面给任何调用程序选择的。输出到表面包车型客车函数就存款和储蓄在这一个Section内。

DLL输出函数分二种办法,通过名称和经过序号输出。当别的程序需求调用DLL的时候,调用GetProcAddress,通过设置需求调用的函数名称或函数序号能够调用DLL内部输出的函数。

 

 

那么GetProcAddress是怎么获取DLL中确实的出口函数地址呢?以下是事无巨细的演说。

PE头的数额目录(DATA DIRECTOEscortY)数组的首先个成员对应的(通过中间的宝马X5VA地址可得到)数据结构是IMAGE_EXPORT_DIRECTO卡宴Y(这里名叫输出目录)。

 

 

 

成员

大小

描述

Characteristics

DWORD

未定义,总是0

TimeDateStamp

DWORD

输出表的创建时间。与IMAGE_NT_HEADER.FileHeader.TimeDateStamp有相同的定义

MajorVersion

WORD

输出表的主版本号。未使用,为0

MinorVersion

DWORD

输出表的次版本号。未使用,为0

nName

DWORD

指向一个ASCII字符串的RVA,这个字符串是与这些输出函数关联的DLL的名称(比如,Kernel32.dll)。这个值必须定义,因为如果DLL文件的名称如果被修改,加载器将使用这里的名称。

nBase

DWORD

这个字段包含用于这个可执行文件输出表的起始序数值(基数)。正常情况下为1,但不是一定是。当通过序数来查询一个输出函数时,这个值会被从序数里减去。(比如,如果nBase = 1,被查询的函数的序数是3,那么这个函数在序号表的索引是3 -1 = 2)。

NumberOfFunctions

DWORD

输出地址表(EAT)的条目数。其中一些条目可能是0,意味着这个序数值没有代码和数据输出。

NumberOfNames

DWORD

输出名称表(ENT)的条目数。这个值总是大于或等于NumberOfFunctions。小于的情况发生在符号只通过序数来输出时。另外,当被赋值的序数里有数字间隔时也会有小于的情况。这个值也是输出序数表的长度。

AddressOfFunctions

DWORD

输出地址表(EAT)的RVA。输出地址表本身是一个RVA数组,数组中的每一个非零的RVA都对应一个被输出的符号。

AddressOfNames

DWORD

输出名称表(ENT)的RVA。输出名称表本身是一个RVA数组。数组中的每一个非零的RVA都向一个ASCII字符串。每一个字符串都对应一个通过名称输出的符号。这个表是排序。这允许加栽器在查询一个被输出的符号时可用二进制查找方式。名称的排序是二进制的,而不是按字母。

AddressOfNameOrdinals

DWORD

输出序数表(EOT)的RVA。这个表将ENT中的数组索引映射到相应的输出地址条目。

 

实际上,IMAGE_EXPORT_DIRECTOEvoqueY结构指向多个数组和一个ASCII字符串表。当中最首要的是出口地址表(EAT,即AddressOfFunctions指向的表), 输出函数地址指针(奥迪Q5VA)构成了这几个表。而ENT和EOT则是能够共同同盟来得到EAT里对应的地址数据。下图演示了那么些进度。

 

 

本条被加载的DLL的称号是F00.DLL。总共输出了七个函数,其科雷傲VA地址分别为0x四千42、0x400156、0x401256和0x400520。贰个表面调用程序须求调用在那之中二个名称为”Bar”的函数,那么它先在出口名称表(ENT)里探寻名称叫Bar的函数,找到后,依照其在出口序号表(EOT)中对应的索引号,获取当中的数值为EAT中的索引值,这里是4,然后从EAT中根据索引4得到其确实的ENVISIONVA地址0x400520。以下是多少个注意点:

l         输出序号表(EOT)的留存正是为了是EAT跟ENT之间爆发关联。每四个ENT内的分子(函数名)有且唯有二个EAT内的积极分子(函数地址)对应。但是两个EAT内的成员并非独有四个ENT内的分子对应。举例,有的函数存在小名的话,就能够油但是生多个ENT内的成员都对应一个EAT内的成员。

l         要是已经获取叁个函数的序号值,那么就能够向来到EAT内获取其奥德赛VA地址,而没有须要通过ENT和EOT实行寻觅。可是那样的按序号出口的DLL不易于维护。

l         平常意况下,EAT的个数(NumberOfFunctions)必需低于或等于ENT的个数(NumberOfNames)。唯有在三个函数按序号输出时(其在ENT和EOT表里未有相应的数目),ENT的数目才有希望少于EAT的数码。比方,总共有七20个函数输出,不过在ENT表里唯有叁21个,那就意味着剩余的二十捌个函数是靠序号输出的。那么大家什么样通晓哪些是一贯靠序号输出的吧?唯有通过排除法来收获。把存在在EOT表里的序号从EAT里清除出去,剩下的正是靠序号输出的函数。

l         当通过贰个序号值来得到EAT内的函数奥迪Q3VA时,要求把这么些序号值减去nBase的值来获得在EAT表里真的的目录地方。而经过名称查找则无需那样做。

l         输出转速。有个别时候,你从一个DLL中调用的三个函数大概位于另多少个DLL中。那就叫输出转速。比方,Kernel32.dll中的HeapAlloc就是转到调用NTDLL.dll中的本田UR-VtlAllocHeap。这种转化是在链接的时候,在.DEF文件中定义二个非同一般的指令来兑现的。那么当一个函数被转正后,在其所在EAT表里对应的多少便不是其地点,而是二个针对性表明被转化的DLL和函数的ASCII字符串的地方指针。

 

 

上海体育场面正是Kernel32.dll的出口函数表,个中HeapAlloc的宝马7系VA值0x00009048便是三个对准“NTDLL.LacrossetlAllocHeap”的指针。

 

 八 、        输入Section.

  输入Section平日位于.idata段内。它包含了具备程序须要采纳的来自另外DLL的函数的音讯。Windows加载器担负加载全部程序用到的DLL到进度空间。然后为经过找到全部其急需接纳的函数的地点。上面描述那个进程:

 

 

PE头的数据目录(DATA DIRECTORubiconY)数组的第一个成员对应的(通过内部的EnclaveVA地址可获取)数据结构是输入表。输入表是叁个IMAGE_IMPORT_DESCTucsonIPTOLacrosse数据结构的数组。未有字段注解那一个数组的个数,只是它的尾声一个分子的数量都为0。每一个数组成员都对应 三个DLL。

 

 

 

成员 大小 描述
OriginalFirstThunk DWORD 指向输入名称表(INT)的RVA。INT是由IMAGE_THUNK_DATA数据结构构成的数组。数组中的每一个成员定义了一个输入函数的信息,数组最后以一个内容为0的IMAGE_THUNK_DATA结束。
TimeDateStamp DWORD 当执行文件不与被输入的DLL进行绑定时,这个字段为0。当以旧的方式绑定时,这个字段包括时间/日期。当以新的样式绑定时,这个字段为-1。
ForwarderChain DWORD 这是第一个被转向的API的索引。老样式绑定的定义。
Name DWORD 指向被输入DLL的ASCII字符串的RVA。
FirstThunk DWORD 指向输入地址表(IAT)的RVA。IAT也是一个IMAGE_THUNK_DATA数据结构的数组。

 

由上表可知,输入表主假使因此IMAGE_THUNK_DATA那几个数据结构导入函数。上面是IMAGE_THUNK_DATA的描述:

 

这是一个DWOCR-VD联合体数据结构。其实这里对输入表有意义的字段独有八个,Ordinal和 AddressOfData。当以此DWO奥迪Q5D数据的万丈位为1的时候,代表函数以序号的秘诀导入,Ordinal的低三二十一个人就是输入函数在其DLL内的 导出序号。当那几个DWO福特ExplorerD的多少最高位为0的时候,代表函数以字符串方式导入。AddressOfData正是一个对准用来导入函数名称的 IMAGE_IMPORT_BY_NAME的数据结构的CRUISERVA。(这里用来决断最高位的值0x7000000,预约义值为 IMAGE_ORDINAL_FLAG32。)

 

 

l         Hint字段也意味函数的序号,重倘若用来便与加载器快捷搜索在导入的DLL的函数导出表,当通过那些序号查找到的函数跟所要导入的函数不相称时,就改为通过名称查找。可是那个字段是可选的,有个别编写翻译器把它设置为0。

l         Name1字段定义了导入函数的名号字符串,那是多个以0为最终的字符串。

 

方方面面经过有一点点复杂,下图给出八个相对清晰的陈述。

 

1. 加载器首先读入IMAGE_IMPORT_DESCGL450IPTOCR-V,得到须要加载的动态库User32.DLL。

2. 加载 器依据OriginalFirstThunk或FirstThunk所针对的IMAGE_THUNK_DATA数组的揽胜VA来取得真正的输入函数名称表 (INT)和输入函数地址表(IAT)。这里那多个表所指向的是同一个IMAGE_IMPORT_BY_NAME数据结构的库罗德VA。

3. 加载器依照IMAGE_IMPORT_BY_NAME的序号或称谓到导入的DLL(user32.dll)函数导出表中获取导入函数的地址。然后把那些位置替换掉FirstThunk所指向的函数输入地方表中的数据。

上海教室已经认证了怎会设有五个一样的IMAGE_THUNK_DATA数组。答案正是在那个PE文件被装入内部存款和储蓄器后,FirstThunk所针对的IMAGE_THUNK_DATA内的值将被改为用来积攒导入函数的实在的地点。大家称之为IAT(Import Address Table). 其实在数码目录表DATA_DIRECTOWranglerY中的第13项(索引为12)直接提交了这些IAT的地方和大小. 能够直接通过数据目录飞快获得那一个IAT表. 可是如此还不足于表达为啥会设有八个同样的IMAGE_THUNK_DATA数组。INT好象未有存在的 供给。这里要涉及到二个绑定的定义。

绑定:

l         在 加载器加载PE文件的时候,先必要检查输入表获取要输入的DLL的名号,然后把DLL映射到进程的地点空间。再自作者钻探IAT表里的 IMAGE_THUNK_DATA数组所指向的字符串获取要输入函数的名号,然后用输入函数的地方替换掉IMAGE_THUNK_DATA数组内的数量。 整个经过要求绝对相比较长的日子。就算事先在链接的时候就把这几个地点写入IAT中,那么就能够省掉数不完岁月。那便是绑定的由来。

l         再绑定后,PE文件IAT表里放着是导入DLL输出函数的莫过于内存地址。要使绑定的结果能健康运维,要求三个尺码:

n         在加载PE文件所需的DLL的时候,DLL应该被映射到它们本人PE头里定义好的ImageBase那个地址。

n         被实践绑定后,PE文件所导入DLL的函数导出的函数表里的函数符号的职分无法生出转移。

l         这多个规格当然很难在长日子内很难满足。比如,这么些被导入的DLL发生了变动,扩展了新的函数输出。那么其原来输出表内的函数符号的职务爆发了扭转。那么今年,原先绑定的结果就能够生出错误。为了缓和那几个难点,所以就相同的时间定义了INT那个表。让它做为IAT的备份。一旦预先绑定好的IAT产生了错误,那么 加载器便会从INT里拿走所急需的新闻。

那就是干吗会存在八个同样的IMAGE_THUNK_DATA数组真正的案由。微软的链接器一般总会在生成IAT的同时生成一个INT;而Borland的链接器却只生成IAT。所以Borland生成的PE文件是不能够被绑定的。

那么,当加载器加载PE文件的时候,必要看清当前的绑定是不是行得通。在多少目录(Data Directory)的第12项(序号为11)所针对的一组数据结构IMAGE_BOUND_IMPORT_DESCLacrosseIPTOEnclave就是用来检查这一个有效的。

 

成员 大小 描述
TimeDateStamp DWORD 必须与被输入的DLL的PE头内的TimeDateStamp一样,如果不一致,那么加载器就会认为绑定的对象有误,需要重新修补输入表。
OffsetModuleName WORD 第一个IMAGE_BOUND_IMPORT_DESCRIPTOR结构到被输入DLL名称的偏移(非RVA)。
NumberOfModuleForwarderRefs WORD 包含紧跟在这个结构后面IMAGE_BOUND_FORWARDER_REF的数目。

 

以此结构跟IMAGE_BOUND_IMPORT_DESCCRUISERIPTO奥迪Q3其实很象除了倒数分子。它至关主要用来,在被导入的DLL中的某三个函数是转载导出时,这一个布局就用来给出所转向到的函数的消息。

推迟加载:

除开通过加载器营造IAT表以外,程序调用外界DLL函数还应该有别的一种办法。便是先经过LoadLibrary动态加载DLL,然后用GetProcAddress获取所需函数的地址。这种措施叫做“延迟加载”。

数据目录(Data Directory)第17个分子(序号是13)IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT条目款项正是指向延迟加载的数据。这些数据就是由二个叫作ImgDelayDescr数据结构组成的数组。

 

ImgDelayDescr = packed record
grAttrs: DWORD;
szName: DWORD;
phmod: PDWORD;
pIAT: TImageThunkData32;
pINT: TImageThunkData32;
pBoundIAT: TImageThunkData32;
pUnloadIAT: TImageThunkData32;
dwTimeStamp: DWORD;

end;

 

成员 描述
grAttrs 设为1的时候,下面的各个成员都是RVA,否则是VA(虚拟地址)。
szName 指向一个DLL名称的RVA。
phmod 指向一个HMODULE的RVA。
pIAT 指向DLL的IAT的RVA。
pINT 指向DLL的INT的RVA。
pBoundIAT 可选的绑定IAT的RVA。
pUnloadIAT 指向DLL的IAT的未绑定拷贝
dwTimeStamp 延迟装载的输入DLL的时间/日期。通常是0。

 

 

九、        Windows加载器

加载器读取叁个PE文件的长河如下:

1. 先读入PE文件的DOS头,PE头和Section头。

2. 然后依据PE头里的ImageBase所定义的加载地址是还是不是可用,假若已被别的模块占用,则重新分配一块空间。

3. 依照Section底部的消息,把公文的次第Section映射到分配的半空中,并依照各样Section定义的数量来修改所映射的页的习性。

4. 一旦文件被加载的地点不是ImageBase定义的地点,则另行改进ImageBase。

5. 依照PE文件的输入表加载所急需的DLL到进程空间。

6. 然后替换IAT表内的多寡为实在调用函数的地址。

7. 基于PE头内的数不熟悉成开始化的堆和栈。

8. 创立起头化线程,开始运营进程。

那边要提的是加载PE文件所需DLL的进程是起家在四个底层的API上。

LdrpCheckForLoadedDll:检查要加载的模块是或不是早就存在。

LdrpMapDll:映射模块和所需音讯到内部存款和储蓄器。

LdrpWalkImportDescriptor:遍历模块的输入表来加载其所需的其余模块。

LdrpUpdateLoadCount:计数模块的施用次数。

LdrpRunInitializeRoutines:初步化模块。

LdrpClearLoadInProgress:清楚一些标识,注解加载已经产生。

 

十、        插入代码到PE文件

有两种艺术得以插入代码到PE文件:

1. 把代码插足到二个存在的Section的未用空间里。

2. 扩大学一年级个存在的Section,然后把代码参与。

3. 骤增贰个Section。

办法一、扩充代码到二个设有的Section。

率先大家须求找到贰个被映射到多少个块有进行权限的Section。最简单易行的章程正是平素利用CODE Section。

接下来大家须要探寻那块Section内的剩余空间(也正是填满了00h)。大家明白三个Section有四个数据来代表其大小。 VirtualSize和SizeOfRawData。这么些VirtualSize代表Section里代码实际所占用的磁盘空间。 SizeOfRawData代表基于磁盘对齐后所占的上空。平时SizeofRawData都会比VirtualSize要大。如下图。

 

图中的SizeOfRawData是0002A000,而VirtualSize是00029E88。当PE文件被加载到内部存款和储蓄器的时候,他们中间 的剩余空间的数量是不会被加载到内部存款和储蓄器去。那么只要要把参与到那些空隙中间的代码也被加载到内部存款和储蓄器去,就必要修改VirtualSize的值,这里把 VirtualSize的值能够改为00029FFF。这样,大家就有了一小段空间参预自个儿的代码。下边需求做的正是先找到PE文件的入口点 OriginalEntryPoint,例如那个OriginalEntryPoint是0002ADB4,ImageBase是五千00,那么入口 点的实在设想地址是0042ADB4。然后总计出团结代码的发轫福特ExplorerVA,退换掉PE头内的OriginalEntryPoint,在友好的代码最终加上:

MOV EAX,00042ADB4

JMP EAX

与此相类似就能够在PE文件被加载的时候,先运营本人的代码,然后再运维PE文件本身的代码。成功的把代码插足到了PE文件内。

  

艺术二、增加学一年级个设有的Section来加入代码。

要是在三个Section末尾未有丰富的上空贮存本人的代码,那么其他一种格局正是扩展叁个存在的Section。一般我们只扩展PE文件最尾部的Section,因为这么能够免止过多问题,譬喻对其它Section的震慑。

先是咱们的找到最终多个Section使之可读可进行。那足以经过改变其对应Section底部的Characteristics来得到。然后 遵照PE头内文件对齐的深浅,修改其SizeOfRawData。譬如文件对齐的轻重是200h,原先SizeOfRawData=0000柒仟h, 那么我们增添的空中尺寸应该是200h的卡尺头倍,修改完的SizeOfRawData至少是00008200h。增添完空间后,须要修改PE头内的多个字 段的数值,SizeOfCode和SizeOfInitialishedData。分别为它们扩大200h的分寸。那样大家就马到功成的强大了三个Section,然后根据章程一内的主意把代码参与到增添的空间。

 

方法三、新添二个Section来参预代码。

假使要参与的代码比很多,那么就须要新扩充一个Section来存放自身的代码。

l         首先,我们须要在PE头内找到NumberOfSections,使之加1。

l         然后,在文书末尾扩展一个新的空中,假如为200h,记住早先行到PE文件首部的舞狮。假若那么些值是00034500h。同有时间将PE头内的SizeOfImage的值加200h。

l         然后,找到PE头内的Section尾部。日常在Section底部结束到Section数据部分早先间会有一点空中,找到Section底部的末段然后踏向贰个新的头顶。假设最后一个Section尾部的数据是:

1. Virtual offset : 34000h

2. Virtual size : 8E00h

3. Raw offset: 2F400h

4. Raw size : 8E00h

而文件对齐和Section对齐的数额分别是:

5. Section Alignment : 1000h

6. File Alignment : 200h

l         那么新添的Section必需与终极贰个Section的界线对齐。它的数量分别:

1. Virtual offset : 3D000h (因为最终叁个Section的终极界限是3陆仟h 8E00h = 3CE00h,加上Section对齐,则Virtual offset的值为3D000h)。

2. Virtual size : 200h。

3. Raw offset: 00034500h。

4. Raw size: 200h.

5. Characteristics : E0000060 (可读、可写、可执行)。

l         最后,只需求修改一下PE头内的SizeOfCode和SizeOfInitialishedData多个字段,分别增进200h。

l         剩下的正是依据办法一的点子把代码归入就能够。

 

 

十一、        增添实施文书的输入表项目。

在一些极度用途上,大家需求为实施文书或DLL增添其不包蕴的API。那么能够经过扩张那个API在输入表中的挂号来到达。

1. 每二个输入的DLL都有贰个IMAGE_IMPORT_DESCCR-VIPTO揽胜极光(IID)与之相应。PE头中的最后一个IID是以全0来代表一切IID数组的结束。

2. 每叁个IID至少要求五个字段Name1和FirstThunk。别的字段都足以安装为0。

3. 每多少个FirstThunk的多寡必得是多个指向IMAGE_THUNK_DATA数组的RVA。每一个IMAGE_THUNK_DATA又富含了指向一个API名称的GL450VA。

4. 只要IID数组发生转移,那么只需求修改数据目录数组中对应输入表的数据结构IMAGE_DATA_DIRECTORY的iSize。

追加二个新的IID到输入表的终极,正是把输入表末尾的全都以0的IID修改成扩张的新的IID,然后在增添三个全0的IID作为输入表新的末 尾。不过假使在输入表末尾未有空间的话,那就须要拷贝整个输入表到三个新的足足的长空,同一时间修改数据目录数组对应输入表的数据结构 IMAGE_DATA_DIRECTORY的RVA和iSize。

步骤一、增添一个新的IID。

  •   把整个IID数组移到贰个有丰裕空间来扩充三个新的IID的地方。那些地点能够是.idata段的最后或是新添贰个Section来寄放。

  •   修改数据目录数组对应输入表的数据结构IMAGE_DATA_DIRECTORY的RVA和iSize。

  •   若是须求,将存放新IID数组的Section大小依据Section Alignment向上取整(譬如,原本大小是1500h, 而section Alignment为一千h,则调治为三千h)以便于整个段能够被映射到内部存款和储蓄器。

  •   运行活动过IID数组的推行文书,若无难点的话,则开展第二步骤。若是不办事来说,须要检查新扩展的IID是还是不是曾经被映射到内部存款和储蓄器及IID数组新的舞狮地方是否准确。

 

步骤二、增添二个新的DLL及其要求的函数。

  • 在.idata节内扩张多个以null结尾的字符串,贰个用来存放在新添的DLL的名字。 三个用来存放要求导入的API的称谓。那些字符串前须求追加三个为null的WOPorsche911D字段来组成八个Image_Import_By_Name数据结构。

  •  总括这么些新添的DLL名称字符串的OdysseyVA.

  •  把这几个EnclaveVA赋予新增添的IID的Name1字段。

  •  再找到四个DWO昂CoraD的上空,来存放在Image_Import_by_name的奇骏VA。那么些WranglerVA正是新增添DLL的IAT表。

  •  总结上边DWO奥德赛D空间的LX570VA,将其予以新扩充IID的FirstThunk字段。

  •  运营修改完的次第。

本文由pc28.am发布于pc28.am神测网,转载请注明出处:Win32病毒入门,PE结构分析

上一篇:VPS自定义安装Windows贰零零零,整理齐全 下一篇:没有了
猜你喜欢
热门排行
精彩图文
  • Win32病毒入门,PE结构分析
    Win32病毒入门,PE结构分析
    在 PE文件头的 IMAGE_OPTIONAL_HEADELAND 结构中的 DataDirectory(数据目录表)的第贰个成员便是指向输入表的。每一个被链接进来的 DLL文件都各自对应三个IMAGE_IMPOR
  • 抱有主线职责完结措施汇总,win10不能够删除文件
    抱有主线职责完结措施汇总,win10不能够删除文件
    右键点击职务管理器 周详深度剖析斯Parker2--知识点,源码,调优,JVM,图总计,项目  滑炒江湖安卓版v1.1 圆满深度深入分析斯Parker2--知识点,源码,调优
  • 配备指南
    配备指南
    1、打开cmd ,输入 F: // 切换到Apache安装路径,我的Apache安装目录在 F盘 Windows 下配置 Apache 支持 https,apachehttps 1、打开cmd ,输入 F: // 切换到Apache安装路
  • Windows10内置Linux子系统初体验,Unix工程师的Win1
    Windows10内置Linux子系统初体验,Unix工程师的Win1
    WSL Windows10预览版14316中早就提供bash组件,暗中同意关闭的,启用的法门是先选中“开头-设置-更新和张家界-针对开荒人士-开采职员格局”,然后按 Win X
  • 操作系统基本原理,操作系统中的P
    操作系统基本原理,操作系统中的P
    操作系统基本原理,   操作系统用于管理系统的硬件、软件和数据资源,控制程序的运行,是应用软件与硬件之间的接口,也是人机之间的接口。操作系统