1. 程式人生 > >PE格式第六講,導出表

PE格式第六講,導出表

dll cti 很慢 type 方式 博客 會有 應該 顏色

                PE格式第六講,導出表

請註意,下方字數比較多,其實結構挺簡單,但是你如果把博客內容弄明白了,對你受益匪淺,千萬不要看到字數多就懵了,其實字數多代表它重要.特別是第五步,

各種表中之間的關系.

作者:IBinary
出處:http://www.cnblogs.com/iBinary/
版權所有,歡迎保留原文鏈接進行轉載:)

一丶淺談導入表

首先,導出表我們已經學過了,作用就是在程序加載的時候,把自己要調用的API的地址,不斷地填寫到IAT表中

不過我們要知道三個概念,

1.程序運行的時候,導入表直接把調用的API地址填寫到IAT表格中

2.程序運行的時候,用到那個API,才會填寫到IAT表中(延時加載技術,下面講解)

3.程序還沒運行的時候,在PE中函數的地址就已經寫死了.

何為延時加載

我們知道,如果你寫的API有幾萬個,那麽一開始就填寫到IAT表格中,那麽你的程序會很慢.

那麽就會使用延時加載表(也是數據目錄中的)那麽用到那個,才填寫.

技術分享

可以查看結構體(數據目錄的結構體)下面可以找到這個表格.這個就是延時加載表

二丶何為導出表?

導出表,作用就是我們寫的DLL或者EXE導出的函數,那麽會記載這些函數.作用就是這個

那麽是你自己設計導出表,你要怎麽設計.

按照我的思路:

1.我會設計一個 入口地址表(RVA存貯,用來計算模塊 + 入口地址的RVA等於函數地址)

2.我還會設計一個 保存函數名字的表

3.我還會設計一個,保存序號的表

大體就怎麽多.

因為你想,我們如果要保存一個導出函數,並且還有調用它,是不是需要保存函數名字,但是因為導出的時候還要有序號導出,那麽是不是要保存序號,不光保存序號,我們是不是還要通過一定手段讓模塊地址+偏移尋到函數位置.

表格設計大體流程

EntryPoint 裏面保存RVA(入口地址表)

order   序號表格保存序號的

FunctionName 函數名稱表

當然,函數導出的時候可能還會有記錄個數的

那麽看下具體的結構體是怎麽樣子的吧.

不出意外就會和我們的表設計的差不多.

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;     //標誌,未用
    DWORD   TimeDateStamp;      //時間
    WORD    MajorVersion;        //主版本
    WORD    MinorVersion;        //副版本
    DWORD   Name;             //指向導出表文件名的字符串
    DWORD   Base;            //導出函數的起始序號
    DWORD   NumberOfFunctions;    //所有導出函數的個數
    DWORD   NumberOfNames;      //以函數名導出的函數個數
    DWORD   AddressOfFunctions;     
// 導出函數地址表RVA(入口地址) DWORD AddressOfNames; // 函數名稱地址表RVA(名稱表) DWORD AddressOfNameOrdinals; // 函數序號地址表(序號表) } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

可以看出,確實是差不多的.

那麽看下這裏的重要成員

1.執行導出表文件名的字符串

2.base 導出函數的起始序號

3.導出函數地址表RVA

4.函數名稱地址表RVA

5.函數序號地址表

這裏我們隨便找個DLL,使用010模版,看下結構到底怎麽存儲的(先要定位)

三丶定位導出表

1.找出導出表RVA偏移

首先,我們要在數據目錄裏面查看DLL的第一項,也就是導出表的地址的RVA偏移是多少.

技術分享

可以看出,是4820的RVA偏移,大小是84個字節

2.判斷屬於哪一個節

既然知道是4820了,那麽節中的虛擬地址肯定是40開頭的.小於50的 因為4820 > 40開頭, < 50開頭

首先第一個節.text

技術分享

4096代表了1000h,肯定不是,相差太遠.

第二個節.radata

技術分享

是40開頭的,那麽就是了

3.算出FOA位置

既然知道是.radata節中的,那麽直接算出FOA(簡稱FA)的位置即可

RVA = 4820

FA = RVA - 節虛擬地址(上面找的4000h) + 上面找的節的文件偏移大小

文件偏移截圖:

技術分享

公式代入得到:

FA = 4820 - 4000 + 4000

FA = 4820

通過計算,我們發現還是4820的位置,為什麽這麽巧?

原因是虛擬地址和文件偏移位置存儲都是一樣的.所以記錄的RVA則是在文件中的位置.

4.通過FA找到導出表位置

我們得到FA是4820,那麽我們使用010 Editer中的ctrl + G的功能,快速定位位置.

技術分享

技術分享

我們選中了84個字節,那麽這84個字節則是導出表的大小了,在數據目錄中有記錄導出表的大小,所以直接定位即可.

四丶導出表的存儲方式

1.第一部分講解

技術分享

截圖一下截取太多,不好講解,這裏截取一部分,下面接著截取4840下面的

首先,黑色方塊中的無用不重要,所以不講解.

紅色方塊第一個: 成員NAME 這個是一個RVA的偏移,指向了DLL的名稱,可以的同學,可以看下4860(因為RVA是4860,但是虛擬地址和文件偏移是一樣的,所以現在的情況是FA = RVA,不用轉換了)的位置,是不是DLL的名稱,以0結尾

紅色方塊第二個,base成員,起始的導出序號,這個很重要,一會講解

