PE格式詳細講解9 匯出表

2021-07-05 03:49:04 字數 3688 閱讀 5561

有一些函式很多程式都會用到,為每乙個程式寫乙個相同的函式看起來似乎有點浪費空間。

因此windows就整出了動態鏈結庫的概念,將一些常用的函式封裝成動態鏈結庫,等到需要的時候通過直接載入動態鏈結庫。

將需要的函式整合到自身中,這樣就大大的節約了記憶體中資源的存放。

如圖:pe格式詳細講解

有乙個重要的概念需要記住:動態鏈結庫是被對映到其他應用程式的位址空間中執行的,它和應用程式可以看成是「一體」的。

動態鏈結庫可以使用應用程式的資源,它所擁有的資源也可以被應用程式使用,它的任何操作都是代表應用程式進行的。

當動態鏈結庫進行開啟檔案、分配記憶體和建立視窗等操作後,這些檔案、記憶體和視窗都是為應用程式所擁有的。

所以,動態鏈結庫用小甲魚的話說就是「寄生蟲」! )

那匯出表是幹啥用的呢?

匯出表就是記載著動態鏈結庫的一些匯出資訊。

通過匯出表,dll 檔案可以向系統提供匯出函式的名稱、序號和入口位址等資訊,比便windows 載入器通過這些資訊來完成動態連線的整個過程。

擴充套件名為.exe 的pe 檔案中一般不存在匯出表,而大部分的.dll 檔案中都包含匯出表。但注意,這並不是絕對的。

例如純粹用作資源的.dll 檔案就不需要匯出函式啦,另外有些特殊功能的.exe 檔案也會存在匯出函式。

所以,世事無絕對……好了,我們接下來就對匯出表的結構進行分析。

匯出表(export table)中的主要成分是乙個**,內含函式名稱、輸出序數等。

序數是指定dll 中某個函式的16位數字,在所指向的dll 檔案中是獨一無二的。

在此我們不提倡僅僅通過序數來索引函式的方法,這樣會給dll 檔案的維護帶來問題。

例如當dll 檔案一旦公升級或修改就可能導致呼叫改dll 的程式無法載入到需要的函式。

資料目錄表的第乙個成員指向匯出表,是乙個image_export_directory(以後簡稱ied)結構,ied 結構的定義如下:

source code ( by )

image_export_directory struct

characteristics dword ? ; 未使用,總是定義為0

timedatestamp dword ? ; 檔案生成時間

majorversion word ? ; 未使用,總是定義為0

minorversion word ? ; 未使用,總是定義為0

name

dword ? ; 模組的真實名稱

base dword ? ; 基數,加上序數就是函式位址陣列的索引值

numberoffunctions dword ? ; 匯出函式的總數

numberofnames dword ? ; 以名稱方式匯出的函式的總數

addressoffunctions dword ? ; 指向輸出函式位址的rva

addressofnames dword ? ; 指向輸出函式名字的rva

addressofnameordinals dword ? ; 指向輸出函式序號的rva

image_export_directory ends

這個結構中的一些欄位並沒有被使用,有意義的字段說明如下。

乙個rva 值,指向乙個定義了模組名稱的字串。如即使kernel32.dll 檔案被改名為」ker.dll」。

仍然可以從這個字串中的值得知其在編譯時的檔名是」kernel32.dll」。

檔案中包含的匯出函式的總數。

被定義函式名稱的匯出函式的總數,顯然只有這個數量的函式既可以用函式名方式匯出。

也可以用序號方式匯出,剩下的numberoffunctions 減去numberofnames 數量的函式只能用序號方式匯出。

該字段的值只會小於或者等於 numberoffunctions 欄位的值,如果這個值是0,表示所有的函式都是以序號方式匯出的。

乙個rva 值,指向包含全部匯出函式入口位址的雙字陣列。陣列中的每一項是乙個rva 值,陣列的項數等於numberoffunctions 欄位的值。

base:匯出函式序號的起始值,將addressoffunctions 字段指向的入口位址表的索引號加上這個起始值就是對應函式的匯出 序號。

假如base 欄位的值為x,那麼入口位址表指定的第1個匯出函式的序號就是x;第2個匯出函式的序號就是x+1。

總之,乙個匯出函式的匯出序號等 於base 欄位的值加上其在入口位址表中的位置索引值。

