先看一下輸入表結構:
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++;
}
}
完、