紅色方塊第三個,這個我直接把兩個4字節弄在一起來,要分開來看, 第一個四個字節,3,表示了所有導出函數是3個(函數名字,或者序號導出都計算)

第二個四個字節: 顯示2,表示了按照名字導出的函數有兩個.

紅色方塊第四個: 這個重要了.存放的是函數的地址表.

如果會看FA位置的同學請看. 存放的00001000 第一個函數地址偏移 第二個函數地址偏移 00001020 第三個函數地址偏移00001040

為什麽存放的是函數地址偏移,因為這個是個DLL,加載到程序的時候,DLL模塊不固定,所以比如存放偏移

這樣就可以通過 ImageBase + 偏移,定位導出函數的地址了.

所以現在大家應該知道為什麽GetprocAddress(模塊,函數名)為什麽要給模塊了嗎? 就是要通過模塊+偏移的位置定位導出函數地址調用

2.第二部分講解

首先,講解第二部分之前,我們要把第一部分的存儲信息截圖下來,方便對照查看.

技術分享

第二部分截圖:

技術分享

可以看出,我畫了4個顏色的方框

看法: 黃色對應著橘黃色

深紅色,對應著綠色

首先黃色方塊: 代表的函數名稱的表,指向函數名稱的字符串位置 RVA,通過轉化(FA = RVA)得出4854的位置是指向字符串的位置.

那麽就會指導橘黃色的開頭位置了,也就是4854的位置,但是註意一下,因為導出的函數,函數名字是不固定的,那麽其實函數名稱表執行的這個區域還是一個偏移.

而這個偏移,才真正的指向了函數名稱的字符串(註意,0結尾)可邊長的.

那麽486A的位置就是 導出函數的名稱了. 也就是fun1

橘黃色有兩個4字節,第一個RVA偏移,是指向了fun1的函數名字,那麽第二個就是指向了fun2的名字,那麽由此可以得出,按照名字導出,我們總共得到了兩個字符串.(fun1,和fun2)

深紅色位置:

深紅色位置指向了一個偏移,這個偏移也就是綠色方框的開頭,分為2個字節2個字節

那麽綠色是2個2個字節.

這個深紅色位置,就是序號表的RVA偏移,我們知道DLL導出可以是序號導出,那麽這個地方就是存儲的是序號.

導出函數的時候,默認不寫序號,則對應的是從0開始,0 1 2 3順序排列,這裏導出了2個函數,那麽對應就有兩個序號,分別是0和1

通過上面講解,基本了解了導出表的存儲格式,但是下面的講解,才會真正的重要.

五丶BASE成員,導出序號,函數導出,以及函數地址表之間的關系

1.函數名稱,序號表,以及和函數地址表中的關系

首先我們要知道,DLL的導出函數可以按照序號導出,也可以按照函數名字導出,但是怎麽和函數地址表關聯起來那?

首先,我們先看不按照序號導出,按照函數名字來獲取函數的地址(函數地址表中)

上面我們分析過了

我們會需要函數名字,入口地址,以及序號,而下面的結構體也正好應驗了.

那麽重新寫一下

EntryPoint (可以理解為函數地址表,存放的是導出函數的地址)

那麽剛才我們看了,有三個

00001000 fun1的偏移

00001020 fun2的偏移

00001040 fun3的偏移,只不過沒有名字

Order 序號表格

0      fun1的默認序號

1      fun2的默認序號

FunctionName (函數名稱表格)

fun1

fun2

首先關系是這樣的,我們通過fun1的字符串,去查找序號表,通過序號表則找到偏移了.

比如我們要加載 fun1,那麽fun1的序號表中默認是0位置,所以找到的序號0了,也就是第一項(註意,序號表可以理解為Switch(序號))然後0位置存放的就是函數地址表,也就是000010000

那麽就得出了fun1的RVA偏移了

那麽現在知道一個完整的GetprocAddress(模塊,函數名)是怎麽運行的了吧

首先,模塊肯定是要知道的,

函數名字給了,那麽去序號表中看是第幾項,找到序號表中的對應第幾項,那麽就去函數地址表中尋得第幾項,那麽正好就得出偏移了

那麽現在模塊 + 偏移,正好找出導出函數 fun1的地址了.

2.Base,序號表之間的關系

首先我們重新編譯下DLL,這次是按照序號導出

技術分享

可以看出,我們默認指定的fun1的序號是1,fun2是2,fun3是3

那麽為什麽序號表中是0 1 2 那?

原因是這樣的

1.如果我們通過函數名字查找,比如找fun1,那麽序號就是0,也就是第一項,那麽通過0就可以找到fun1的偏移了

2.如果我們通過序號查找,比如我們輸入3,我們要調用fun3了,那麽這個時候,序號表中根本就沒有3,怎麽辦?

此時Base成員的作用就來了,它默認是1,那麽我們輸入3序號的時候,會減去base的值,得出的下標,再去序號表中查找.

那麽3 - 1 = 2,2當做下標去序號表中查找,找到了第三項,也就是02了,通過02找fun3的偏移,也是對的.

所以現在是由兩種方式,

第一種就是名字導入,fun1默認不加序號就是從0開始的,去序號表中查0就能找到偏移.

第二種就是專門按照序號導入的,那麽此時你輸入的序號-base才真正的是函數的偏移了.

作者:IBinary
出處:http://www.cnblogs.com/iBinary/
版權所有,歡迎保留原文鏈接進行轉載:)

PE格式第六講,導出表