欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]PE解析工具

[原创]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;

}

 

上传的附件 QQ截图20131203084529.png
QQ截图20131203093659.png
QQ截图20131203102027.png
QQ截图20131203102854.png
30152426-6784398a9c534a79bb64f584b4a9e63f.jpg
6c224f4a20a44623463c7cf89822720e0df3d7b2.png
QQ截图20131203104809.png
23224356-e984fe1462ed48af9e325c2d316ce4a7.jpg
QQ截图20131203144205.png
QQ截图20131203143957.png

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功