[原创]PE解析工具
【原创】[15Pb培训第三阶段课后小项目]PE解析工具
本工具只能简单的现实PE文件的解析,尚未添加编辑功能,处于未完成阶段.
如文中本文中有错误或者本人理解错误以及不透彻的地方,希望大神们指点一二,在此不胜感激.
下面先贴上工具主界面图片:

一. PE头部
.DOS头
PE文件总体可分为DOS部首,PE文件头,节表,以及节内容四大部分.是以”MZ”(5A4Dh)开始的,用一个64字节大小的IMAGE_DOS_HEADER_STRUCT结构来表示DOS文件头.跟着DOS部首的就是PE文件头,在DOS文件头的e_lfanew字段(也就是文件偏移0x3C)保存着PE文件头的相对文件偏移.
.PE文件头
struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
PE文件头以一个大小为0xF8h的IMAGE_NT_HEADERS结构体表示,结构第一个字段Signature是该文件头的标志,它总是0x4550h(“PE”),可以根据该字段与DOS文件头的e_magic字段来判断一个文件是否是PE文件.第二个字段是一个大小为0x14h字节大小的IMAGE_FILE_HEADER结构体.
struct _IMAGE_FILE_HEADER {
WORD Machine; // 运行平台
WORD NumberOfSections; // 文件节的数目
DWORD TimeDateStamp; // 文件创建时间
DWORD PointerToSymbolTable; // 指向符号表(调试使用)
DWORD NumberOfSymbols; // 符号表中符号个数(调试使用)
WORD SizeOfOptionalHeader; // IMAGE_OPTIONAL_HEADER32结构大小
WORD Characteristics;
}IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
第三字段是一个大小为0xE0h的IMAGE_OPTIONAL_HEADER32的结构体.
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; // 标志
BYTE MajorLinkerVersion; // 链接器主版本号
BYTE MinorLinkerVersion; // 链接器次版本号
DWORD SizeOfCode; // 所有代码区段的总大小
DWORD SizeOfInitializedData; // 所有初始化数据区段的总大小
DWORD SizeOfUninitializedData; // 所有未初始化数据区段的总大小
DWORD AddressOfEntryPoint; // 程序执行入口RVA
DWORD BaseOfCode; // 代码区段起始RVA
DWORD BaseOfData; // 数据区段起始RVA
DWORD ImageBase; // 程序默认装载基址
DWORD SectionAlignment; // 内存中区段的对齐值(一般为1000)
DWORD FileAlignment; // 文件中区段的对齐值(一般为200)
WORD MajorOperatingSystemVersion; // 操作系统主版本号
WORD MinorOperatingSystemVersion; // 操作系统次版本号
WORD MajorImageVersion; // 用户自定义主版本号
WORD MinorImageVersion; // 用户自定义次版本号
WORD MajorSubsystemVersion; // 所需要子系统主版本号
WORD MinorSubsystemVersion; // 所需要子系统次版本号
DWORD Win32VersionValue; // 保留,一般为0
DWORD SizeOfImage; // 映像装入内存后的总尺寸
DWORD SizeOfHeaders; // 所有头部的总大小
DWORD CheckSum; // 映像校验和
WORD Subsystem; // 文件子系统
WORD DllCharacteristics; // 显示DLL特性的标志
DWORD SizeOfStackReserve; // 初始化堆栈大小
DWORD SizeOfStackCommit; // 初始化实际堆栈大小
DWORD SizeOfHeapReserve; // 初始化保留堆栈大小
DWORD SizeOfHeapCommit; // 初始化实际保留堆栈大小
DWORD LoaderFlags; // 与调试相关,默认为0
DWORD NumberOfRvaAndSizes; // 数据目录表的项数
// 数据目录数组
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
IMAGE_DATA_DIRECTORY结构体定义如下:
struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; // 指向的RVA
DWORD Size; // 数据的大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
实现逻辑:由于本工具用的是MFC实现的,实现起来非常简单.
1. 给每个文本框定义一个cstring类型的变量.
2. 直接把需要显示的成员格式化到对应的变量.
3. 最后用UpdateData(FALSE)更新到界面就OK.
下面放上实现的主要代码:
VOID CMFC_PEditor_Dlg::ShowPE_Info(PVOID lpImage)
{
if (nullptr == lpImage)
{
MessageBox(_T("文件地址为空"),_T("错误"));
PostMessage(WM_CLOSE,NULL,NULL);
return;
}
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpImage;
PIMAGE_NT_HEADERS32 pNT32 = (PIMAGE_NT_HEADERS32)((LONG)lpImage+pDos->e_lfanew);
PIMAGE_FILE_HEADER pFileHead = &pNT32->FileHeader;
// 把文件信息显示到窗口对应的控件上
m_strMachine.Format(_T("%04X"),pFileHead->Machine);
m_strNumberOfSections.Format(_T("%04X"),pFileHead->NumberOfSections);
m_strTimeDateStamp.Format(_T("%p"),pFileHead->TimeDateStamp);
m_strPointerToSymbolTable.Format(_T("%p"),pFileHead->PointerToSymbolTable);
m_strNumberOfSymbols.Format(_T("%p"),pFileHead->NumberOfSymbols);
m_strSizeOfOptionalHeader.Format(_T("%04X"),pFileHead->SizeOfOptionalHeader);
m_strCharacteristics.Format(_T("%p"),pFileHead->Characteristics);
// 显示扩展头部信息
PIMAGE_OPTIONAL_HEADER32 pOptionalHead = &pNT32->OptionalHeader;
m_strBaseOfCode.Format(_T("%p"),pOptionalHead->BaseOfCode);
m_strBaseOfData.Format(_T("%p"),pOptionalHead->BaseOfData);
m_strSizeOfImage.Format(_T("%p"),pOptionalHead->SizeOfImage);
m_strSizeOfHeader.Format(_T("%p"),pOptionalHead->SizeOfHeaders);
m_strSectionAlignment.Format(_T("%p"),pOptionalHead->SectionAlignment);
m_strFileAlignment.Format(_T("%p"),pOptionalHead->FileAlignment);
m_strSubSystem.Format(_T("%p"),pOptionalHead->Subsystem);
// 文件信息
m_strAddrOfEntryPoint.Format(_T("%p"),pOptionalHead->AddressOfEntryPoint);
m_strImageBase.Format(_T("%p"),pOptionalHead->ImageBase);
UpdateData(FALSE);
}
二. 节表
显示的界面如下:

