再讀PE(2)

先看一下輸入表結構:

typedef struct _IMAGE_IMPORT_DESCRIPTOR

{

union

{

 DWORD   Characteristics; //這個union子結構只是給OriginalFirstThunk添了個別名

 DWORDOriginalFirstThunk;

};

DWORD   TimeDateStamp;  

DWORD   ForwarderChain;

DWORD   Name;  //含有指向DLL名字的RVA,即指向DLL名字的指針

DWORD   FirstThunk;  

} IMAGE_IMPORT_DESCRIPTOR;

輸入表裏邊有兩個結構很重要已做標記、、

OriginalFirstThunk是一個RVA這個RVA指向一個數組IMAGE_THUNK_DATA

這個數組是這樣的一個DWORD類型的集合聯合體、、

typedef struct _IMAGE_THUNK_DATA32 {

   union {

       PBYTE  ForwarderString;

       PDWORD Function;

       DWORD Ordinal;

       PIMAGE_IMPORT_BY_NAME  AddressOfData;

   } u1;

} IMAGE_THUNK_DATA32;

一般解釋爲是一個RVA指向這個結構IMAGE_IMPORT_BY_NAME注意不是數組只是一個結構他真正包含 引入函數的相關信息

typedef struct _IMAGE_IMPORT_BY_NAME {

   WORD    Hint;本函數在其DLL的引出表中的索引號、一些連接器將此值設爲0。

   BYTE    Name[1];   有引入函數的函數名

} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

這個數組IMAGE_THUNK_DATA結束的標誌就是全0、、

也就是說引入多少函數,就有多少IMAGE_THUNK_DATA結構、、引入多少DLL就有多少引入表結構、、

現在說FirstThunk、、、

FirstThunk與OriginalFirstThunk相似,它也包含指向一個IMAGE_THUNK_DATA結構數組的RVA(當然這是另外一個IMAGE_THUNK_DATA數組)。

爲什麼會有兩個一模一樣的數組?是這樣的、、

當PE文件被裝載到內存時,PE裝載器將查找IMAGE_THUNK_DATA

和IMAGE_IMPORT_BY_NAME這些結構數組,以此決定引入函數的地址。然後用引入函數真實地址來替代由FirstThunk指向的IMAGE_THUNK_DATA數組裏的元素值。

由OriginalFirstThunk指向的IMAGE_THUNK_DATA數組始終不會改變,所以若還反過頭來查找引入函數名,PE裝載器還能找到、、

上邊說的是如果函數是由函數名引入的情況 、、

有些情況一些函數僅由序數引出,也就是說不能用函數名來調用它們、只能用它們的位置來調用。此時,調用者模塊中就不存在該函數的IMAGE_IMPORT_BY_NAME結構。

這種情況下、這麼解釋分析、、

這種函數的IMAGE_THUNK_DATA值的低位字指示函數序數而最高二進位(MSB)爲1。例 如果一個函數只由序數引出且其序數是1234h

那麼對應該函數的IMAGE_THUNK_DATA值是80001234h。

Microsoft提供了一個常量來測試dword值的MSB位,就是IMAGE_ORDINAL_FLAG32,其值爲80000000h。

也就是說當我們要循環遍歷導入函數的時候先判斷一下  IMAGE_THUNK_DATA的最高二進制位是否爲1  再做定奪、、、

好了基本路線有了部分代碼如下:

while (NULL != IMPORT->OriginalFirstThunk)

