微信公众号:计算机与网络安全ID:Computer-network编写一个PE查看器并不是什么复杂的事情,只要根据PE结构一步步分析.下面简单解析几个字段的内容并显示sectiontable的信息,其余内容只需稍作修改即可。PE查看器界面如图1所示。图1PE查看器分析记事本程序PE查看器界面设置如图1所示,但可以根据个人喜好进行设置。编写PE查看器的步骤是打开文件并创建文件内存映像,判断文件是否为PE文件并获取PE格式相关结构的指针,解析PE基本字段,枚举节表,最后关闭文件。类中需要添加几个成员变量和成员函数,添加的内容如图2所示。图2类中添加的成员变量和成员函数按照上述顺序依次实现添加的成员函数。BOOLCPeParseDlg::FileCreate(char*szFileName){BOOLbRet=FALSE;m_hFile=CreateFile(szFileName,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);如果(m_hFile==INVALID_MaphANDLE_VALUE)Ret(m_hFile==INVALID_MaphANDLE_VALUE)Ret(m_hFile==INVALID_MaphANDLE_VALUE)Ret(m_hFileID==INVALT_MaphFile)(If(m_lpBase==NULL){CloseHandle(m_hMap);CloseHandle(m_hFile);returnbRet;}bRet=TRUE;returnbRet;}这个函数的主要作用是打开文件,创建内存文件镜像。通常,两个函数ReadFile()和WriteFile()直接用于连续读写文件。间断操作文件时,每次在ReadFile()或WriteFile()后都要用SetFilePointer()调整文件指针的位置,比较麻烦。内存文件映像的作用是将整个文件映射到进程的虚拟空间中,这样操作文件就像操作内存变量或内存数据一样方便。有两个函数用来创建内存文件图像,分别是CreateFileMapping()和MapViewOfFile()。CreateFileMapping()函数定义如下:参数说明如下。hFile:这个参数是CreateFile()函数返回的句柄。lpAttributes:是一个安全属性,值通常为NULL。flProtect:创建文件映射后的属性,一般设置为可读可写PAGE_READWRITE。如果需要像加载可执行文件一样将文件映射到内存中,则需要使用SEC_IMAGE。最后3个参数在这里为0。如果创建的映射需要在多个进程中共享数据,则将最后一个参数设置为字符串,以便通过名称找到共享内存块。该函数的返回值是内存映射的句柄。MapViewOfFile()函数的定义如下:参数说明如下。hFileMappingObject:该参数是CreateFileMapping()返回的句柄。dwDesiredAccess:你想要获得的访问权限,通常是可读可写的FILE_MAP_READ,FILE_MAP_WRITE。最后3个参数一般给0值。根据编程规则,打开必须关闭,应用必须释放。CreateFileMapping()的关闭需要使用CloseHandle()函数。要关闭MapViewOfFile(),请使用UnmapViewOfFile()函数,其定义如下:BOOLUnmapViewOfFile(LPCVOIDlpBaseAddress);该函数的参数是MapViewOfFile()函数的返回值。然后说说PE查看器,文件已经打开,需要判断文件是否是合法的PE文件。如果是有效的PE文件,则还获取到解析PE格式的相关结构指针。代码如下:BOOLCPeParseDlg::IsPeFileAndGetPEPointer(){BOOLbRet=FALSE;//判断是否为MZ头m_pDosHdr=(PIMAGE_DOS_HEADER)m_lpBase;if(m_pDosHdr->e_magic!=IMAGE_DOS_SIGNATURE){returnbRet;}//Based关于IMAGE_DOS_lfHEADER的e的值到PE头的位置m_pNtHdr=(PIMAGE_NT_HEADERS)((DWORD)m_lpBase+m_pDosHdr->e_lfanew);//判断是否是PE\0\0if(m_pNtHdr->Signature!=IMAGE_NT_SIGNATURE){returnbRet;}//获取节表的位置m_pSecHdr=(PIMAGE_SECTION_HEADER)((DWORD)&(m_pNtHdr->OptionalHeader)+m_pNtHdr->FileHeader.SizeOfOptionalHeader);bRet=TRUE;returnbRet;}这段代码应该很容易理解,继续看解析PE格式的部分。VOIDCPeParseDlg::ParseBasePe(){CStringStrTmp;//入口地址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);SetDlg(IDC_EDIT_LINKVERSION,StrTmp);//节表数量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格式信息基础就是简单的获取结构体的成员变量,没有太多复杂的内容。获取进出口表比获取基本信息复杂。接下来枚举section表,具体代码如下:.InsertItem(i,(constchar*)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查看器就完成了。