.节表(区段表)
节表是若干个大小为0x28h的IMAGE_SECTION_HEADER结构体组成的数组,数组的无素个数取决于节的个数,一个IMAGE_SECTION_HEADER结构对应一个节.
struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 8字节的区段名
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc; // 区段大小
DWORD VirtualAddress; // 区段的RVA
DWORD SizeOfRawData; // 在文件中对齐后的大小
DWORD PointerToRawData; // 区段在文件中的偏移
DWORD PointerToRelocations; // 在obj文件中使用,重定位的偏移
DWORD PointerToLinenumbers; // 行号表偏移(调试使用)
WORD NumberOfRelocations; // 在obj文件使用,重定位表项目数
WORD NumberOfLinenumbers; // 行号表中行号的数目
DWORD Characteristics; // 区段属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
文件偏移(FileOffset) = 虚拟相对偏移(RVA) – 所在节的虚拟相对偏移 + 对应节的文件偏移
实现逻辑:由于节表是由IMAGE_SECTION_HEADER结构组成的数组,只要定义一个指针
指向首个IMAGE_SECTION_HEADER结构,就可以循环的显示出每个节的信息了.
实现的主要代码:
1. 在初始化函数中初始化控件样式及对应项
m_lisSection.SetExtendedStyle(LVS_EX_FULLROWSELECT );
TCHAR strTitle[6][10] = {_T("节名"),_T("虚拟大小"),_T("虚拟偏移"),
_T("原始大小"),_T("原始偏移"),_T("特征值")};
for (int i = 0; i < 6; i++)
{
m_lisSection.InsertColumn(i,strTitle[i],0,80);
}
2.
VOID CSection::ShowSection(CONST PVOID lpImage)
{
if (nullptr == lpImage)
{
return;
}
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpImage;
PIMAGE_NT_HEADERS32 pNT32 = (PIMAGE_NT_HEADERS32)((LONG)lpImage+pDos->e_lfanew);
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNT32);// 取得第一个节表所在位置
DWORD dwIndex = 0; // 子项的索引值(可视为引导)
LVITEM lvItem = {0};
lvItem.mask = LVIF_TEXT|LVIF_IMAGE|LVIF_PARAM|LVIF_STATE;
for (int i = 0; i < pNT32->FileHeader.NumberOfSections; i++)
{
WCHAR wTemp[30];
lvItem.iItem = dwIndex;
MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,(char*)pSection->Name,8,wTemp,30);
int n = GetLastError();
lvItem.pszText = wTemp;
m_lisSection.InsertItem(&lvItem);
CString strbuff;
strbuff.Format(_T("%p"),pSection->Misc);
m_lisSection.SetItemText(dwIndex,1,strbuff);
strbuff.Format(_T("%p"),pSection->VirtualAddress);
m_lisSection.SetItemText(dwIndex,2,strbuff);
strbuff.Format(_T("%p"),pSection->SizeOfRawData);
m_lisSection.SetItemText(dwIndex,3,strbuff);
strbuff.Format(_T("%p"),pSection->PointerToRawData);
m_lisSection.SetItemText(dwIndex,4,strbuff);
strbuff.Format(_T("%p"),pSection->Characteristics);
m_lisSection.SetItemText(dwIndex,5,strbuff);
dwIndex++;
pSection++;
}
数据目录表
实现逻辑跟前面的主界面一样.

