PE格式第六講,導出表
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格式第六講,導出表