{   // OriginalFirstThunk爲空代表引入表結束一般可以這樣作爲判斷條件

  //有的時候他就是爲空但輸入表也是有的、、

//若OriginalFirstThunk爲0,就改用FirstThunk值不過這樣也得不到函數的名字只//是對應的RVA這裏也用FisrtThunk去得到引入函數的RVA、、

LPDWORDpOriginalFirstThunk= (LPDWORD)(IMPORT->OriginalFirstThunk - SECRVA +SECVA +pFileAddr);  

//將OriginalFirstThunk代表的RVA  轉換爲RAW

//此時pOriginalFirstThunk就指向IMAGE_THUNK_DATA結構它加加就是

//IMAGE_THUNK_DATA數組的下一個元素   四個字節嘛、、記住這裏的用法、、

LPDWORD FirstThunk =(LPDWORD)(IMPORT->FirstThunk )

//這是每個存放API的RVA下邊則是存放API的  RAW

//LPDWORD RAW=(LPDWORD)(IMPORT->FirstThunk- SECRVA +SECVA +pFileAddr )

    while(*pOriginalFirstThunk)

{

    if (*pOriginalFirstThunk & IMAGE_ORDINAL_FLAG32)

{

    DWORD Hint = (*pOriginalFirstThunk)&0x7fffffff;  //取低位、、

    printf("序號引入= = = %x\n",Hint);

}

else

{

    PIMAGE_IMPORT_BY_NAME NAME =

                         PIMAGE_IMPORT_BY_NAME(*pOriginalFirstThunk - SECRVA +SECVA +(DWORD)pFileAddr);

    printf("HINT = %x\n",NAME->Hint);

    printf("NAME = %s\n",NAME->Name);

}

pOriginalFirstThunk++;

FirstThunk++;

}

IMPORT++; }

}

當PE文件加載到內存時FirstThunk的內容就變爲真正的API的地址了、

再看導出表結構、、

typedef struct _IMAGE_EXPORT_DIRECTORY {

   DWORD   Characteristics;

   DWORD   TimeDateStamp;

   WORD    MajorVersion;

   WORD    MinorVersion;

   DWORD   Name;

   DWORD   Base;

   DWORD   NumberOfFunctions;

   DWORD   NumberOfNames;

   DWORD   AddressOfFunctions;     // RVA from base of image

   DWORD   AddressOfNames;        // RVA from base of image

   DWORD   AddressOfNameOrdinals;  // RVA from base of image

} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;


導出表的概念:

輸出函數是用來給其他程序使用的。其他程序如果知道了某個輸出函數的入口地址(就是實現這個函數功能的代碼開始的地方),就可以轉到那裏去執行。一個PE文件中,如果有有輸出函數,一般都不是一個。所以有一個數組來保存每個輸出函數的入口地址。

在PE文件中,提供兩種方法,來找到某個輸出函數的入口地址。


第一種方法是通過入口地址數組序號,就是說知道是入口地址數組中的第幾個元素,這樣就可以得到裏面的入口地址。

第二種方法是通過函數名,通過比較函數名,然後得到對應該函數名的入口地址數組的序號,從而得到該函數名的對應函數的入口地址。


導出表的遍歷比較簡單、、

因爲導出函數的地址的RVA有 導出函數的名字的RVA有導出函數的序號的RVA有、、

直接遍歷即可、、

LPDWORD DLLNAME = (LPDWORD)(EXPORT->Name- SECRVA + SECVA +pFileAddr);

printf("EXPORT  DLLNAME =======  %s\n\n\n",DLLNAME);//dll名字的輸出

LPDWORD FUNCADDR = (LPDWORD)(EXPORT->AddressOfFunctions- SECRVA + SECVA +pFileAddr);//函數RVA的RAW

LPWORD ORDADDR = (LPWORD)(EXPORT->AddressOfNameOrdinals- SECRVA + SECVA +pFileAddr);//函數序號索引的RAW

LPDWORD FUNCNAME = (LPDWORD)(EXPORT->AddressOfNames- SECRVA + SECVA +pFileAddr);//函數名字的RAW

for(DWORD i=0; i<EXPORT->NumberOfFunctions; i++)

{

LPDWORD REALFUNCNAME=(LPDWORD)(*FUNCNAME-SECRVA + SECVA +pFileAddr);//函數名字的輸出 再轉換一次纔是名字的RAW

、、

、、

、、 輸出即可、、

FUNCNAME++;  //數組加加、、

ORDADDR++;

FUNCADDR++;

}

}

完、


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章