PEファイル解析器の作成(二)――PEファイルヘッダの解析
10415 ワード
以前PEファイルのフォーマットを勉強していたときは、自分で各構造を見て、自分で各メンバーの構造内のオフセットを一歩一歩計算して、ファイル内のオフセットを計算して、各構造の値を見つけましたが、C言語でこのツールを書くときは、これよりずっと便利で、対応するポインタタイプを各構造タイプに変換すれば、ポインタの矢印を使用して、構造内の各メンバーに直接アドレスできます.今回は主にPEファイルヘッダの解析,すなわち,CPeFileInfoという解析クラスの部分コードと,CPeFileInfoDlgというダイアログクラスのコードに関する最初に見たインタフェースに表示される内容について説明する.
ターゲットファイルの選択
まずopenボタンをクリックしてダイアログボックスを開き、解析するファイルを選択させます.このセクションのコードは次のとおりです.
このコードでは、まずGetOpenFileName関数を使用してファイルを選択するダイアログボックスを開きます.この関数はOPENFILENAME構造のポインタ変数を入力する必要があります.この構造は複雑で、これは重要ないくつかのメンバーです.
一般的には、タイトル、メモリバッファポインタ、およびそのサイズを変更するだけで、残りは上のコードに従ってデフォルトでユーザーが選択した後、ユーザーが選択したファイルのフルパスを表示し、CPefileInfoクラスのロード関数を呼び出してロードします.前にロードした場合は、アンインストールします.次に、ダイアログボックスに主要な情報を表示し、すべてのボタンを使用可能な状態に設定します.
PEファイル構造のロードとアンインストール
この中には主にこのようないくつかの関数があります
次にCPeFileInfoというクラスに移動します.このクラスでは、このような4つの主要なメンバーが定義されています.
ロード時には、主に1つのファイルマッピング方式でpeファイルの全体の内容をそのままメモリにコピーし、このファイルハンドル、ファイルマッピングハンドル、ファイルが存在するメモリのヘッダアドレスなどの情報を保存し、アンインストール時にハンドルを閉じ、リソースをクリーンアップする操作を行う.プログラムには、そのファイルがPEファイルであるか否かを判断する操作がある.PEのDOSヘッダ構造におけるe_magic構造が保存しているのは「MZ」というフラグに対応する16進数が0 x 4 d 5 aであり、またpeヘッダには0 x 5045000という値が保存されており、対応する文字は「PE」であり、この2つの条件を満たすファイルだけが正常なPEファイルである.さもないと違います.
DOSヘッダとPEヘッダの取得
PEファイルの開始位置はDOSヘッダ構造であるIMAGE_DOS_HEADER STRUCT、その最初のメンバーはDOSヘッドの標識として、一般的に保存されているのはすべて“MZ”で、その中のe_lfanewは本物のPEヘッダがあるオフセットを保存してDOSヘッダを取得する際に簡単に前の数バイトをこの構造に変換すればよく,PEヘッダをアドレスする際にe_lfanewメンバーにファイルの開始アドレスを加えるとPEヘッダのアドレスが得られる.具体的に対応するコードは以下の通りです.
FileHeader情報とptionalHeader情報の表示
PEヘッダの構造体は以下のように定義される.
この中の2番目の3番目のメンバーはそれぞれFileHeader情報とptionalHeader情報で、残りはこの構造の一部の重要なメンバーを解析して表示しただけです.
この関数ではポインタアドレスによって直接情報を取得します.たとえば、Number OfSections(現在のファイルのセクションテーブル数)、TimeDateStamp(タイムスタンプ情報)、PointerToSymbolTable(シンボルテーブルの所在地のファイルに対するオフセット)、Number OfSymbols(シンボルテーブルの数)、SizeOfOptionalHeader(OptionalHeader構造のサイズ)、Characteristics(ファイルの属性値).属性値を表示するときに、変換関数が指定した値を特定のIDに変換します.変換する必要がある場合は、次のコードを参照してください.
OptionalHeader構造の解析については,現在も単純にその中のデータ構造を印刷するだけであり,その中で最も重要な構造であるDataDirectoryは後の部分で説明されている.
ターゲットファイルの選択
まず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というクラスに移動します.このクラスでは、このような4つの主要なメンバーが定義されています.
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;
}
ロード時には、主に1つのファイルマッピング方式でpeファイルの全体の内容をそのままメモリにコピーし、このファイルハンドル、ファイルマッピングハンドル、ファイルが存在するメモリのヘッダアドレスなどの情報を保存し、アンインストール時にハンドルを閉じ、リソースをクリーンアップする操作を行う.プログラムには、そのファイルがPEファイルであるか否かを判断する操作がある.PEのDOSヘッダ構造におけるe_magic構造が保存しているのは「MZ」というフラグに対応する16進数が0 x 4 d 5 aであり、またpeヘッダには0 x 5045000という値が保存されており、対応する文字は「PE」であり、この2つの条件を満たすファイルだけが正常な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
この中の2番目の3番目のメンバーはそれぞれ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(" "));
}
}
この関数ではポインタアドレスによって直接情報を取得します.たとえば、Number OfSections(現在のファイルのセクションテーブル数)、TimeDateStamp(タイムスタンプ情報)、PointerToSymbolTable(シンボルテーブルの所在地のファイルに対するオフセット)、Number OfSymbols(シンボルテーブルの数)、SizeOfOptionalHeader(OptionalHeader構造のサイズ)、Characteristics(ファイルの属性値).属性値を表示するときに、変換関数が指定した値を特定のIDに変換します.変換する必要がある場合は、次のコードを参照してください.
void CPeFileInfo::GetFileCharacteristics(CString &strCharacter)
{
DWORD dwMachine = 0;
PIMAGE_FILE_HEADER pFileHeader = GetFileHeader();
if (0 != (pFileHeader->Characteristics & IMAGE_FILE_RELOCS_STRIPPED))
{
strCharacter += _T(" \r
");
}
if(0 != (pFileHeader->Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE))
{
strCharacter += _T(" \r
");
}
if (0 != (pFileHeader->Characteristics & IMAGE_FILE_LARGE_ADDRESS_AWARE))
{
strCharacter += _T(" 2GB \r
");
}
if(0 != (pFileHeader->Characteristics & IMAGE_FILE_32BIT_MACHINE))
{
strCharacter += _T(" 32 \r
");
}
if (0 != (pFileHeader->Characteristics & IMAGE_FILE_SYSTEM))
{
strCharacter += _T(" \r
");
}
if (0 != (pFileHeader->Characteristics & IMAGE_FILE_DLL))
{
strCharacter += _T(" dll \r
");
}
if(0 != (pFileHeader->Characteristics & IMAGE_FILE_UP_SYSTEM_ONLY))
{
strCharacter += _T(" ");
}
}
OptionalHeader構造の解析については,現在も単純にその中のデータ構造を印刷するだけであり,その中で最も重要な構造であるDataDirectoryは後の部分で説明されている.