深入浅出PE文件格式---自己动手打造PE Show
深入淺出PE文件格式---自己動手打造PE Show
??????????????????????????????????????????????????????????? 作者:WiNrOOt
//??????????????????????????? 開篇???????????????????????????????????????????? //
///
?? 大家好!我一位菜鳥,學習加密解密已經有一段時間了,可是對于脫殼總是似懂非懂,心中甚是不爽。
于是就從PE結構開始,在學習的過程中我發現要真正了解PE文件結構就必須動手,只有動手您才能看懂他,
感受他,直到你應用他………………這是我的一點廢話希望大家不要嫌煩。下面是是我的學習筆記,希望能給大家
帶來一點提示,文中有不對的地方請各位大蝦指正。謝謝!
//
//??????????????????????????? 準備??????????????????????????????????????? //
/
開篇之前我想大家起碼要有幾點準備:
1。Iczelion's Win32 Assembly的教程我們主要是圍繞他的PE教程來實現我們的函數功能。
??? (其實Win32ASM Tutorial Resource Kit v1.00 Collected and packed by dREAMtHEATER就包含這些
??? 還是翻譯過的。下載地址WWW.PEDIY.COM)
??? 下載回來希望您能看一下,這樣大家就好交流:-)
2。一個開發環境。(我用的是VC++6.0)
3。一個適合你研究東西的環境。
//?????????????????????????? 正文????????????????????????????????????????? //
///
???? “ PE 的意思就是 Portable Executable(可移植的執行體)。它是 Win32環境自身所帶的執行體文件格
式。它的一些特性繼承自 Unix的 Coff (common object file format)文件格式。"portable executable"
(可移植的執行體)意味著此文件格式是跨win32平臺的 :
即使Windows運行在非Intel的CPU上,任何win32平臺的PE裝載器都能識別和使用該文件格式。當然,移植到不
同的CPU上PE執行體必然得有一些改變。所有 win32執行體
(除了VxD和16位的Dll)都使用PE文件格式,包括NT的內核模式驅動程序(kernel mode
drivers)。因而研究PE文件格式給了我們洞悉Windows結構的良機。“
????? 好了,上面這段話就是我們為什么要研究PE文件結構。
????? 看圖1,
這張圖我相信大家不陌生,第一塊是DOS MZ header這是什么呢?
????? 這張圖的每一塊都是什么意思呢?
????? 從編程這方面說圖中的每一塊都代表著一個結構體,這些小塊都包含有不同的子塊,也是些結構體。
每個小塊都有他自己的功能,我相信Iczelion's的教程中已經表達得很明白了。
那么我們就來開始動手。
?我們要設計我們自己的PE TOOLS----PE Show
?主要功能:1判斷文件是否是PE文件。
?????????? 2顯示pe文件的相關信息。
1.打開文件代碼如下:
代碼
if(FALSE==PEfile.Open(m_filename,CFile::typeBinary&line;CFile::shareDenyNone))
?&leftsign;
? MessageBox("文件打不開!");
? return;
?&rightsign;
CFile類的使用方法希望大及自己去查找msdn
2。文件我們打開了而且是以Binary方式打開的,下面我們該干什么了?
? 編寫第一個功能-----檢驗PE文件的有效性
在Iczelion's的教程中有這樣一段話:
“1。首先檢驗文件頭部第一個字的值是否等于 IMAGE_DOS_SIGNATURE,是則 DOS MZ header 有效。
?2。一旦證明文件的 DOS header 有效后,就可用e_lfanew來定位 PE header 了。
?3。比較 PE header 的第一個字的值是否等于 IMAGE_NT_HEADER。
??? 如果前后兩個值都匹配,那我們就認為該文件是一個有效的PE文件。
這就是檢驗PE文件有效性的流程。
從上面那段話我們看出判斷的關鍵是PE header 的第一個字的值是否等于 IMAGE_NT_HEADER
直到這些我們就倒著找。
PE header 的第一個字的值是什么?
我么就來看一下IMAGE_NT_HEADERS的結構:(查看WINNT.H就找到了)
typedef struct _IMAGE_NT_HEADERS &leftsign;
??? DWORD Signature;
??? IMAGE_FILE_HEADER FileHeader;
??? IMAGE_OPTIONAL_HEADER32 OptionalHeader;
&rightsign; IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
Signature 一dword類型,值為50h, 45h, 00h, 00h(PE/0/0)。 本域為PE標記,我們可以此識別給定文件是否為有效PE文件。
FileHeader 該結構域包含了關于PE文件物理分布的信息, 比如節數目、文件執行機器等。
OptionalHeader 該結構域包含了關于PE文件邏輯分布的信息,雖然域名有"可選"字樣,但實際上本結構總是存在的。
我們目的很明確。如果IMAGE_NT_HEADERS的signature域值等于"PE/0/0",那么就是有效的PE文件。實際上,為了比較方便,Microsoft已定義了常量IMAGE_NT_SIGNATURE供我們使用。
IMAGE_DOS_SIGNATURE equ 5A4Dh
IMAGE_OS2_SIGNATURE equ 454Eh
IMAGE_OS2_SIGNATURE_LE equ 454Ch
IMAGE_VXD_SIGNATURE equ 454Ch
IMAGE_NT_SIGNATURE equ 4550h
判斷的問題我們解決了,新的問題又來了我們如何定位IMAGE_NT_HEADERS結構的位置。
MS肯定有辦法,PE文件的開頭是什么?DOS MZ header結構,我們來看一下他的定義:代碼
typedef struct _IMAGE_DOS_HEADER &leftsign;????? // DOS .EXE header
??? WORD?? e_magic;???????????????????? // Magic number
??? WORD?? e_cblp;????????????????????? // Bytes on last page of file
??? WORD?? e_cp;??????????????????????? // Pages in file
??? WORD?? e_crlc;????????????????????? // Relocations
??? WORD?? e_cparhdr;?????????????????? // Size of header in paragraphs
??? WORD?? e_minalloc;????????????????? // Minimum extra paragraphs needed
??? WORD?? e_maxalloc;????????????????? // Maximum extra paragraphs needed
??? WORD?? e_ss;??????????????????????? // Initial (relative) SS value
??? WORD?? e_sp;??????????????????????? // Initial SP value
??? WORD?? e_csum;????????????????????? // Checksum
??? WORD?? e_ip;??????????????????????? // Initial IP value
??? WORD?? e_cs;??????????????????????? // Initial (relative) CS value
??? WORD?? e_lfarlc;??????????????????? // File address of relocation table
??? WORD?? e_ovno;????????????????????? // Overlay number
??? WORD?? e_res[4];??????????????????? // Reserved words
??? WORD?? e_oemid;???????????????????? // OEM identifier (for e_oeminfo)
??? WORD?? e_oeminfo;?????????????????? // OEM information; e_oemid specific
??? WORD?? e_res2[10];????????????????? // Reserved words
??? LONG?? e_lfanew;??????????????????? // File address of new exe header
? &rightsign; IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
?
? 看一下最后一項!!!!!
? 發現了嗎?他指向的就是PE header
? 那么DOS MZ header的位置怎么確定呢?呵呵,他就是文件的開始
? 看代碼:代碼
??????? PEfile.Read(&stPEDosHeader,sizeof(_IMAGE_DOS_HEADER));
?if(stPEDosHeader.e_magic!=IMAGE_DOS_SIGNATURE)//"MZ"
?&leftsign;
? MessageBox("DOS MZ header無效!");
? PEfile.Close();
? return;
?&rightsign;
?else
?&leftsign;
? //-----------------------
? //顯示DOS Header
? //-----------------------
? UpdateData(true);
? m_Magicnumber.Format(_T("0x%.4X"),stPEDosHeader.e_magic);
? m_cblp.Format(_T("0x%.4X"),stPEDosHeader.e_cblp);
? m_cp.Format(_T("0x%.4X"),stPEDosHeader.e_cp);
? m_crlc.Format(_T("0x%.4X"),stPEDosHeader.e_crlc);
? m_cparhdr.Format(_T("0x%.4X"),stPEDosHeader.e_cparhdr);
? m_minalloc.Format(_T("0x%.4X"),stPEDosHeader.e_minalloc);
? m_maxalloc.Format(_T("0x%.4X"),stPEDosHeader.e_maxalloc);
? m_ss.Format(_T("0x%.4X"),stPEDosHeader.e_ss);
? m_sp.Format(_T("0x%.4X"),stPEDosHeader.e_sp);
? m_csum.Format(_T("0x%.4X"),stPEDosHeader.e_csum);
? m_ip.Format(_T("0x%.4X"),stPEDosHeader.e_ip);
? m_cs.Format(_T("0x%.4X"),stPEDosHeader.e_cs);
? m_lfarlc.Format(_T("0x%.4X"),stPEDosHeader.e_lfarlc);
? m_ovno.Format(_T("0x%.4X"),stPEDosHeader.e_ovno);
? m_oemid.Format(_T("0x%.4X"),stPEDosHeader.e_oemid);
? m_oeminfo.Format(_T("0x%.4X"),stPEDosHeader.e_oeminfo);
? m_lfanew.Format(_T("0x%.8X"),stPEDosHeader.e_lfanew);
? UpdateData(false);
?&rightsign;
?buf=stPEDosHeader.e_lfanew;? //確定_IMAGE_DOS_HEADER偏移
?try&leftsign;PEfile.Seek(buf,CFile::begin);&rightsign;
?catch(...)
?&leftsign;
? MessageBox("_IMAGE_DOS_HEADER.e_lfanew不對!");
? PEfile.Close();
? return;
?&rightsign;
?PEfile.Read(&stPEHeader,sizeof(_IMAGE_NT_HEADERS));//----------NT頭
?if(stPEHeader.Signature!=IMAGE_NT_SIGNATURE)//"PE/0/0"
?&leftsign;
? MessageBox("該文件不是PE格式!");
? PEfile.Close();
? return;
?&rightsign;
?else
?????? &leftsign;
????????? MessageBox("該文件是PE格式!");
????????? PEfile.Close();
? return;
??????? &rightsign;?
?
好了現在我們已經確定了文件是否是有效的PE文件。順便我們把IMAGE_DOS_HEADER的結構成員都顯示出來了。
我們已經寫了一個功能。
下面我們繼續順著往下看:
IMAGE_DOS_HEADER結構結束那么就是IMAGE_NT_HEADERS開始。結構體的成員情況前面我們已經介紹過了
下面我們來提取他們:代碼
? //---------------------------
? //顯示IMAGE_FILE_HEADER結構
? //---------------------------
? UpdateData(true);
? m_Machine.Format(_T("0x%.4X"),stPEHeader.FileHeader.Machine);
? m_NumberOfSections.Format(_T("0x%.4X"),stPEHeader.FileHeader.NumberOfSections);
? m_TimeDateStamp.Format(_T("0x%.8X"),stPEHeader.FileHeader.TimeDateStamp);
????? m_PointerToSymbolTable.Format(_T("0x%.8X"),stPEHeader.FileHeader.PointerToSymbolTable);
? m_NumberOfSymbols.Format(_T("0x%.8X"),stPEHeader.FileHeader.NumberOfSymbols);
? m_SizeOfOptionalHeader.Format(_T("0x%.4X"),stPEHeader.FileHeader.SizeOfOptionalHeader);
? m_Characteristics.Format(_T("0x%.4X"),stPEHeader.FileHeader.Characteristics);
? UpdateData(false);??
????
這是IMAGE_FILE_HEADER FileHeader的成員,我們已經將他們提取出來。
看教程我們繼續尋找IMAGE_OPTIONAL_HEADER的成員,他是結構體中的結構體我們就順著找。代碼
typedef struct _IMAGE_OPTIONAL_HEADER &leftsign;
??? //
??? // Standard fields.
??? //
??? WORD??? Magic;
??? BYTE??? MajorLinkerVersion;
??? BYTE??? MinorLinkerVersion;
??? DWORD?? SizeOfCode;
??? DWORD?? SizeOfInitializedData;
??? DWORD?? SizeOfUninitializedData;
??? DWORD?? AddressOfEntryPoint;
??? DWORD?? BaseOfCode;
??? DWORD?? BaseOfData;
??? //
??? // NT additional fields.
??? //
??? DWORD?? ImageBase;
??? DWORD?? SectionAlignment;
??? DWORD?? FileAlignment;
??? WORD??? MajorOperatingSystemVersion;
??? WORD??? MinorOperatingSystemVersion;
??? WORD??? MajorImageVersion;
??? WORD??? MinorImageVersion;
??? WORD??? MajorSubsystemVersion;
??? WORD??? MinorSubsystemVersion;
??? DWORD?? Win32VersionValue;
??? DWORD?? SizeOfImage;
??? DWORD?? SizeOfHeaders;
??? DWORD?? CheckSum;
??? WORD??? Subsystem;
??? WORD??? DllCharacteristics;
??? DWORD?? SizeOfStackReserve;
??? DWORD?? SizeOfStackCommit;
??? DWORD?? SizeOfHeapReserve;
??? DWORD?? SizeOfHeapCommit;
??? DWORD?? LoaderFlags;
??? DWORD?? NumberOfRvaAndSizes;
??? IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
&rightsign; IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
?
?
看到這些成員將他們全部顯示出來
代碼
? //--------------------------------
? //顯示IMAGE_OPTIONAL_HEADER
? //--------------------------------
? UpdateData(true);
? m_Magic.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.Magic);
? m_MajorLinkerVersion.Format(_T("0x%.2X"),stPEHeader.OptionalHeader.MajorLinkerVersion);
? m_MinorLinkerVersion.Format(_T("0x%.2X"),stPEHeader.OptionalHeader.MinorLinkerVersion);
? m_SizeOfCode.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfCode);
? m_SizeOfInitializedData.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfInitializedData);
? m_SizeOfUninitializedData.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfUninitializedData);
? m_AddressOfEntryPoint.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.AddressOfEntryPoint);
? m_BaseOfCode.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.BaseOfCode);
? m_BaseOfData.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.BaseOfData);
? m_ImageBase.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.ImageBase);
? m_SectionAlignment.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SectionAlignment);
? m_FileAlignment.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.FileAlignment);
? m_MajorOperatingSystemVersion.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.MajorOperatingSystemVersion);
? m_MinorOperatingSystemVersion.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.MinorOperatingSystemVersion);
? m_MajorImageVersion.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.MajorImageVersion);
? m_MinorImageVersion.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.MinorImageVersion);
? m_MajorSubsystemVersion.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.MajorSubsystemVersion);
? m_MinorSubsystemVersion.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.MinorSubsystemVersion);
? m_Win32VersionValue.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.Win32VersionValue);
? m_SizeOfImage.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfImage);
? m_SizeOfHeaders.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfHeaders);
? m_CheckSum.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.CheckSum);
? m_Subsystem.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.Subsystem);
? m_DllCharacteristics.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.DllCharacteristics);
? m_SizeOfStackReserve.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfStackReserve);
? m_SizeOfStackCommit.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfStackCommit);
? m_SizeOfHeapReserve.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfHeapReserve);
? m_SizeOfHeapCommit.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfHeapCommit);
? m_LoaderFlags.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.LoaderFlags);
? m_NumberOfRvaAndSizes.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.NumberOfRvaAndSizes);
? UpdateData(false);
??
我們已經學了許多關于 DOS header 和 PE header 的知識。接下來就該輪到 section table(節表)了。
節表其實就是緊挨著 PE header 的一結構數組。該數組成員的數目由 file header (IMAGE_FILE_HEADER)
結構中 NumberOfSections 域的域值來決定。節表結構又命名為 IMAGE_SECTION_HEADER。代碼
typedef struct _IMAGE_SECTION_HEADER &leftsign;
??? BYTE??? Name[IMAGE_SIZEOF_SHORT_NAME];
??? union &leftsign;
??????????? DWORD?? PhysicalAddress;
??????????? DWORD?? VirtualSize;
??? &rightsign; Misc;
??? DWORD?? VirtualAddress;
??? DWORD?? SizeOfRawData;
??? DWORD?? PointerToRawData;
??? DWORD?? PointerToRelocations;
??? DWORD?? PointerToLinenumbers;
??? WORD??? NumberOfRelocations;
??? WORD??? NumberOfLinenumbers;
??? DWORD?? Characteristics;
&rightsign; IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER
Name???? 這兒的節名長不超過8字節。記住節名僅僅是個標記而已,我們選擇任何名字甚至空著也行,注意這里不用null結束。命名不是一個ASCIIZ字符串,所以不用null結尾。
VirtualAddress 本節的RVA(相對虛擬地址)。PE裝載器將節映射至內存時會讀取本值,因此如果域值是1000h,而PE文件裝在地址400000h處,那么本節就被載到401000h。
SizeOfRawData 經過文件對齊處理后節尺寸,PE裝載器提取本域值了解需映射入內存的節字節數。(譯者注: 假設一個文件的文件對齊尺寸是0x200,如果前面的 VirtualSize域指示本節長度是0x388字節,則本域值為0x400,表示本節是0x400字節長)。
PointerToRawData 這是節基于文件的偏移量,PE裝載器通過本域值找到節數據在文件中的位置。
Characteristics 包含標記以指示節屬性,比如節是否含有可執行代碼、初始化數據、未初始數據,是否可寫、可讀等。
現在我們已知曉 IMAGE_SECTION_HEADER 結構,再來模擬一下 PE裝載器的工作吧:
1 讀取 IMAGE_FILE_HEADER 的 NumberOfSections域,知道文件的節數目。
2 SizeOfHeaders 域值作為節表的文件偏移量,并以此定位節表。
3 遍歷整個結構數組檢查各成員值。
4 對于每個結構,我們讀取PointerToRawData域值并定位到該文件偏移量。然后再讀取SizeOfRawData域值來決定映射內存的字節數。將VirtualAddress域值加上ImageBase域值等于節起始的虛擬地址。然后就準備把節映射進內存,并根據Characteristics域值設置屬性。
5 遍歷整個數組,直至所有節都已處理完畢。
代碼如下:代碼
? //------------------------------
? //顯示Section結構
? //------------------------------
? nSection=stPEHeader.FileHeader.NumberOfSections;
? stSectionHeader=new _IMAGE_SECTION_HEADER[nSection];
? m_ListCtrl.DeleteAllItems();
? for(int i=0;i? &leftsign;
??
?? PEfile.Read(&stSectionHeader[i],sizeof(_IMAGE_SECTION_HEADER));//-----節表
?? //NO
?? szTemp.Format(_T("%.2d"), i+1);
?? m_ListCtrl.InsertItem(i,szTemp,i);
?? //SectionName
?? strcpy(chSectionName,(LPCSTR)stSectionHeader[i].Name);
?????????? m_ListCtrl.SetItemText(i,1,chSectionName);
?? //VirtualSize
?? szTemp.Format(_T("0x%.8X"),stSectionHeader[i].Misc.VirtualSize);
????????????? m_ListCtrl.SetItemText(i,2,szTemp);
?? //VirtualAddress
?? szTemp.Format(_T("0x%.8X"),stSectionHeader[i].VirtualAddress );
????????????? m_ListCtrl.SetItemText(i,3,szTemp);
?? //SizeOfRawData
?? szTemp.Format(_T("0x%.8X"),stSectionHeader[i].SizeOfRawData );
?? m_ListCtrl.SetItemText(i,4,szTemp);
?? //SizeOffset
?? szTemp.Format(_T("0x%.8X"),stSectionHeader[i].PointerToRawData );
?? m_ListCtrl.SetItemText(i,5,szTemp);
?? //Characteristics
?? szTemp.Format(_T("0x%.8X"),stSectionHeader[i].Characteristics );
?? m_ListCtrl.SetItemText(i,6,szTemp);
??
??
? &rightsign;
? delete stSectionHeader;
總結
以上是生活随笔為你收集整理的深入浅出PE文件格式---自己动手打造PE Show的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: rabbitmq 延迟队列_框架系列|中
- 下一篇: button 样式_实战PyQt5: 1