PE文件解析器的編寫(二)——PE文件頭的解析


之前在學習PE文件格式的時候,是通過自己查看各個結構,自己一步步計算各個成員在結構中的偏移,然后在計算出其在文件中的偏移,從而找到各個結構的值,但是在使用C語言編寫這個工具的時候,就比這個方便的多,只要將對應的指針類型轉化為各個結構類型,就可以使用指針中的箭頭來直接尋址到結構中的各個成員。
這次主要說明的是PE文件頭的解析,也就是之前看到的第一個界面中顯示的內容,這個部分涉及到CPeFileInfo這個解析類的部分代碼,以及CPeFileInfoDlg這個對話框類的代碼。

選擇目標文件

首先通過點擊open按鈕來彈出一個對話框,讓用戶選擇需要解析的文件。
這個部分的代碼如下:

void CPEInfoDlg::OnBnClickedBtnOpen()
{
    // TODO: 在此添加控件通知處理程序代碼
    TCHAR szFilePath[MAX_PATH] = _T("");
    OPENFILENAME ofn = {0};
    ofn.lStructSize      = sizeof(ofn);
    ofn.hwndOwner        = m_hWnd;
    ofn.hInstance        = GetModuleHandle(NULL);
    ofn.nMaxFile         = MAX_PATH;
    ofn.lpstrInitialDir  = _T(".");
    ofn.lpstrFile        = szFilePath;
    ofn.lpstrTitle       = _T("Open ...[PEInfo] by liuhao");
    ofn.Flags            = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
    ofn.lpstrFilter      = _T("*.*\0*.*\0");
    GetOpenFileName(&ofn);

    m_PeFileInfo.strFilePath = szFilePath;
    GetDlgItem(IDC_FILE_PATH)->SetWindowText(szFilePath);

    m_PeFileInfo.UnLoadFile();
    BOOL bLoadSuccess = m_PeFileInfo.LoadFile();
    if (bLoadSuccess)
    {
        //成功打開
        if (m_PeFileInfo.IsPeFile())
        {
            ShowFileHeaderInfo();
            ShowOptionHeaderInfo();
            GetDlgItem(IDC_BTN_CALC)->EnableWindow(TRUE);
            GetDlgItem(IDC_BTN_DATA_DIR_INFO)->EnableWindow(TRUE);
            GetDlgItem(IDC_BTN_SECTION_INFO)->EnableWindow(TRUE);
            GetDlgItem(IDC_BTN_CHAR_INFO)->EnableWindow(TRUE);
            GetDlgItem(IDC_RVA)->EnableWindow(TRUE);

        }else
        {
            MessageBox(_T("打開的文件不是PE文件"));
            InitCommandCtrl();
        }
    }
}

在這段代碼中首先通過GetOpenFileName函數來彈出一個選擇文件的對話框。這個函數需要傳入一個OPENFILENAME 結構的指針變量,這個結構比較復雜,在這說下它比較重要的幾個成員:

lStructSize //結構的大小
hwndOwner //這個對話框所屬的父窗口句柄
hInstance //所在模塊的句柄
lpstrFile //用來保存用戶選擇文件的路徑的緩沖
nMaxFile //緩沖區的大小
lpstrTitle //這個對話框的標題
Flags//對話框的標識,具體標識請查看MSDN,一般我們用這樣幾個就足夠了

一般只需要更改標題,內存緩沖區指針和它的大小,其余按照上面的代碼默認就好
用戶選擇后,將用戶選擇的文件的全路徑顯示出來,並調用CPefileInfo類中的加載函數進行加載,如果之前加載過,那么先卸載它。然后再在對話框中顯示它主要的信息,並且將所有按鈕設置為可用狀態,

加載與卸載PE文件結構

在這個里面主要有這樣幾個函數

m_PeFileInfo.UnLoadFile();
m_PeFileInfo.LoadFile();
m_PeFileInfo.IsPeFile();
ShowFileHeaderInfo();
ShowOptionHeaderInfo();

接下來轉到CPeFileInfo這個類中
在這個類中定義了這樣四個主要的成員

    CString strFilePath; //對應PE文件所在路徑
    HANDLE hFile; //打開這個文件時的文件句柄
    PVOID pImageBase; //文件在內存中的首地址
    HANDLE hMapping; //文件映射的句柄
BOOL CPeFileInfo::LoadFile()
{
    if(strFilePath.IsEmpty())
    {
        return FALSE;
    }
    hFile = CreateFile(strFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (INVALID_HANDLE_VALUE  == hFile)
    {
        return FALSE;
    }

    hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
    if (NULL == hMapping)
    {
        CloseHandle(hFile);
        hFile = NULL;
        return FALSE;
    }

    pImageBase = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
    if (NULL == pImageBase)
    {
        CloseHandle(hMapping);
        CloseHandle(hFile);

        hMapping = NULL;
        hFile = NULL;
        return FALSE;
    }

    return TRUE;
}

void CPeFileInfo::UnLoadFile()
{
    if(pImageBase != NULL)
    {
        UnmapViewOfFile(pImageBase);
    }

    if(NULL != hMapping)
    {
        CloseHandle(hMapping);
    }

    if (NULL != hFile)
    {
        CloseHandle(hFile);
    }

    pImageBase = NULL;
    hFile = NULL;
    hMapping = NULL;
}

BOOL CPeFileInfo::IsPeFile()
{
    PIMAGE_DOS_HEADER pDosHeader = NULL;
    PIMAGE_NT_HEADERS pNtHeader = NULL;
    if (NULL == pImageBase)
    {
        return FALSE;
    }

    pDosHeader = (PIMAGE_DOS_HEADER)pImageBase;
    if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
    {
        return FALSE;
    }

    pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)(pDosHeader->e_lfanew) + (DWORD)pImageBase);
    if (pNtHeader->Signature != IMAGE_NT_SIGNATURE)
    {
        return FALSE;
    }

    return TRUE;
}