实现代码:
VOID CDirectory::ShowDirectory()
{
if (nullptr == m_lpFileImage)
{
MessageBox(_T("文件地址为空"),_T("错误"));
PostMessage(WM_CLOSE,NULL,NULL);
return ;
}
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)m_lpFileImage;
PIMAGE_NT_HEADERS32 pNT32 = (PIMAGE_NT_HEADERS32)((LONG)m_lpFileImage+pDos->e_lfanew);
// pDir指向目录表
PIMAGE_DATA_DIRECTORY pDir = pNT32->OptionalHeader.DataDirectory;
// 输出目录
m_strExport_RVA.Format(_T("%p"),pDir->VirtualAddress);
m_strExport_Size.Format(_T("%p"),pDir->Size);
pDir++;
// 输入目录
m_strImport_RVA.Format(_T("%p"),pDir->VirtualAddress);
m_strImport_Size.Format(_T("%p"),pDir->Size);
pDir++;
// 资源目录
m_strResources_RVA.Format(_T("%p"),pDir->VirtualAddress);
m_strReception_Size.Format(_T("%p"),pDir->Size);
pDir++;
// 异常目录
m_strException_RVA.Format(_T("%p"),pDir->VirtualAddress);
m_strException_Size.Format(_T("%p"),pDir->Size);
pDir++;
// 安全目录
m_strSecurity_RVA.Format(_T("%p"),pDir->VirtualAddress);
m_strSecurity_Size.Format(_T("%p"),pDir->Size);
pDir++;
// 重定位目录
m_strBase_RVA.Format(_T("%p"),pDir->VirtualAddress);
m_strBase_Size.Format(_T("%p"),pDir->Size);
pDir++;
// 调试目录
m_strDebug_RVA.Format(_T("%p"),pDir->VirtualAddress);
m_strDebug_Size.Format(_T("%p"),pDir->Size);
pDir++;
// 版权目录
m_strCopyright_RVA.Format(_T("%p"),pDir->VirtualAddress);
m_strCopyright_Size.Format(_T("%p"),pDir->Size);
pDir++;
// 全局指针目录
m_strGlobal_RVA.Format(_T("%p"),pDir->VirtualAddress);
m_strGlobal_Size.Format(_T("%p"),pDir->Size);
pDir++;
// TLS目录
m_strTLS_RVA.Format(_T("%p"),pDir->VirtualAddress);
m_strTLS_Size.Format(_T("%p"),pDir->Size);
pDir++;
// 载入配置目录
m_strLoadConfiguration_RVA.Format(_T("%p"),pDir->VirtualAddress);
m_strLoadConfiguration_Size.Format(_T("%p"),pDir->Size);
pDir++;
// 绑定输入目录
m_strBoundImport_RVA.Format(_T("%p"),pDir->VirtualAddress);
m_strBoundImport_Size.Format(_T("%p"),pDir->Size);
pDir++;
// IAT目录
m_strIAD_RVA.Format(_T("%p"),pDir->VirtualAddress);
m_strIAT_Size.Format(_T("%p"),pDir->Size);
pDir++;
// 延迟导入目录
m_strDelayImport_RVA.Format(_T("%p"),pDir->VirtualAddress);
m_strDelayImport_Size.Format(_T("%p"),pDir->Size);
pDir++;
// COM目录
m_strCOM_RVA.Format(_T("%p"),pDir->VirtualAddress);
m_strCOM_Size.Format(_T("%p"),pDir->Size);
pDir++;
// 保留
m_strTemp_RVA.Format(_T("%p"),pDir->VirtualAddress);
m_strTemp_Size.Format(_T("%p"),pDir->Size);
UpdateData(FALSE);
}. 输出表:在数据目录数组中,第一个结构体表示的就是输出表.

struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; // 保留,恒为0
DWORD TimeDateStamp; // 时间戳
WORD MajorVersion; // 主版本号
WORD MinorVersion; // 次版本号
DWORD Name; // 模块名
DWORD Base; // 索引基址
DWORD NumberOfFunctions; // 导出表中成员的总数
DWORD NumberOfNames; // 导出表中,按函数名导出的个数
DWORD AddressOfFunctions; // 导出地址表(RVA)
DWORD AddressOfNames; // 导出函数名称表
DWORD AddressOfNameOrdinals; // 指向导出序列号数组
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
代码实现如下 :
VOID CExport::ShowExport(CONST LPVOID lpImage)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpImage;
PIMAGE_NT_HEADERS32 pNT32 = (PIMAGE_NT_HEADERS32)((LONG)lpImage+pDos->e_lfanew);
// pExportdir指向数据目录表中的输出表目录
PIMAGE_DATA_DIRECTORY pExportDir = pNT32->OptionalHeader.DataDirectory;
if (0 == pExportDir->VirtualAddress)
{
MessageBox(_T("此文件不存在输出表"),_T("提示"));
PostMessage(WM_CLOSE,NULL,NULL);
return;
}
// 获得输出表的Offset;
DWORD dwExportOfffset = m_pRVAToOffset->RVAToOffset(lpImage,pExportDir->VirtualAddress);
// pExport指向输出表
PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)((DWORD)lpImage+dwExportOfffset);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 输出表信息更新到窗口
m_strOffset.Format(_T("%p"),dwExportOfffset); // 输出表偏移
m_strCharacteristics.Format(_T("%p"),pExport->Characteristics); // 特征值
m_strBase.Format(_T("%p"),pExport->Base); // 索引基数
m_strName.Format(_T("%p"),pExport->Name); // 模块名RVA
m_strNumberOfFun.Format(_T("%p"),pExport->NumberOfFunctions); // 导出表成员总数
m_strNumberOfName.Format(_T("%p"),pExport->NumberOfNames); // 以函数名导出的个数
m_strAddrFun.Format(_T("%p"),pExport->AddressOfFunctions); // 导出函数地址表
m_strAddrFunName.Format(_T("%p"),pExport->AddressOfNames); // 函数名称地址表
m_strAddrNum.Format(_T("%p"),pExport->AddressOfNameOrdinals); // 导出序列号数组
// 把模块名RVA转换成Offset再加上文件所在地址,得到模块名的Offset
PCHAR pstrName = (PCHAR)lpImage + m_pRVAToOffset->RVAToOffset(lpImage,pExport->Name);
m_strSzName = pstrName; // 模块名称字符串
UpdateData(FALSE);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
PDWORD pEAT = (PDWORD)((DWORD) // 导出表RVA转换成Offset再加上文件地址等于导出地址表
lpImage + m_pRVAToOffset->RVAToOffset(lpImage,pExport->AddressOfFunctions));
PDWORD pENT = (PDWORD)((DWORD) // 函数名称RVA转换成Offset再加上文件地址等于函数名称地址表
lpImage + m_pRVAToOffset->RVAToOffset(lpImage,pExport->AddressOfNames));
PWORD pEIT = (PWORD)((DWORD) // 函数序号RVA转换成Offset再加上文件地址等于函数序号地址表
lpImage + m_pRVAToOffset->RVAToOffset(lpImage,pExport->AddressOfNameOrdinals));
LVITEM lvItem = {0};
lvItem.mask = LVIF_TEXT|LVIF_IMAGE|LVIF_PARAM|LVIF_STATE;
CString strbuff;
DWORD dwOffset = 0;
int j = 0;
// 遍历整个导出表中的导出函数地址
for (DWORD i=0; i<pExport->NumberOfFunctions; i++)
{
// 如果该地址为nullptr,则遍历下一个地址
if (!pEAT[i])
{
continue;
}
// 遍历以名称导出的函数
for (DWORD dwIndex = 0; dwIndex<pExport->NumberOfFunctions; dwIndex++)
{
// 比较序号是否有函数序号与其对应
if (pEIT[dwIndex] == i)
{
// 如果序号数组中的序号与函数名的序号相符
lvItem.iItem = j;
TCHAR szName[8] = {0};
wsprintf(szName,_T("%04X"),i + pExport->Base);
lvItem.pszText = szName;
m_lisExport.InsertItem(&lvItem); // 插入序号
strbuff.Format(_T("%p"),pEAT[j]);
m_lisExport.SetItemText(j,1,strbuff); // 插入RVA
// 把函数RVA转换成Offset
dwOffset = m_pRVAToOffset->RVAToOffset(lpImage,pEAT[j]);
strbuff.Format(_T("%p"),dwOffset);
m_lisExport.SetItemText(j,2,strbuff); // 插入偏移
pstrName = (PCHAR)lpImage + m_pRVAToOffset->RVAToOffset(lpImage,pENT[dwIndex]);
USES_CONVERSION;
strbuff = A2W(pstrName);
m_lisExport.SetItemText(j,3,strbuff); // 插入函数名
j++;
break;
}
// 如果遍历完整个序号数组,都没找到与函数相符的序号
else if ( dwIndex == pExport->NumberOfFunctions-1 )
{
// 该序号没有对应的函数名,
lvItem.iItem = j;
TCHAR szName[8] = {0};
wsprintf(szName,_T("%04X"),i + pExport->Base);
lvItem.pszText = szName;
m_lisExport.InsertItem(&lvItem); // 插入序号
strbuff.Format(_T("%p"),pEAT[j]);
m_lisExport.SetItemText(j,1,strbuff); // 插入RVA
// 把函数RVA转换成Offset
dwOffset = m_pRVAToOffset->RVAToOffset(lpImage,pEAT[j]);
strbuff.Format(_T("%p"),dwOffset);
m_lisExport.SetItemText(j,2,strbuff); // 插入偏移
strbuff.Format(_T("%s"),_T("-")); // 用"-"表示无对应的函数名
m_lisExport.SetItemText(j,3,strbuff); // 插入函数名
j++;
}
}
}
}需要注意的是:在遍历导出成员时,要以NumberOfFunctions为条件来遍历,不能以NumberOfNames作为条件来遍历;
. 输入表
struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;
} DUMMYUNIONNAME; // 指向输入名称表的RVA(INT)
DWORD TimeDateStamp; // 时间标识
DWORD ForwarderChain; // 转发链,如果不转发则此值为0
DWORD Name; // 指向映像文件的名字
DWORD FirstThunk; // 指向输入地址表(IAT)的RVA
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
此结构中的OriginalFirstThunk字段跟FirstThunk这两个字段很容易会混淆,因为在文件中,它们都指向导入函数地址表.以下两图片很容易能看明白结构各字段的意思.
声明:以下两张图片来自 百度图片

上图是导入表在文件中的情况, OriginalFirstThunk跟OriginalFirstThunk最终都指向一个IMAGE_IMPORT_BY_MAME结构数组,这结构表示的是函数名与序号;当程序加载到内存后,如下图所示, OriginalFirstThunk字段指向一个IAT表,此表保存的是函数的地址.


导入表是以一个空的IMAGE_IMPORT_DESCRIPTOR结构结尾的IMAGE_IMPORT_DESCRIPTOR结构数组,可能用while循环遍历所有导入模块
关键代码:
VOID CImport::ShowImport()
{
// 1. 获取基本信息
// 1.1 获取DOS/NT头
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)m_lpFileImage;
PIMAGE_NT_HEADERS32 pNT32 = (PIMAGE_NT_HEADERS32)((LONG)m_lpFileImage+pDos->e_lfanew);
// 1.2 获取数据目录表
PIMAGE_DATA_DIRECTORY pDir = (PIMAGE_DATA_DIRECTORY)pNT32->OptionalHeader.DataDirectory;
// 1.3 获取导入表的数据目录
PIMAGE_DATA_DIRECTORY pDataDir = pDir+IMAGE_DIRECTORY_ENTRY_IMPORT;
// 1.4 获取导入表
m_pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)
((DWORD)m_lpFileImage + m_pRVAToOffset->RVAToOffset(m_lpFileImage, pDataDir->VirtualAddress));
PIMAGE_IMPORT_DESCRIPTOR pImport = m_pImportTable;
LVITEM lvItem = {0};
lvItem.mask = LVIF_TEXT|LVIF_IMAGE|LVIF_PARAM|LVIF_STATE;
// 2. 循环遍历导入表
DWORD dwItem = 0;
m_lisImport.DeleteAllItems();
while ( pImport->Name )
{
// 2.1 获取导入项DLL名称
PCHAR pszDllName = (PCHAR)((DWORD)
m_lpFileImage + m_pRVAToOffset->RVAToOffset(m_lpFileImage, pImport->Name));
lvItem.iItem = dwItem; // 行号
USES_CONVERSION;
CString szName = A2W(pszDllName);
lvItem.pszText = szName.GetBuffer();
m_lisImport.InsertItem(&lvItem); // 插入DLL名称
szName.Format(_T("%p"),pImport->OriginalFirstThunk);
m_lisImport.SetItemText(dwItem,1,szName); // 插入OriginalFirstThunk
szName.Format(_T("%p"),pImport->TimeDateStamp);
m_lisImport.SetItemText(dwItem,2,szName); // 插入时间标识
szName.Format(_T("%p"),pImport->ForwarderChain);
m_lisImport.SetItemText(dwItem,3,szName); // 插入ForwarderChain
szName.Format(_T("%p"),pImport->Name);
m_lisImport.SetItemText(dwItem,4,szName); // 插入名称
szName.Format(_T("%p"),pImport->FirstThunk);
m_lisImport.SetItemText(dwItem,5,szName); // 插入FirstThunk
pImport++;
dwItem++;
}
}然后在显示list控件添加一个NM_CLICK消息响应函数,用来响应鼠标点击,该函数已自动的把包含信息计算好了
响应点击的代码如下:
void CImport::OnNMClickListImport(NMHDR *pNMHDR, LRESULT *pResult)
{
// TODO: 在此添加控件通知处理程序代码
// 保存参数
m_pNMHDR = pNMHDR;
// 清空控件内容
m_lisThunk.DeleteAllItems();
LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
if (pResult)
{// 如果是系统调用,保存选项
m_dwItem = pNMItemActivate->iItem;
}
// 获取选中模块的IAT
DWORD temp = (DWORD)m_lpFileImage + m_pRVAToOffset->RVAToOffset(m_lpFileImage,
m_pImportTable[m_dwItem].FirstThunk);
PIMAGE_THUNK_DATA32 pIAT = (PIMAGE_THUNK_DATA32)temp;
PIMAGE_THUNK_DATA32 pTemp;
if (!(m_pImportTable[m_dwItem].OriginalFirstThunk))
{
m_btnIsThunk.EnableWindow(FALSE);
}
if (!m_bIsThunk && m_pImportTable[m_dwItem].OriginalFirstThunk)
{
// OriginalFirstThunk不为空且m_bIsThunk为空,显示OriginalFirstThunk字段的内容
pTemp = (PIMAGE_THUNK_DATA32)m_pImportTable[m_dwItem].OriginalFirstThunk;
}
else
{
// 否则,显示FirstThunk字段内容
pTemp = (PIMAGE_THUNK_DATA32)m_pImportTable[m_dwItem].FirstThunk;
}
LVITEM lvItem = {0};
lvItem.mask = LVIF_TEXT|LVIF_IMAGE|LVIF_PARAM|LVIF_STATE;
DWORD dwItem = 0;
CString szName;
// 遍历选中模块的所有导入函数
while ( pIAT->u1.Ordinal )
{
lvItem.iItem = dwItem; // 行号
m_lisThunk.InsertItem(&lvItem);
szName.Format(_T("%p"),pTemp);
m_lisThunk.SetItemText(dwItem,0,szName);
szName.Format(_T("%p"),m_pRVAToOffset->RVAToOffset(m_lpFileImage,(DWORD)pTemp));
m_lisThunk.SetItemText(dwItem,1,szName);
szName.Format(_T("%p"),pIAT->u1.Ordinal);
m_lisThunk.SetItemText(dwItem,2,szName);
// 指向函数名结构体;
PIMAGE_IMPORT_BY_NAME pName = (PIMAGE_IMPORT_BY_NAME)
((DWORD)m_lpFileImage + m_pRVAToOffset->RVAToOffset(m_lpFileImage,pIAT->u1.AddressOfData));
szName.Format(_T("%04X"),pName->Hint);
m_lisThunk.SetItemText(dwItem,3,szName);
USES_CONVERSION;
//szName = A2W(pName->Name);
char n[50];
memcpy(n,pName->Name,40);
szName=A2W(n);
szName.Format(_T("%s"),szName);
m_lisThunk.SetItemText(dwItem,4,szName);
pIAT++;
pTemp++;
dwItem++;
}
*pResult = 0;
}
.资源
下图来自百度图片