均為rva 值。前者指向函式名字串位址表。

這個位址表是乙個雙字陣列,陣列中的每一項指向乙個函式名稱字串的rva。

陣列的項數等於numberofnames 欄位的值,所有有名稱的匯出函式的名稱字串都定義在這個表中;後者指向另乙個word 型別的陣列(注意不是雙字陣列)。

陣列專案與檔名位址表中的專案一一對應,專案值代表函式入口位址表的索引,這樣函 數名稱與函式入口位址關聯起來。

(舉個例子說,加入函式名稱字串位址表的第n 項指向乙個字串「myfunction」。

那麼可以去查詢 addressofnameordinals 指向的陣列的第n 項,假如第n 項中存放的值是x,則表示addressoffunctions 字段描述的位址表中的第x 項函式入口位址對應的名稱就是「myfunction」複雜吧?

沒事,接著看你就懂了,別放棄哦~)

整個流程跟其他pe 結構一樣說起來複雜,但看圖說話倒是挺容易的。所以小甲魚還是本著實事求是的精神&……%¥#

下邊小甲魚帶大家來模擬一下windows 裝載器查詢匯出函式入口位址的整個過程。

如果已知函式的匯出序號,如何得到函式的入口位址呢 ?

定位到pe 檔案頭

從pe 檔案頭中的 image_optional_header32 結構中取出資料目錄表,並從第乙個資料目錄中得到匯出表的rva

從匯出表的 base 字段得到起始序號

將需要查詢的匯出序號減去起始序號,得到函式在入口位址表中的索引

檢測索引值是否大於匯出表的 numberoffunctions 欄位的值,如果大於後者的話,說明輸入的序號是無效的用這個索引值在 addressoffunctions 字段指向的匯出函式入口位址表中取出相應的專案,這就是函式入口位址的rva 值,當函式被裝入記憶體的時候,這個rva 值加上模組實際裝入的基位址,就得到了函式真正的入口位址

如果已知函式的名稱,如何得到函式的入口位址呢?與使用序號來獲取入口位址相比,這個過程要相對複雜一點!

最初的步驟是一樣的,那就是首先得到匯出表的位址

從匯出表的 numberofnames 字段得到已命名函式的總數,並以這個數字作為迴圈的次數來構造乙個迴圈

從 addressofnames 字段指向得到的函式名稱位址表的第一項開始,在迴圈中將每一項定義的函式名與要查詢的函式名相比較,如果沒有任何一

個函式名是符合的,表示檔案中沒有指定名稱的函式

如果某一項定義的函式名與要查詢的函式名符合,那麼記下這個函式名在字串位址表中的索引值,然後在 addressofnamesordinals 指向的陣列中以同樣的索引值取出陣列項的值,我們這裡假設這個值是x

最後,以 x 值作為索引值,在 addressoffunctions 字段指向的函式入口位址表中獲取的 rva 就是函式的入口位址

一幫情況下病毒程式就是通過函式名稱查詢入口位址的,因為病毒程式作為一段額外的**被附加到可執行檔案中的。

如果病毒**中用到某些 api 的話,這些 api 的位址不可能在宿主檔案的匯出表中為病毒**準備好。

因此只能通過在記憶體中動態查詢的方法來實現獲取api 的位址。

PE格式詳細講解8 輸入表

在此之前,我們已經對這個輸入表進行了一些實踐和理解,這有助於大家對這個概念更進一步的加深認識。小甲魚覺得,越是複雜的問題我們應該越是去動手操作它,認識它,這樣才容易熟悉它!在上一節課我們像小鹿一樣的亂撞,終於撞到了輸入表裡邊包含的函式名稱,嘿嘿,不過位址,我們還是沒能找著 這節課我們將深入來剖析輸入...

PE格式詳解講解1

下面的內容主要是圍繞這個圖來進行 這個頭部是為了相容早期的dos系統,pe檔案的第乙個位元組起始於乙個傳統的ms dos頭,被稱為image dos header,這個結構體完整的定義如下 注 最左邊是檔案頭的偏移量。image dos header struct image dos header ...

PE匯出表操作

bool enumeattable pvoid pmodulebase 下面的操作會使pdosheader e lfanew的值乘以64,再進行相加,由於pdosheader為指標型別,因此在進行相加時,會乘以結構體的大小 pntheader pimage nt headers pdosheader...