PE檔案基礎補註
前言: 最近學習PE, 略有心得, 拿來和大家分享.
感謝: 小蝦斑斑, 非安全 ,Bookworm對我的協助.
1.IMAGE_SECTION_HEADER小結:
1.1 獲得節表數 :NumberOfSections = NtHeader->FileHeader.NumberOfSections;
1.2 節表獲得方法
方法1.因為NT頭之後就是節表,故,節表頭地址就是nt頭地址加上NT結構大小.
SectionHeader=(PIMAGE_SECTION_HEADER)((UINT32)NtHeader+(UINT32)(sizeof(IMAGE_NT_HEADERS)));
方法2.或者用ImageBase+SizeOfHeaders的辦法直接定位.
SectionHeader=(PIMAGE_SECTION_HEADER)((UINT32)(NtHeader->OptionalHeader.ImageBase)+
(UINT32)(NtHeader->OptionalHeader.SizeOfHeaders));
方法3.既然節都是連在一起的,那麼,也就可以這樣:
SectionHeader= (PIMAGE_SECTION_HEADER) (NtHeader + 1),
方法4.論壇裡面 hmimys 告訴的辦法:
SectionHeader=(PIMAGE_SECTION_HEADER)((UINT32)NtHeader+0x18+
(UINT32)(NtHeader->FileHeader.SizeOfOptionalHeader));
到現在我還沒有弄懂為什麼 hmimys 說最好要用方法4而不用方法3.
2. IMAGE_IMPORT_DECSRITOR 小結:
2.1:獲得引入表結構起始地址:
方法1:ImportDec = (PIMAGE_IMPORT_DESCRIPTOR)(NtHeader->OptionalHeader.DataDirectory[12].VirtualAddress);
這個方法我覺得理論上是對的,但是我在啟動並執行時候總是得不到正確的地址.後來知道,似乎不能用'12',而要用IMAGE_DIRECTORY_ENTRY_IAT這個宏
方法2:ImportDes = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)(NtHeader->OptionalHeader.DataDirectory)+
(DWORD)(sizeof(IMAGE_DATA_DIRECTORY)*12));
方法3 : ImportDes = (PIMAGE_IMPORT_DESCRIPTOR)(NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress-
Offset + (PBYTE)pMapping);
注 : 前兩種方法都是從 IAT 中得出 IMAGE_IMPORT_DESCRIPTOR,而後面的那個是 非安全 大哥教的. 這裡有個疑問:
3種方法都可以得到 IMAGE_IMPORT_DESCRIPTOR 結構,都可以得到函數名, 區別在於前兩種方法枚舉的函數名不全.
難道說兩個結構都指向同一個結構PIMAGE_IMPORT_DESCRIPTOR?
2.2 IMAGE_IMPORT_DESCRIPTOR 結構既不是在Import Symbols中,也不是在IAT (IMAGE_IMPORT_ADDRESS_TABLE)中。它就是一個結構.
我原來說:"IMAGE_IMPORT_DESCRIPTOR 結構不是在Import Symbols中,是在IAT (IMAGE_IMPORT_ADDRESS_TABLE)中。" 有問題.
就是因為這個錯誤的理解, 讓我走了好多死路.
這個是Winnt.h中關於 IMAGE_SYNMBOL的結構資訊
typedef struct _IMAGE_SYMBOL {
union {
BYTE ShortName[8];
struct {
DWORD Short; // if 0, use LongName
DWORD Long; // offset into string table
} Name;
PBYTE LongName[2];
} N;
DWORD Value;
SHORT SectionNumber;
WORD Type;
BYTE StorageClass;
BYTE NumberOfAuxSymbols;
} IMAGE_SYMBOL;
typedef IMAGE_SYMBOL UNALIGNED *PIMAGE_SYMBOL;
而下面的是IAT:
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
BYTE Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
};
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date/time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
_IMAGE_IMPORT_DESCRIPTOR 結構聯合中的OriginalFirstThunk , 就是到IMAGE_THUNK_DATA的RVA.
如果像下面這樣寫,也許更明白
typedef struct _IMAGE_THUNK_DATA {
union {
PBYTE ForwarderString;
PDWORD Function;
DWORD Ordinal;
PIMAGE_IMPORT_BY_NAME AddressOfData;
} ;
} IMAGE_THUNK_DATA,*PIMAGE_THUNK_DATA;
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
PIMAGE_THUNK_DATA OriginalFirstThunk;
} ;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
PIMAGE_THUNK_DATA FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;
3. 地址轉換小結(RVAToOffset):
為什麼要地址轉換, 前人的文章說了很多,下面給出我的轉換方法:
3.1 函數,它能給出RVA返回此RVA所在的節,來自 Matt Pietrek的書:
PIMAGE_SECTION_HEADER GetEnclosingSectionHeader(DWORD rva){
unsigned i;
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION32(NtHeader);
for ( i=0; i < NtHeader->FileHeader.NumberOfSections; i++,section++){
if ( (rva >=section->VirtualAddress) &&
(rva < (section->VirtualAddress + section->Misc.VirtualSize)))
return section;
}
return 0;
}
註: hnhuqiong 給的 ollydump300110 的源碼裡面也有類似函數,但是,
很明顯的有漏洞,那就是若RVA不在任何一個Section那麼函數會返回最後
一個Section, 而不是像這裡返回 0 .下面是原始串連
http://bbs.pediy.com/showthread.php?threadid=26520
3.2 RVAToOffset:
我一直沒有注意的就是'Offset'這個詞. Offset其實還是一個位移,只不過是
在檔案中, 要想得到目標檔案的IAT, 就要將這個值加上由 MapViewOfFile 返回
的檔案基址指標.
Offset的的獲得 :
pSection = GetEnclosingSectionHeader(NtHeader->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress);
Offset = (DWORD) (pSection->VirtualAddress - pSection->PointerToRawData);
以獲得IMAGE_THUNK_DATA結構為例,給出用法:
ThunkData = (PIMAGE_THUNK_DATA)((DWORD)ImportDes->OriginalFirstThunk -
Offset + (PBYTE)pMapping);
呵呵, (DWORD)ImportDes->OriginalFirstThunk -Offset 得到的只是檔案中的位移,
注意加上由 MapViewOfFile 返回的pMapping. 如果你象我原來一樣,加上的是
NtHeader->OptionalHeader.ImageBase , 那麼恭喜你, 訪問錯誤.
4. 用VC 6.0 + API獲得IMAGE_IMPORT_BY_NAME結構的一點問題.
在 VC 裡面, 在一個結構指標比如ThunkData後面加上'->'時, vc會自動的列出
結構的成員供你選擇, 十分方便. 但是, 通過ThunkData繼續想獲得IMAGE_IMPORT_BY_NAME
結構的時候, 你在ThunkData後面加'->'時, 出來的是一個'u1'. 此時不要疑惑,
這個'u1'就是 IMAGE_THUNK_DATA 裡面的那個 union 的名稱, 所以你可以這樣得到
IMAGE_IMPORT_BY_NAME結構:
ImportBN = (PIMAGE_IMPORT_BY_NAME)((DWORD)(ThunkData->u1.AddressOfData)-
Offset +(PBYTE)pMapping);
5. Iczelion的PE教程關於匯入表的描述沒有講清楚,只是說用IMAGE_THUNK_DATA
的每個數組元素和IMAGE_ORDINAL_FLAG32,比較可以推斷如果某個函數是由函數序數引出的,
我就誤解成用ImportDes->OriginalFirstThunk或者ImportDes->FirstThunk 判斷。是不是錯的很遠?
參考(【翻譯】“PE檔案格式”1.9版 完整譯文(附註釋))http://bbs.pediy.com/showthread.php?threadid=21932,
我們應該用IMAGE_THUNK_DATA結構裡面的AddressOfData來判斷。下面的代碼可行:
while(ThunkData->u1.AddressOfData!=NULL){
ImportBN = (PIMAGE_IMPORT_BY_NAME)((DWORD)(ThunkData->u1.AddressOfData) - Offset +(PBYTE)pMapping);
//顯示匯入函數
if(((DWORD)ThunkData->u1.AddressOfData & IMAGE_ORDINAL_FLAG32) == 0){
AddText(hEdit,TEXT("%03d: %s/r/n"),i++,ImportBN->Name);
}
else{
AddText(hEdit,TEXT("%03d: Ord by Hint/r/n"),i++);
}
ThunkData ++;
}//End of while
6 匯出表:
6.1 匯出表的結構,
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;
6.2 AddressOfNames 和AddressOfNameOrdinals 是一一對應的,只不過一個用於名字,
一個用於序號, 同一個函數的索引都相同。
6.3 NumberOfFunctions – NumberOfNames 應該就是由序號引出的函數數目了
6.4 對於由序號匯出的函數,不知道有沒有辦法能通過序數找到函數名。個人考慮似乎不可能這樣
找函數名字,不然,微軟未公開的函數就都被我們通過函數序數枚舉出來了? :)
7: 把我的PE查看器修改了下, 原來的在處理用序號引出的函數時會出錯.:)