zoukankan      html  css  js  c++  java
  • 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("*.**.*");
        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("文件中不存在重定位信息
    ");
        }
        if(0 != (pFileHeader->Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE))
        {
            strCharacter += _T("文件可执行
    ");
        }
    
        if (0 != (pFileHeader->Characteristics & IMAGE_FILE_LARGE_ADDRESS_AWARE))
        {
            strCharacter += _T("可以处理大于2GB内容
    ");
        }
        if(0 != (pFileHeader->Characteristics & IMAGE_FILE_32BIT_MACHINE))
        {
            strCharacter += _T("目标平台为32位机器
    ");
        }
    
        if (0 != (pFileHeader->Characteristics & IMAGE_FILE_SYSTEM))
        {
            strCharacter += _T("该文件是系统文件
    ");
        }
        if (0 != (pFileHeader->Characteristics & IMAGE_FILE_DLL))
        {
            strCharacter += _T("该文件是dll文件
    ");
        }
        if(0 != (pFileHeader->Characteristics & IMAGE_FILE_UP_SYSTEM_ONLY))
        {
            strCharacter += _T("该程序只能运行在单核处理器上");
        }
    }

    对于OptionalHeader结构的解析,目前也只是简单的对它其中的数据结构进行打印,它里面最重要的结构DataDirectory留着在后面的部分进行说明。

  • 相关阅读:
    POJ1094查分约束,判断关系是否唯一
    POJ1087DFS+匈牙利或者DINIC
    POJ1087DFS+匈牙利或者DINIC
    #Leetcode# 34. Find First and Last Position of Element in Sorted Array
    #Leetcode# 18. 4Sum
    #Leetcode# 16. 3Sum Closest
    #Leetcode# 15. 3Sum
    #Leetcode# 42. Trapping Rain Water
    #Leetcode# 63. Unique Paths II
    #Leetcode# 62. Unique Paths
  • 原文地址:https://www.cnblogs.com/lanuage/p/7725694.html
Copyright © 2011-2022 走看看