PE文件學習系列筆記四-C++實現PE文件的分析


合肥程序員群:49313181。    合肥實名程序員群:128131462 (不願透露姓名和信息者勿加入)
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++代碼實現:

PEc_1

首先實現這些如圖程序的功能,其中包括識別是否是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打開:

PE標志

在WinNT.h頭文件中被定義為IMAGE_DOS_SIGNATURE和IMAGE_NT_SIGNATURE,顧名思義,DOS頭標志和PE頭標志。這樣即使你隨便將一個文件修改成.exe后綴或者.dll后綴的PE格式文件依然不通過驗證。

PEyz

通過這個動態圖片我們很容易看到我想打開一個.txt文本這是不行的。我現在去把他改成exe再打開:

peyz4

可以看見修改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,一分頁大小。

這一節就記到這里,后續的功能等我們學習到的時候再去添加。

版權:歸博客園和Egojit所有,轉載請標明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM