?? lion-petut-c07.htm
字號:
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=gb_2312-80">
<meta name="GENERATOR" content="Microsoft FrontPage Express 2.0">
<title>Iczelion的PE教程7: Export Table(引出表)</title>
</head>
<body bgcolor="#000066" text="#FFFFFF" link="#FFFFCC"
vlink="#FFCCCC" alink="#CCFFCC">
<h1 align="center"><font color="#FFFFCC">PE教程7: Export Table(引出表)</font></h1>
<p><font size="2" face="MS Sans Serif">上一課我們已經(jīng)學(xué)習(xí)了動態(tài)聯(lián)接中關(guān)于引入表那部分知識,現(xiàn)在繼續(xù)另外一部分,那就是引出表。</font></p>
<p><font size="2">下載 </font><a href="files/pe-tut07.zip"
style="text-decoration:none"><font size="2"><b>范例</b></font></a><font
size="2">。</font></p>
<h3>理論<font face="Arial, Helvetica, sans-serif">:</font></h3>
<p><font size="2" face="MS Sans Serif">當(dāng)PE裝載器執(zhí)行一個程序,它將相關(guān)DLLs都裝入該進程的地址空間。然后根據(jù)主程序的引入函數(shù)信息,查找相關(guān)DLLs中的真實函數(shù)地址來修正主程序。PE裝載器搜尋的是DLLs中的引出函數(shù)。</font></p>
<p><font size="2" face="MS Sans Serif">DLL/EXE要引出一個函數(shù)給其他DLL/EXE使用,有兩種實現(xiàn)方法: 通過函數(shù)名引出或者僅僅通過序數(shù)引出。比如某個DLL要引出名為"GetSysConfig"的函數(shù),如果它以函數(shù)名引出,那么其他DLLs/EXEs若要調(diào)用這個函數(shù),必須通過函數(shù)名,就是GetSysConfig。另外一個辦法就是通過序數(shù)引出。什么是序數(shù)呢?
序數(shù)是唯一指定DLL中某個函數(shù)的16位數(shù)字,在所指向的DLL里是獨一無二的。例如在上例中,DLL可以選擇通過序數(shù)引出,假設(shè)是16,那么其他DLLs/EXEs若要調(diào)用這個函數(shù)必須以該值作為GetProcAddress調(diào)用參數(shù)。這就是所謂的僅僅靠序數(shù)引出。</font></p>
<p><font size="2" face="MS Sans Serif">我們不提倡僅僅通過序數(shù)引出函數(shù)這種方法,這會帶來DLL維護上的問題。一旦DLL升級/修改,程序員無法改變函數(shù)的序數(shù),否則調(diào)用該DLL的其他程序都將無法工作。</font></p>
<p><font size="2" face="MS Sans Serif">現(xiàn)在我們開始學(xué)習(xí)引出結(jié)構(gòu)。象引出表一樣,可以通過數(shù)據(jù)目錄找到引出表的位置。這兒,引出表是數(shù)據(jù)目錄的第一個成員,又可稱為IMAGE_EXPORT_DIRECTORY。該結(jié)構(gòu)中共有11
個成員,常用的列于下表。</font></p>
<table border="1" cellpadding="2">
<tr>
<th bgcolor="#006666"><font size="2" face="MS Sans Serif"><b>Field
Name</b></font></th>
<th bgcolor="#006666"><font size="2" face="MS Sans Serif">Meaning</font></th>
</tr>
<tr>
<td align="center" bgcolor="#006666"><font size="2"
face="MS Sans Serif"><b>nName</b></font></td>
<td align="center" bgcolor="#006666"><font size="2"
face="MS Sans Serif">模塊的真實名稱。本域是必須的,因為文件名可能會改變。這種情況下,PE裝載器將使用這個內(nèi)部名字。</font></td>
</tr>
<tr>
<td align="center" bgcolor="#006666"><font size="2"
face="MS Sans Serif"><b>nBase</b></font></td>
<td align="center" bgcolor="#006666"><font size="2"
face="MS Sans Serif">基數(shù),加上序數(shù)就是函數(shù)地址數(shù)組的索引值了。</font></td>
</tr>
<tr>
<td align="center" bgcolor="#006666"><font size="2"
face="MS Sans Serif"><b>NumberOfFunctions</b></font></td>
<td align="center" bgcolor="#006666"><font size="2"
face="MS Sans Serif">模塊引出的函數(shù)/符號總數(shù)。</font></td>
</tr>
<tr>
<td align="center" bgcolor="#006666"><font size="2"
face="MS Sans Serif"><b>NumberOfNames</b></font></td>
<td align="center" bgcolor="#006666"><font size="2"
face="MS Sans Serif">通過名字引出的函數(shù)/符號數(shù)目。該值</font><font
color="#CC9900" size="2" face="MS Sans Serif"><b>不是</b></font><font size="2"
face="MS Sans Serif">模塊引出的函數(shù)/符號總數(shù)</font>,這是由上面的<font color="#FFFFCC" size="2"
face="MS Sans Serif"><b>NumberOfFunctions</b></font><font
size="2" face="MS Sans Serif">給出。本域可以為0,表示模塊可能僅僅通過序數(shù)引出。如果模塊根本不引出任何函數(shù)/符號,那么數(shù)據(jù)目錄中引出表的RVA為0。</font></td>
</tr>
<tr>
<td align="center" bgcolor="#006666"><font size="2"
face="MS Sans Serif"><b>AddressOfFunctions</b></font></td>
<td align="center" bgcolor="#006666"><font size="2"
face="MS Sans Serif">模塊中有一個指向所有函數(shù)/符號的RVAs數(shù)組,本域就是指向該RVAs數(shù)組的RVA。簡言之,模塊中所有函數(shù)的RVAs都保存在一個數(shù)組里,本域就指向這個數(shù)組的首地址。</font></td>
</tr>
<tr>
<td align="center" bgcolor="#006666"><font size="2"
face="MS Sans Serif"><b>AddressOfNames</b></font></td>
<td align="center" bgcolor="#006666"><font size="2"
face="MS Sans Serif">類似上個域,模塊中有一個指向所有函數(shù)名的RVAs數(shù)組,本域就是指向該RVAs數(shù)組的RVA。</font></td>
</tr>
<tr>
<td align="center" bgcolor="#006666"><font size="2"
face="MS Sans Serif"><b>AddressOfNameOrdinals</b></font></td>
<td align="center" bgcolor="#006666"><font size="2"
face="MS Sans Serif">RVA,指向包含上述 AddressOfNames數(shù)組中相關(guān)函數(shù)之序數(shù)的16位數(shù)組。</font></td>
</tr>
</table>
<p><font size="2" face="MS Sans Serif">上面也許無法讓您完全理解引出表,下面的簡述將助您一臂之力。</font></p>
<p><font size="2" face="MS Sans Serif">引出表的設(shè)計是為了方便PE裝載器工作。首先,模塊必須保存所有引出函數(shù)的地址以供PE裝載器查詢。模塊將這些信息保存在</font><font color="#FFFFCC" size="2"
face="MS Sans Serif"><b>AddressOfFunctions</b></font><font
size="2" face="MS Sans Serif">域指向的數(shù)組中,而數(shù)組元素數(shù)目存放在</font><font color="#FFFFCC" size="2"
face="MS Sans Serif"><b>NumberOfFunctions</b></font><font
size="2" face="MS Sans Serif">域中。 因此,如果模塊引出40個函數(shù),則</font><font
color="#FFFFCC" size="2" face="MS Sans Serif"><b>AddressOfFunctions</b></font><font size="2" face="MS Sans Serif">指向的數(shù)組必定有40個元素,而</font><font
color="#FFFFCC" size="2" face="MS Sans Serif"><b>NumberOfFunctions</b></font><font
size="2" face="MS Sans Serif">值為40。現(xiàn)在如果有一些函數(shù)是通過名字引出的,那么模塊必定也在文件中保留了這些信息。這些 名字的RVAs存放在一數(shù)組中以供PE裝載器查詢。該數(shù)組由</font><font
color="#FFFFCC" size="2" face="MS Sans Serif"><b>AddressOfNames</b></font><font
size="2" face="MS Sans Serif">指向,</font><font
color="#FFFFCC" size="2" face="MS Sans Serif"><b>NumberOfNames</b></font><font
size="2" face="MS Sans Serif">包含名字數(shù)目。考慮一下PE裝載器的工作機制,它知道函數(shù)名,并想以此獲取這些函數(shù)的地址。至今為止,模塊已有兩個模塊:
名字數(shù)組和地址數(shù)組,但兩者之間還沒有聯(lián)系的紐帶。因此我們還需要一些聯(lián)系函數(shù)名及其地址的東東。PE參考指出使用到地址數(shù)組的索引作為聯(lián)接,因此PE裝載器在名字數(shù)組中找到匹配名字的同時,它也獲取了</font><font color="#999900" size="2"
face="MS Sans Serif"><b> 指向地址表中對應(yīng)元素的索引</b></font><font
size="2" face="MS Sans Serif">。 而這些索引保存在由</font><font
color="#FFFFCC" size="2" face="MS Sans Serif"><b>AddressOfNameOrdinals</b></font><font
size="2" face="MS Sans Serif">域指向的另一個數(shù)組(最后一個)中。由于該數(shù)組是起了聯(lián)系名字和地址的作用,所以其元素數(shù)目必定和名字數(shù)組相同,比如,每個名字有且僅有一個相關(guān)地址,反過來則不一定</font><font
size="2" face="MS Sans Serif">: 每個地址可以有好幾個名字來對應(yīng)。因此我們給同一個地址取"別名"。為了起到連接作用,名字數(shù)組和索引數(shù)組必須并行地成對使用,譬如,索引數(shù)組的第一個元素必定含有第一個名字的索引,以此類推。
</font></p>
<table border="0" cellpadding="2">
<tr>
<th bgcolor="#006666"><font size="2" face="MS Sans Serif">AddressOfNames</font></th>
<th> </th>
<th bgcolor="#006666"><font size="2" face="MS Sans Serif">AddressOfNameOrdinals</font></th>
</tr>
<tr>
<td align="center"><font size="2" face="MS Sans Serif">|</font>
</td>
<td align="center"> </td>
<td align="center"><font size="2" face="MS Sans Serif">|</font>
</td>
</tr>
<tr>
<td align="center"><table border="1" cellpadding="2">
<tr>
<td align="center" bgcolor="#660066"><font
size="2" face="MS Sans Serif">RVA of Name 1</font></td>
</tr>
<tr>
<td align="center" bgcolor="#660066"><font
size="2" face="MS Sans Serif">RVA of Name 2</font></td>
</tr>
<tr>
<td align="center" bgcolor="#660066"><font
size="2" face="MS Sans Serif">RVA of Name 3</font></td>
</tr>
<tr>
<td align="center" bgcolor="#660066"><font
size="2" face="MS Sans Serif">RVA of Name 4</font></td>
</tr>
<tr>
<td align="center" bgcolor="#660066"><font
size="2" face="MS Sans Serif">...</font> </td>
</tr>
<tr>
<td align="center" bgcolor="#660066"><font
size="2" face="MS Sans Serif">RVA of Name N</font></td>
</tr>
</table>
</td>
<td align="center"><table border="0" cellpadding="2">
<tr>
<td align="center"><font size="2"
face="MS Sans Serif"><--></font></td>
</tr>
<tr>
<td align="center"><font size="2"
face="MS Sans Serif"><--></font></td>
</tr>
<tr>
<td align="center"><font size="2"
face="MS Sans Serif"><--></font></td>
</tr>
<tr>
<td align="center"><font size="2"
face="MS Sans Serif"><--></font></td>
</tr>
<tr>
<td align="center"><font size="2"
face="MS Sans Serif">...</font> </td>
</tr>
<tr>
<td align="center"><font size="2"
face="MS Sans Serif"><--></font></td>
</tr>
</table>
</td>
<td align="center"><table border="1" cellpadding="2">
<tr>
<td align="center" bgcolor="#003300"><font
size="2" face="MS Sans Serif">Index of Name 1</font></td>
</tr>
<tr>
<td align="center" bgcolor="#003300"><font
size="2" face="MS Sans Serif">Index of Name 2</font></td>
</tr>
<tr>
<td align="center" bgcolor="#003300"><font
size="2" face="MS Sans Serif">Index of Name 3</font></td>
</tr>
<tr>
<td align="center" bgcolor="#003300"><font
size="2" face="MS Sans Serif">Index of Name 4</font></td>
</tr>
<tr>
<td align="center" bgcolor="#003300"><font
size="2" face="MS Sans Serif">...</font> </td>
</tr>
<tr>
<td align="center" bgcolor="#003300"><font
size="2" face="MS Sans Serif">Index of Name N</font></td>
</tr>
</table>
</td>
</tr>
</table>
<p><font size="2" face="MS Sans Serif">下面舉一兩個例子說明問題。如果我們有了引出函數(shù)名并想以此獲取地址,可以這么做:</font></p>
<ol>
<li><font size="2" face="MS Sans Serif">定位到PE header。</font></li>
<li><font size="2" face="MS Sans Serif">從數(shù)據(jù)目錄讀取引出表的虛擬地址。</font></li>
<li><font size="2" face="MS Sans Serif">定位引出表獲取名字數(shù)目(</font><font
color="#FFFFCC" size="2" face="MS Sans Serif"><b>NumberOfNames</b></font><font
size="2" face="MS Sans Serif">)。</font></li>
<li><font size="2" face="MS Sans Serif">并行遍歷</font><font color="#FFFFCC" size="2"
face="MS Sans Serif"><b>AddressOfNames</b></font><font
size="2" face="MS Sans Serif">和</font><font
color="#FFFFCC" size="2" face="MS Sans Serif"><b>AddressOfNameOrdinals</b></font><font size="2" face="MS Sans Serif">指向的數(shù)組匹配名字。如果在</font><font
color="#FFFFCC" size="2" face="MS Sans Serif"><b>AddressOfNames</b></font><font
size="2" face="MS Sans Serif"> 指向的數(shù)組中找到匹配名字,從</font><font
color="#FFFFCC" size="2" face="MS Sans Serif"><b>AddressOfNameOrdinals</b></font><font
size="2" face="MS Sans Serif"> 指向的數(shù)組中提取索引值。例如,若發(fā)現(xiàn)匹配名字的RVA存放在</font><font
color="#FFFFCC" size="2" face="MS Sans Serif"><b>AddressOfNames</b></font><font
size="2" face="MS Sans Serif"> 數(shù)組的第77個元素,那就提取</font><font
color="#FFFFCC" size="2" face="MS Sans Serif"><b>AddressOfNameOrdinals</b></font><font
size="2" face="MS Sans Serif">數(shù)組的第77個元素</font><font
size="2" face="MS Sans Serif">作為索引值。如果遍歷完</font><font color="#FFFFCC" size="2"
face="MS Sans Serif"><b>NumberOfNames</b></font><font
size="2" face="MS Sans Serif"> 個元素,說明當(dāng)前模塊沒有所要的名字。</font></li>
<li><font size="2" face="MS Sans Serif">從</font><font color="#FFFFCC" size="2"
face="MS Sans Serif"><b>AddressOfNameOrdinals</b></font><font
size="2" face="MS Sans Serif"> 數(shù)組提取的數(shù)值作為</font><font color="#FFFFCC" size="2"
face="MS Sans Serif"><b>AddressOfFunctions</b></font><font
size="2" face="MS Sans Serif"> 數(shù)組的索引。也就是說,如果值是5,就必須讀取</font><font color="#FFFFCC" size="2"
face="MS Sans Serif"><b>AddressOfFunctions</b></font><font
size="2" face="MS Sans Serif"> 數(shù)組的第5個元素,此值就是所要函數(shù)的RVA。</font></li>
</ol>
<p><font size="2" face="MS Sans Serif">現(xiàn)在我們在把注意力轉(zhuǎn)向</font><font color="#FFFFCC"
size="2" face="MS Sans Serif"><b>IMAGE_EXPORT_DIRECTORY</b></font><font
size="2" face="MS Sans Serif"> 結(jié)構(gòu)的</font><font color="#FFFFCC" size="2"
face="MS Sans Serif"><b>nBase</b>成員。您已經(jīng)知道</font><font color="#FFFFCC" size="2" face="MS Sans Serif"><b>AddressOfFunctions</b></font><font
size="2" face="MS Sans Serif"> 數(shù)組包含了模塊中所有引出符號的地址。當(dāng)PE裝載器索引該數(shù)組查詢函數(shù)地址時,讓我們設(shè)想這樣一種情況,如果程序員在.def文件中設(shè)定起始序數(shù)號為200,這意味著</font><font
color="#FFFFCC" size="2" face="MS Sans Serif"><b>AddressOfFunctions</b></font><font
size="2" face="MS Sans Serif"> 數(shù)組至少有200個元素,甚至這前面200個元素并沒使用,但它們必須存在,因為PE裝載器這樣才能索引到正確的地址。這種方法很不好,所以又設(shè)計了</font><font color="#FFFFCC" size="2"
face="MS Sans Serif"><b>nBase</b></font><font size="2"
face="MS Sans Serif"> 域解決這個問題。如果程序員指定起始序數(shù)號為200,</font><font
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -