PE結構 匯出表

2021-10-02 23:22:50 字數 4409 閱讀 8070

那麼,作業系統是如何來獲取函式位址呢,也就是getprocaddress的實現,這裡就涉及到了匯出表。匯出表,會記錄這個庫函式的位址是多少,所以簡單來說getprocaddress就是查匯出表來獲取位址,如何查就是下面的話題了。

匯出意味著需要提供api給他人使用,一般來說會是一些dll之類的,所以,我們先來寫乙個dll,再來分析其pe格式

.386

.model flat, stdcall ;32 bit memory model

option casemap :none ;case sensitive

include windows.inc

include user32.inc

includelib user32.lib

public g_ntest

.data

g_ntest dd 87654321h

.code

showmsg proc sztext:lpstr,sztitle:lpstr

invoke messagebox,null,sztext,sztitle,mb_ok

retshowmsg endp

mysub proc x:uint,y:uint

mov eax,x

sub eax,y

mysub endp

myadd proc x:uint,y:uint

mov eax,x

add eax,y

myadd endp

dllmain proc hinstdll:hinstance,fdwreason:dword,lpvreserved:lpvoid

mov eax,true

retdllmain endp

end dllmain

def檔案描述

exports

showmsg @11

mysub @5 noname

myadd @7

g_ntest @8

這裡我們先用depends來觀察一下匯出情況

其中序號為5,7,8,9的就是我們自己匯出的,那麼剩餘的是什麼,下面就可以通過匯入表來解釋了

首先先來說一下如何定位到匯入表,匯入表位於資料目錄的第一項

2060這裡轉fa的話就是660,所以檔案位址從660開始,大小為77,這一整塊是匯入表資訊的總大小,這裡的總大小是包含所有資訊的。

ok,下面先來看一下匯入表結構,注意上面的0x77是全部資訊的大小,而單純的匯入表結構只佔40位元組,也就是兩行半

typedef struct _image_export_directory  image_export_directory, *pimage_export_directory;
前四個字段我們略過,因為可以填寫任意值,沒有太大意義,開始討論後面的字段意義

name欄位,說明性資訊,一般情況下會預設原始碼檔名

ok,下面幾個字段相對會有那麼點繞,總的來說,站在getprocaddress的角度去思考就會清爽很多

base 全部匯出函式中序號的最小值,首先來思考一下這個欄位的意義?

首先我們先來想一下根據序號來找到函式位址,那麼此時肯定會有一張函式位址表(addressoffunctions),如何查詢效率高?毫無疑問,直接將序號當成陣列的下標來定址,這樣找到函式位址的效率是最高的,

那麼問題來了,拿我們的匯出例子來說,最小的序號為5,陣列下標來定址意味著需要使得0~4項都是空的,所以base欄位的意義就來了,下標平移,節省空間。也就是說序號減去base就是陣列下標的位置,那麼序號5就會放在第0項,這樣子省去了一部分空間。

不過像我們中間出現的斷序的情況(序號5後面是序號7),這個就沒有辦法了,所以在上面depends圖中0x6,0x9,0xa這幾項都是空的,只是用於佔位置罷了,畢竟陣列是需要連續的。

numberoffunctions用於記錄總共有多少項匯出函式位址

根據上圖中該結果是0x7,懂了上面的base設計原理後,應該能明白為什麼只有四項匯出函式,這裡卻顯示7項。因為還有好幾項只是用於陣列中佔位置的,空間換時間的想法。

numberofnames用於記錄有多個是是根據名字匯出的

好了,上面兩個欄位都是根據序號這一方面的考慮的,那麼使用函式名匯出該這麼辦呢,我們可以這樣子來設計一下

typedef struct _image_export_by_name
看一下上面的結構體,其中乙個欄位是名字位址,根據其可以找到函式名,匹配函式名成功後可那下面的index下標欄位去函式位址表中找位址。

當然windows並不是這樣設計的,windows把上面的兩項分別拆成了兩張表:名字匯出表(addressofnames),名字對應下標表(addressofnameordinals),這兩張表的同下標是相互關聯的,也相當於上面的結構體。

所以這個字段就是用於描述這兩項有多少個的。

下面我們具體在winhex中來看一下數值,可對照著上表的資料看,上表是乙個概括

首先看addressoffunctions,其陣列的大小為numberoffunctions

va2088對應fa為688,下面在688位置查詢7項

再來看一下addressofnames,其陣列大小為numberofnames,三項

這裡因為是名字表,所以其對應是個名字的va值,要找到名字字串,我們還需要根據其值再找一次

再來看最後一張表了,這張表的大小也為numberofnames,主要用於輔助名稱查位址,比如你知道了名稱,那麼這麼知道這個名稱在位址表的第幾項呢,所以這張表的目的就是用於對映該名稱在位址表中的下標值。需要注意的是因為序號最大2位元組,所以該錶的每項長度也是2位元組的

好了,分析完後可以再對照著上面的表看看,下面再來總結作業系統如何實現getprocaddress

1.序號

定位到資料目錄->匯入表

序號-base = 下標(index)

越界檢查,取函式總個數,檢查下標有沒有超過個數

取函式位址表,取index項 -> rva值 + 引數一例項控制代碼 = 函式位址

2.名稱

定位到資料目錄->匯入表

根據名稱匯出個數遍歷函式名稱陣列查詢 - 匯出函式多

作業系統會折半查詢,所以鏈結器會按照ascii碼順序排放

查詢不到則返回null

查詢到下標-index,找函式名稱和序號對應關係表

同在該表中取index項,取到內容->index2

越界檢查,取函式總個數,檢查index2有沒有超過個數

取函式位址表,取index2項 -> rva值 + 引數一例項控制代碼 = 函式位址

下面是具體的**實現,當然這裡的**只是簡易版的,作業系統做的事情還有挺多的,這裡只是給了乙個主體概要

void* mygetprocaddress(hmodule hmodule, lpcstr lpprocname)

if(funname[funindex] || lpprocname[procindex])

continue;

word ordinal = pordinaltable[i];

lpprocname = (lpcstr)ordinal;

break;}}

if (!((dword)lpprocname & 0xffff0000))

return null;

}

PE檔案結構詳解(三)PE匯出表

上篇文章 pe檔案結構詳解 二 可執行檔案頭 的結尾出現了乙個大陣列,這個陣列中的每一項都是乙個特定的結構,通過函式獲取陣列中的項可以用rtlimagedirectoryentrytodata函式,datadirectory中的每一項都可以用這個函式獲取,函式原型如下 base 模組基位址。dire...

PE檔案結構詳解(三)PE匯出表

上篇文章 pe檔案結構詳解 二 可執行檔案頭 的結尾出現了乙個大陣列,這個陣列中的每一項都是乙個特定的結構,通過函式獲取陣列中的項可以用rtlimagedirectoryentrytodata函式,datadirectory中的每一項都可以用這個函式獲取,函式原型如下 base 模組基位址。dire...

PE檔案結構詳解(三)PE匯出表

上篇文章 pe檔案結構詳解 二 可執行檔案頭 的結尾出現了乙個大陣列,這個陣列中的每一項都是乙個特定的結構,通過函式獲取陣列中的項可以用rtlimagedirectoryentrytodata函式,datadirectory中的每一項都可以用這個函式獲取,函式原型如下 base 模組基位址。dire...