如图上所示:
在PE文件中,资源一般分为三层,第一层都由一个PIMAGE_RESOURCE_DIRECTORY结构,和紧跟其后的一个PIMAGE_RESOURCE_DIRECTORY_ENTRY结构数组表示. PIMAGE_RESOURCE_DIRECTORY_ENTRY结构的每个字段在不同的层次表示的意思也不相同.
struct _IMAGE_RESOURCE_DIRECTORY {
DWORD Characteristics; // 资源属性标识
DWORD TimeDateStamp; // 资源建立的时间
WORD MajorVersion; // 资源主版本号
WORD MinorVersion; // 资源子版本号
WORD NumberOfNamedEntries; // 资源名称条目个数
WORD NumberOfIdEntries; // 资源ID条目个数
// IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
union {
struct {
DWORD NameOffset:31; // 资源名偏移
DWORD NameIsString:1; // 资源名为字符串
} DUMMYSTRUCTNAME;
DWORD Name; // 资源/语言类型
WORD Id; // 资源数字ID
} DUMMYUNIONNAME;
union {
DWORD OffsetToData; // 数据偏移地址
struct {
DWORD OffsetToDirectory:31; // 子目录偏移地址
DWORD DataIsDirectory:1; // 数据为目录
} DUMMYSTRUCTNAME2;
} DUMMYUNIONNAME2;
} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;

