網絡安全編程:PE編程實例之PE查看器
微信公眾號:計算機與網絡安全
ID:Computer-network
寫PE查看器并不是件復雜的事情,只要按照PE結構一步一步地解析就可以了。下面簡單地解析其中幾個字段內容,顯示一下節(jié)表的信息,其余的內容只要稍作修改即可。PE查看器的界面如圖1所示。
圖1 PE查看器解析記事本程序
PE查看器的界面按照圖1所示的設置,不過這個可以按照個人的偏好進行布局設置。編寫該PE查看器的步驟為打開文件并創(chuàng)建文件內存映像,判斷文件是否為PE文件并獲得PE格式相關結構體的指針,解析基本的PE字段,枚舉節(jié)表,最后關閉文件。需要在類中添加幾個成員變量及成員函數,添加的內容如圖2所示。
圖2 在類中添加的成員變量及成員函數
按照前面所說的順序,依次實現添加的各個成員函數。
- BOOL CPeParseDlg::FileCreate(char *szFileName)
- {
- BOOL bRet = FALSE;
- m_hFile = CreateFile(szFileName,
- GENERIC_READ | GENERIC_WRITE,
- FILE_SHARE_READ,NULL,OPEN_EXISTING,
- FILE_ATTRIBUTE_NORMAL,NULL);
- if ( m_hFile == INVALID_HANDLE_VALUE )
- {
- return bRet;
- }
- m_hMap = CreateFileMapping(m_hFile, NULL,
- PAGE_READWRITE | SEC_IMAGE,0, 0, 0);
- if ( m_hMap == NULL )
- {
- CloseHandle(m_hFile);
- return bRet;
- }
- m_lpBase = MapViewOfFile(m_hMap,
- FILE_MAP_READ | FILE_SHARE_WRITE,
- 0, 0, 0);
- if ( m_lpBase == NULL )
- {
- CloseHandle(m_hMap);
- CloseHandle(m_hFile);
- return bRet;
- }
- bRet = TRUE;
- return bRet;
- }
這個函數的主要功能是打開文件并創(chuàng)建內存文件映像。通常對文件進行連續(xù)讀寫時直接使用ReadFile()和WriteFile()兩個函數。當不連續(xù)操作文件時,每次在ReadFile()或者WriteFile()后就要使用SetFilePointer()來調整文件指針的位置,這樣的操作較為繁瑣。內存文件映像的作用是把整個文件映射入進程的虛擬空間中,這樣操作文件就像操作內存變量或內存數據一樣方便。
創(chuàng)建內存文件映像所使用的函數有兩個,分別是CreateFileMapping()和MapViewOfFile()。CreateFileMapping()函數的定義如下:
- HANDLE CreateFileMapping(
- HANDLE hFile,
- LPSECURITY_ATTRIBUTES lpAttributes,
- DWORD flProtect,
- DWORD dwMaximumSizeHigh,
- DWORD dwMaximumSizeLow,
- LPCTSTR lpName
- );
參數說明如下。
hFile:該參數是 CreateFile()函數返回的句柄。
lpAttributes:是安全屬性,該值通常是 NULL。
flProtect:創(chuàng)建文件映射后的屬性,通常設置為可讀可寫 PAGE_READWRITE。如果需要像裝載可執(zhí)行文件那樣把文件映射入內存的話,那么需要使用 SEC_IMAGE。最后3個參數在這里為0。如果創(chuàng)建的映射需要在多進程中共享數據的話,那么最后一個參數設定為一個字符串,以便通過該名稱找到該塊共享內存。
該函數的返回值為一個內存映射的句柄。
MapViewOfFile()函數的定義如下:
- LPVOID MapViewOfFile(
- HANDLE hFileMappingObject,
- DWORD dwDesiredAccess,
- DWORD dwFileOffsetHigh,
- DWORD dwFileOffsetLow,
- SIZE_T dwNumberOfBytesToMap
- );
參數說明如下。
hFileMappingObject:該參數為 CreateFileMapping()返回的句柄。
dwDesiredAccess:想獲得的訪問權限,通常情況下也是可讀可寫 FILE_MAP_READ、FILE_MAP_WRITE。
最后3個參數一般給0值就可以了。
按照編程的規(guī)矩,打開要關閉,申請要釋放。CreateFileMapping()的關閉需要使用CloseHandle()函數。MapViewOfFile()的關閉,要使用UnmapViewOfFile()函數,該函數的定義如下:
- BOOL UnmapViewOfFile(
- LPCVOID lpBaseAddress
- );
該函數的參數就是MapViewOfFile()函數的返回值。
接著說PE查看器,文件已經打開,就要判斷文件是否為有效的PE文件了。如果是有效的PE文件,就把解析PE格式的相關結構體的指針也得到。代碼如下:
- BOOL CPeParseDlg::IsPeFileAndGetPEPointer()
- {
- BOOL bRet = FALSE;
- // 判斷是否為 MZ 頭
- m_pDosHdr = (PIMAGE_DOS_HEADER)m_lpBase;
- if ( m_pDosHdr->e_magic != IMAGE_DOS_SIGNATURE )
- {
- return bRet;
- }
- // 根據 IMAGE_DOS_HEADER 的 e_lfanew 的值得到 PE 頭的位置
- m_pNtHdr = (PIMAGE_NT_HEADERS)((DWORD)m_lpBase + m_pDosHdr->e_lfanew);
- // 判斷是否為 PE\0\0
- if ( m_pNtHdr->Signature != IMAGE_NT_SIGNATURE )
- {
- return bRet;
- }
- // 獲得節(jié)表的位置
- m_pSecHdr = (PIMAGE_SECTION_HEADER)((DWORD)&(m_pNtHdr->OptionalHeader)
- + m_pNtHdr->FileHeader.SizeOfOptionalHeader);
- bRet = TRUE;
- return bRet;
- }
這段代碼應該非常容易理解,繼續(xù)看解析PE格式的部分。
- VOID CPeParseDlg::ParseBasePe()
- {
- CString StrTmp;
- // 入口地址
- StrTmp.Format("%08X", m_pNtHdr->OptionalHeader.AddressOfEntryPoint);
- SetDlgItemText(IDC_EDIT_EP, StrTmp);
- // 映像基地址
- StrTmp.Format("%08X", m_pNtHdr->OptionalHeader.ImageBase);
- SetDlgItemText(IDC_EDIT_IMAGEBASE, StrTmp);
- // 連接器版本號
- StrTmp.Format("%d.%d",
- m_pNtHdr->OptionalHeader.MajorLinkerVersion,
- m_pNtHdr->OptionalHeader.MinorLinkerVersion);
- SetDlgItemText(IDC_EDIT_LINKVERSION, StrTmp);
- // 節(jié)表數量
- StrTmp.Format("%02X", m_pNtHdr->FileHeader.NumberOfSections);
- SetDlgItemText(IDC_EDIT_SECTIONNUM, StrTmp);
- // 文件對齊值大小
- StrTmp.Format("%08X", m_pNtHdr->OptionalHeader.FileAlignment);
- SetDlgItemText(IDC_EDIT_FILEALIGN, StrTmp);
- // 內存對齊值大小
- StrTmp.Format("%08X", m_pNtHdr->OptionalHeader.SectionAlignment);
- SetDlgItemText(IDC_EDIT_SECALIGN, StrTmp);
- }
PE格式的基礎信息,就是簡單地獲取結構體的成員變量,沒有過多復雜的內容。獲取導入表、導出表比獲取基礎信息復雜。接下來進行節(jié)表的枚舉,具體代碼如下:
- VOID CPeParseDlg::EnumSections()
- {
- int nSecNum = m_pNtHdr->FileHeader.NumberOfSections;
- int i = 0;
- CString StrTmp;
- for ( i = 0; i < nSecNum; i ++ )
- {
- m_SectionLIst.InsertItem(i, (const char *)m_pSecHdr[i].Name);
- StrTmp.Format("%08X", m_pSecHdr[i].VirtualAddress);
- m_SectionLIst.SetItemText(i, 1, StrTmp);
- StrTmp.Format("%08X", m_pSecHdr[i].Misc.VirtualSize);
- m_SectionLIst.SetItemText(i, 2, StrTmp);
- StrTmp.Format("%08X", m_pSecHdr[i].PointerToRawData);
- m_SectionLIst.SetItemText(i, 3, StrTmp);
- StrTmp.Format("%08X", m_pSecHdr[i].SizeOfRawData);
- m_SectionLIst.SetItemText(i, 4, StrTmp);
- StrTmp.Format("%08X", m_pSecHdr[i].Characteristics);
- m_SectionLIst.SetItemText(i, 5, StrTmp);
- }
- }
最后的動作是釋放動作,因為很簡單,這里就不給出代碼了。將這些自定義函數通過界面上的“查看”按鈕聯系起來,整個PE查看器就算是寫完了。