Q Q:408365330 E-Mail:egojit@qq.com
綜述:
首先說明我也只是PE文件的初學者,我所寫的都是自己的學習記錄。前3節學習了PE的一些結構,其中包括DOS頭和PE頭部分。總是這樣去學習這些機構和理論的分析我想大家和我一樣毫無興趣,提不起精神。所以我嘗試着在自己對PE了解的基礎上用C++寫一個小程序分析PE結構文件。我所接觸的教程都是Win32匯編去實現一些小工具對PE文件進行分析。我只是能看懂匯編,讓我去寫那沒什么勁,所以我還是用C++去實現。我只是對C比較了解,至於面向對象的知識是在學習C#時候接觸的。所以這里的案例的C++代碼很大的可能還是繼承了C的風格。為什么是C++,主要是MFC寫界面比Windows API寫界面方便多了。當然如果大家有興趣,可以留言我寫一個C#版本的。當然這一節也不可能實現所有的功能,暫時展示一些小功能。廢話不多說了,上代碼:
C++代碼實現:
首先實現這些如圖程序的功能,其中包括識別是否是PE文件,其次是給出,PE文件在磁盤中的對齊尺寸和內存中的對齊尺寸,另一個就是實現知道程序的裝載入口地址,都是以16進制的方式表現出來的。
//選擇文件按鈕 void CPEinfoDlg::OnBnClickedBtnSelectfile() { CFileDialog dilog(TRUE); dilog.m_ofn.lpstrTitle=_T("請選擇PE文件"); if(IDOK==dilog.DoModal()){ CString fileName= dilog.GetFileName(); CString filePath= dilog.GetPathName(); //給路徑文本框賦值 this->GetDlgItem(IDC_EDIT1)->SetWindowTextW(filePath); HANDLE fileHandle= CreateFile(filePath,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if(INVALID_HANDLE_VALUE!=fileHandle){ HANDLE mapHandle= CreateFileMapping(fileHandle,NULL,PAGE_READONLY,0,0,NULL); if(mapHandle==NULL){ AfxMessageBox(L"打開文件映射對象失敗!"); } else { strContet= MapViewOfFile(mapHandle,FILE_MAP_READ,0,0,0); LPBYTE lpBaseAddress = (LPBYTE)strContet; PIMAGE_DOS_HEADER dosHead=(PIMAGE_DOS_HEADER)lpBaseAddress; PIMAGE_NT_HEADERS ntHead=(PIMAGE_NT_HEADERS)(lpBaseAddress+dosHead->e_lfanew); //根據DOS頭和PE頭判斷是否是PE文件 if(dosHead->e_magic==IMAGE_DOS_SIGNATURE&&ntHead->Signature==IMAGE_NT_SIGNATURE){ //AfxMessageBox(L"說明是正常的PE!"); CString show=L"0x"; wchar_t r[10]=L""; int h=ntHead->OptionalHeader.FileAlignment; _itow_s(h,r,16); show+=r; this->GetDlgItem(IDC_STATIC_FileA)->SetWindowTextW(show); //內存對齊尺寸 int ss= ntHead->OptionalHeader.SectionAlignment; _itow_s(ss,r,16); show=L"0x"; show+=r; this->GetDlgItem(IDC_STATIC_SESSIONSIZE)->SetWindowTextW(show); //入口地址 int ept= ntHead->OptionalHeader.AddressOfEntryPoint; _itow_s(ept,r,16); show=L"0x"; show+=r; this->GetDlgItem(IDC_STATIC_BaseEntry)->SetWindowTextW(show); } else { AfxMessageBox(L"請打開PE格式文件!"); } //撤銷映射 UnmapViewOfFile(strContet); //關閉文件映射對象句柄 CloseHandle(mapHandle); } //關閉文件對象 CloseHandle(fileHandle); } else { AfxMessageBox(L"打開文件句柄失敗!"); } } delete dilog; // TODO: 在此添加控件通知處理程序代碼 }
我就主要上主要代碼,當然在這里我們最起碼得知道MFC框架的簡單開發。
這些小功能的原理就是讀文件,不過我這里用的讀文件的方式是文件映射的方式,直接將文件映射到內存中。
1.首先是打開文件,返回一個文件內核對象(什么是內核對象?這個我就不多做解釋了,要想了解請參照《Windows 核心編程》。這絕對是Windows開發的一本葵花寶典),
HANDLE fileHandle= CreateFile(filePath,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
2.然后創建一個文件映射對象
HANDLE mapHandle= CreateFileMapping(fileHandle,NULL,PAGE_READONLY,0,0,NULL);
當然是以讀方式打開。
3.然后就是文件內存視圖映射
strContet= MapViewOfFile(mapHandle,FILE_MAP_READ,0,0,0);
其中strContet是LPVOID類型,這個指針就指向了PE文件被映射到內存中的起始地址
4.找DOS頭和PE頭
PIMAGE_DOS_HEADER dosHead=(PIMAGE_DOS_HEADER)lpBaseAddress;
PIMAGE_NT_HEADERS ntHead=(PIMAGE_NT_HEADERS)(lpBaseAddress+dosHead->e_lfanew);
PIMAGE_DOS_HEADER 和PIMAGE_NT_HEADERS結構體就是廣義上的DOS頭和PE頭結構。這兩個結構體在WinNT.h頭文件中能找到。
5.判斷是否是PE文件
if(dosHead->e_magic==IMAGE_DOS_SIGNATURE&&ntHead->Signature==IMAGE_NT_SIGNATURE)
就和我們在前面所說的dosHead->e_magic是DOS頭的標志,里面是MZ所以DOS頭又稱作MZ頭,ntHead->Signature就是PE頭的標志,內容是ASIIC碼PE00。我用UE打開:
在WinNT.h頭文件中被定義為IMAGE_DOS_SIGNATURE和IMAGE_NT_SIGNATURE,顧名思義,DOS頭標志和PE頭標志。這樣即使你隨便將一個文件修改成.exe后綴或者.dll后綴的PE格式文件依然不通過驗證。
通過這個動態圖片我們很容易看到我想打開一個.txt文本這是不行的。我現在去把他改成exe再打開:
可以看見修改txt后綴為.exe是不行的。
6.顯示內存對齊尺寸程序入口地址等信息
int h=ntHead->OptionalHeader.FileAlignment; _itow_s(h,r,16); show+=r; this->GetDlgItem(IDC_STATIC_FileA)->SetWindowTextW(show); //內存對齊尺寸 int ss= ntHead->OptionalHeader.SectionAlignment; _itow_s(ss,r,16); show=L"0x"; show+=r; this->GetDlgItem(IDC_STATIC_SESSIONSIZE)->SetWindowTextW(show); //入口地址 int ept= ntHead->OptionalHeader.AddressOfEntryPoint; _itow_s(ept,r,16); show=L"0x"; show+=r; this->GetDlgItem(IDC_STATIC_BaseEntry)->SetWindowTextW(show);
其中沒什么復雜度,只是將結構成員數據處理顯示出來。這里也驗證了。一般PE文件在磁盤中的對齊粒度是200H在內存中的對齊粒度是1000H也就是4K,一分頁大小。
這一節就記到這里,后續的功能等我們學習到的時候再去添加。