实现主要代码:
VOID CRecDir::ShowResDir(PIMAGE_RESOURCE_DIRECTORY_ENTRY pDirEntry,HTREEITEM hParent,DWORD dwLevel)
{
// 插入节点对应的信息结构
TREE_TIME_INFO stcTII = {hParent,dwLevel,pDirEntry};
// 节点
HTREEITEM hItem = NULL;
// 自定义名称字符串指针
PIMAGE_RESOURCE_DIR_STRING_U pNameString = nullptr;
// 树节点名称
CString strItemName;
// 节点名称
if(pDirEntry->NameIsString) // 自定义资源名称
{
// 自定义名称字符串偏移,它是相对于资源头的偏移
pNameString = (PIMAGE_RESOURCE_DIR_STRING_U)((PBYTE)m_pRoot + pDirEntry->NameOffset);
strItemName = pNameString->NameString;
// 截取字符串
strItemName = strItemName.Left(pNameString->Length);
}
else // 系统标准资源名称
{
if (1 == dwLevel) // 只对根节点有效
{
strItemName = m_szResourceType[pDirEntry->Name];
}
else // 无标准名称
{
strItemName.Format(_T("%d"),pDirEntry->Id);
}
}
// 插入树节点
if (NULL == hParent)
{
hItem = m_TreeResource.InsertItem(strItemName,TVI_ROOT); // 默认是根节点
}
else
{
hItem = m_TreeResource.InsertItem(strItemName,hParent); // 插入父节点是hParent
}
// 如果还有下一层目录,递归调用
if (pDirEntry->DataIsDirectory)
{
PIMAGE_RESOURCE_DIRECTORY pDir = (PIMAGE_RESOURCE_DIRECTORY)
((DWORD)m_pRoot + pDirEntry->OffsetToDirectory);
// 获取当前目录下的PIMAGE_RESOURCE_DIRECTORY_ENTRY结构数组
PIMAGE_RESOURCE_DIRECTORY_ENTRY pEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pDir + 1);
// 当前PIMAGE_RESOURCE_DIRECTORY_ENTRY结构数组元素个数
DWORD dwCount = pDir->NumberOfIdEntries + pDir->NumberOfNamedEntries;
// 循环处理PIMAGE_RESOURCE_DIRECTORY_ENTRY结构数组的每个元素
for (DWORD i = 0; i < dwCount; i++,pEntry++)
{
ShowResDir(pEntry,hItem,dwLevel + 1);
}
}
// 将插入节点的句柄作为Key值,相关信息作为Value存入到集合中
m_mapTreeItem.insert(pair<HTREEITEM,TREE_TIME_INFO>(hItem,stcTII));
}
资源用Tree Control控件来显示,同样的也添加一个TVN_SELCHANGED消息响应函数,用来响应选中的项
void CRecDir::OnTvnSelchangedTree1(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
// TODO: 在此添加控件通知处理程序代码
HTREEITEM hSelcItem = m_TreeResource.GetSelectedItem();
TREE_TIME_INFO stcTII = m_mapTreeItem[hSelcItem];
// 选中的目录信息
PIMAGE_RESOURCE_DIRECTORY pSelcDir = nullptr;
PIMAGE_RESOURCE_DIRECTORY_ENTRY pDirEntry = stcTII.pEntry;
CString strTemp;
if (pDirEntry->DataIsDirectory) // 点击的是目录
{
pSelcDir = (PIMAGE_RESOURCE_DIRECTORY)((DWORD)m_pRoot + pDirEntry->OffsetToDirectory);
}
else // 点击的是文件条目
{
// 获取叶子节点父节点的PIMAGE_RESOURCE_DIRECTORY_ENTRY
PIMAGE_RESOURCE_DIRECTORY_ENTRY pParentDirEntry = m_mapTreeItem[stcTII.hParent].pEntry;
pSelcDir = (PIMAGE_RESOURCE_DIRECTORY)((DWORD)m_pRoot + pParentDirEntry->OffsetToDirectory);
// 获取PIMAGE_RESOURCE_DATA_ENTRY
PIMAGE_RESOURCE_DATA_ENTRY pDataEntry = (PIMAGE_RESOURCE_DATA_ENTRY)
((DWORD)m_pRoot + stcTII.pEntry->OffsetToData);
strTemp.Format(_T("%p"),pDataEntry->Size);
m_strSelcSizeCount = strTemp;
strTemp.Format(_T("%p"),(DWORD)m_pRoot + m_pRVAToOffset->RVAToOffset(m_lpFileImage,pDataEntry->OffsetToData));
m_strSelcOffsetCount = strTemp;
strTemp.Format(_T("%p"),pDataEntry->OffsetToData);
m_strSelcRVACount = strTemp;
}
// 显示选中目录
strTemp.Format(_T("%04X"),pSelcDir->NumberOfIdEntries);
m_strSelcIDCount = strTemp;
strTemp.Format(_T("%04X"),pSelcDir->NumberOfNamedEntries);
m_strSelcNameCount = strTemp;
UpdateData(FALSE);
*pResult = 0;
}.重定位
基址重定位是由多个IMAGE_BASE_RELOCATION结构组成的,每个结构对应一个分页,也就是说每个结构只负责0x1000大小的重定位数据.
struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress; // 需重定位数据的起始RVA
DWORD SizeOfBlock; // 描述本分页重定位信息数据的总大小(本结构加上需要重定位的数据)
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
实现的主要代码:
VOID CBaseReloc::AddRelocBlocks()
{
if (nullptr == m_lpFileImage)
{
MessageBox(_T("文件地址为空"),_T("错误"));
PostMessage(WM_CLOSE,NULL,NULL);
return ;
}
// 1. 获取基本信息
// 1.1 获取DOS/NT头
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)m_lpFileImage;
PIMAGE_NT_HEADERS32 pNT32 = (PIMAGE_NT_HEADERS32)((LONG)m_lpFileImage+pDos->e_lfanew);
// 1.2 获取数据目录表
PIMAGE_DATA_DIRECTORY pDir = (PIMAGE_DATA_DIRECTORY)pNT32->OptionalHeader.DataDirectory;
// 1.3 获取重定位表的数据目录
PIMAGE_DATA_DIRECTORY pRelocDir = pDir+IMAGE_DIRECTORY_ENTRY_BASERELOC;
// 1.4 获取重定位表
PIMAGE_BASE_RELOCATION pReloc = (PIMAGE_BASE_RELOCATION)((DWORD)m_lpFileImage +
m_pRVAToOffset->RVAToOffset(m_lpFileImage, pRelocDir->VirtualAddress));
BLOCK_ITEM stcBi = {0};
// 一个重定位表的项目数
DWORD dwSumItem = 0;
//
DWORD dwIndex = 0;
DWORD dwSectionIndex = 0;
CString strTemp;
CString strSectionName;
LVITEM lvItem = {0};
lvItem.mask = LVIF_TEXT|LVIF_IMAGE|LVIF_PARAM|LVIF_STATE;
//lvItem.state = 0; // 子项的状态
//lvItem.stateMask = 0; // 状态有效的屏蔽位
//lvItem.iImage = 0; // 与列表框关联的指定图像的索引值
//lvItem.iSubItem = 0; // 子项的索引值(可以视为列号)
while (pReloc->SizeOfBlock) // 整个重定位块的大小
{
// 当前重定位结构需要重定位项的个数
dwSumItem = (pReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
stcBi.dwSumItem = dwSumItem;
stcBi.pReloc = pReloc;
// m_lisRelocBlocks.InsertItem(&lvItem);
lvItem.iItem = dwIndex;
strTemp.Format(_T("%d"),dwIndex + 1);
lvItem.pszText = strTemp.GetBuffer();
m_lisRelocBlocks.InsertItem(&lvItem); // 插入一行跟索引
if(m_pRVAToOffset->GetRVASectionName(pReloc->VirtualAddress,strSectionName,dwSectionIndex))
{
strTemp.Format(_T("%d(\"%s\")"),dwSectionIndex,strSectionName);
m_lisRelocBlocks.SetItemText(lvItem.iItem,1,strTemp);
}
strTemp.Format(_T("%p"),pReloc->VirtualAddress);
m_lisRelocBlocks.SetItemText(lvItem.iItem,2,strTemp);
strTemp.Format(_T("%0Xh/ %dd"),dwSumItem,dwSumItem);
m_lisRelocBlocks.SetItemText(lvItem.iItem,3,strTemp);
m_vecBlockItem.push_back(stcBi); // 保存
pReloc = (PIMAGE_BASE_RELOCATION)((DWORD)pReloc + pReloc->SizeOfBlock);
dwIndex++;
}
}响应函数如下:
void CBaseReloc::OnNMClickListRelocBlocks(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
// TODO: 在此添加控件通知处理程序代码
if (nullptr == m_lpFileImage)
{
MessageBox(_T("文件地址为空"),_T("错误"));
PostMessage(WM_CLOSE,NULL,NULL);
return ;
}
PIMAGE_NT_HEADERS32 pNtHead = (PIMAGE_NT_HEADERS32)
(((PIMAGE_DOS_HEADER)m_lpFileImage)->e_lfanew + (DWORD)m_lpFileImage);
// 清空控件内容
m_lisReloc_BlockItem.DeleteAllItems();
DWORD dwIndex = pNMItemActivate->iItem;
CString strTemp;
LVITEM lvItem = {0};
lvItem.mask = LVIF_TEXT|LVIF_IMAGE|LVIF_PARAM|LVIF_STATE;
// lvItem.state = 0; // 子项的状态
// lvItem.stateMask = 0; // 状态有效的屏蔽位
// lvItem.iImage = 0; // 与列表框关联的指定图像的索引值
// lvItem.iSubItem = 0; // 子项的索引值(可以视为列号)
BLOCK_ITEM stcBi = m_vecBlockItem[dwIndex];
// 重定位起始RVA
DWORD dwStartRVA = stcBi.pReloc->VirtualAddress;
// DWORD dwFOA = 0;
DWORD dwRVA = 0;
PTYPE_OFFSET pTypeOffset = (PTYPE_OFFSET)(stcBi.pReloc + 1);
for (DWORD i = 0; i < stcBi.dwSumItem; i++,pTypeOffset++)
{
lvItem.iItem = i;
m_lisReloc_BlockItem.InsertItem(&lvItem);
strTemp.Format(_T("%d"),i);
m_lisReloc_BlockItem.SetItemText(lvItem.iItem,0,strTemp);
dwRVA = dwStartRVA + pTypeOffset->Offset;
strTemp.Format(_T("%p"),dwRVA);
m_lisReloc_BlockItem.SetItemText(lvItem.iItem,1,strTemp);
strTemp.Format(_T("%p"),m_pRVAToOffset->RVAToOffset(dwRVA));
m_lisReloc_BlockItem.SetItemText(lvItem.iItem,2,strTemp);
if (IMAGE_REL_BASED_HIGHLOW == pTypeOffset->Type)
{
strTemp.Format(_T("HIGHLOW (3)"));
}
else
{
strTemp.Format(_T("Unknow"));
}
m_lisReloc_BlockItem.SetItemText(lvItem.iItem,3,strTemp);
// 取出需要重定位的地址
DWORD dwRelocAddr = *((PDWORD)(m_pRVAToOffset->RVAToOffset(dwRVA) + (DWORD)m_lpFileImage));
strTemp.Format(_T("%p"),dwRelocAddr);
m_lisReloc_BlockItem.SetItemText(lvItem.iItem,4,strTemp);
strTemp.Empty();
CString strByte;
//
PBYTE pData = (PBYTE)(m_pRVAToOffset->RVAToOffset(dwRelocAddr - pNtHead->OptionalHeader.ImageBase) + (DWORD)m_lpFileImage);
for (DWORD j = 0; j < 0x10; j++,pData++)
{
strByte.Format(_T("%02X "),*pData);
strTemp += strByte;
}
m_lisReloc_BlockItem.SetItemText(lvItem.iItem,5,strTemp);
}
*pResult = 0;
}