在加載的時候,主要通過一個文件映射的方式,將pe文件的整個內容原模原樣的拷貝到內存中,並保存這個文件句柄,文件映射句柄,文件所在內存的首地址等信息,在卸載的時候進行關閉句柄,清理資源的操作。
在程序中有一個判斷該文件是否是PE文件的操作。在PE的DOS頭結構中的e_magic結構保存的是’MZ’這個標志對應的16進制數是0x4d5a,另外在pe頭中有一個Sinature成員保存了0x50450000這個值,它對應的字符是‘PE’只有滿足這兩個條件的文件才是一個正常的PE文件。否則就不是。

獲取DOS頭和PE頭

在之前我們說過,PE文件開始的位置是一個DOS頭結構——IMAGE_DOS_HEADER STRUCT,它的第一個成員作為DOS頭的標識,一般保存的都是‘MZ’,而它里面的e_lfanew則保存真正的PE頭所在的偏移
所在獲取DOS頭的時候簡單的將前面的幾個字節轉化為這個結構即可,在尋址PE頭的時候用e_lfanew成員加上文件的起始地址就可以得到PE頭的地址。具體對應的代碼如下:

pDosHeader = (PIMAGE_DOS_HEADER)pImageBase;
pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)(pDosHeader->e_lfanew) + (DWORD)pImageBase);

顯示FileHeader信息和ptionalHeader信息

在PE頭的結構體定義如下:

IMAGE_NT_HEADERS STRUCT 
{
     DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;  
} IMAGE_NT_HEADERS ENDS

這個里面的第二個第三個成員就分別是FileHeader信息和ptionalHeader信息,剩下的就只是對這個結構的部分重要成員進行解析和顯示了

void CPEInfoDlg::ShowFileHeaderInfo()
{
    PIMAGE_FILE_HEADER pFileHeader = m_PeFileInfo.GetFileHeader();
    if (NULL != pFileHeader)
    {
        //省略部分不重要的代碼
        //時間戳轉為具體時間
        tm p;
        errno_t err1;
        err1 = gmtime_s(&p,(time_t*)&pFileHeader->TimeDateStamp);
        TCHAR s[100] = {0};
        _tcsftime (s, sizeof(s) / sizeof(TCHAR), _T("%Y-%m-%d %H:%M:%S"), &p);
        GetDlgItem(IDC_TIME_STAMP)->SetWindowText(s);
    }
    else
    {
        MessageBox(_T("顯示數據錯誤"));
    }
}

在這函數中我們直接通過指針尋址的方式來獲取其中的信息,比如NumberOfSections(當前文件中的節表數量)、TimeDateStamp(時間戳信息)、PointerToSymbolTable(符號表所在地址相對於文件的偏移)、NumberOfSymbols(符號表的數目)、SizeOfOptionalHeader(OptionalHeader結構的大小)、Characteristics(文件的屬性值)。
在顯示屬性值時,另外提供了一個轉化函數將這個值轉化為具體的標識。需要進行轉化的請參考下面的代碼

void CPeFileInfo::GetFileCharacteristics(CString &strCharacter)
{
    DWORD dwMachine = 0;
    PIMAGE_FILE_HEADER pFileHeader = GetFileHeader();
    if (0 != (pFileHeader->Characteristics & IMAGE_FILE_RELOCS_STRIPPED))
    {
        strCharacter += _T("文件中不存在重定位信息\r\n");
    }
    if(0 != (pFileHeader->Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE))
    {
        strCharacter += _T("文件可執行\r\n");
    }

    if (0 != (pFileHeader->Characteristics & IMAGE_FILE_LARGE_ADDRESS_AWARE))
    {
        strCharacter += _T("可以處理大於2GB內容\r\n");
    }
    if(0 != (pFileHeader->Characteristics & IMAGE_FILE_32BIT_MACHINE))
    {
        strCharacter += _T("目標平台為32位機器\r\n");
    }

    if (0 != (pFileHeader->Characteristics & IMAGE_FILE_SYSTEM))
    {
        strCharacter += _T("該文件是系統文件\r\n");
    }
    if (0 != (pFileHeader->Characteristics & IMAGE_FILE_DLL))
    {
        strCharacter += _T("該文件是dll文件\r\n");
    }
    if(0 != (pFileHeader->Characteristics & IMAGE_FILE_UP_SYSTEM_ONLY))
    {
        strCharacter += _T("該程序只能運行在單核處理器上");
    }
}

對於OptionalHeader結構的解析,目前也只是簡單的對它其中的數據結構進行打印,它里面最重要的結構DataDirectory留着在后面的部分進行說明。


免責聲明!

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



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