• 标 题:来一篇垃圾的东西(bugfix)
  • 作 者:WiNrOOt
  • 时 间:004-09-04,21:44
  • 链 接:http://bbs.pediy.com

深入浅出PE文件格式---自己动手打造PE Show
                                                            作者:WiNrOOt
////////////////////////////////////////////////////////////////////
//                            开篇                               //
///////////////////////////////////////////////////////////////////
   大家好!我一位菜鸟,学习加密解密已经有一段时间了,可是对于脱壳总是似懂非懂,心中甚是不爽。
于是就从PE结构开始,在学习的过程中我发现要真正了解PE文件结构就必须动手,只有动手您才能看懂他,
感受他,直到你应用他………………这是我的一点废话希望大家不要嫌烦。下面是是我的学习笔记,希望能给大家
带来一点提示,文中有不对的地方请各位大虾指正。谢谢!
//////////////////////////////////////////////////////////////////
//                            准备                             //
/////////////////////////////////////////////////////////////////
开篇之前我想大家起码要有几点准备:
1。Iczelion's Win32 Assembly的教程我们主要是围绕他的PE教程来实现我们的函数功能。
    (其实Win32ASM Tutorial Resource Kit v1.00 Collected and packed by dREAMtHEATER就包含这些
    还是翻译过的。下载地址WWW.PEDIY.COM)
    下载回来希望您能看一下,这样大家就好交流:-)

2。一个开发环境。(我用的是VC++6.0)
3。一个适合你研究东西的环境。
4.源码本地下载(或点击右键另存为)
////////////////////////////////////////////////////////////////
//                           正文                            //
///////////////////////////////////////////////////////////////

     “ PE 的意思就是 Portable Executable(可移植的执行体)。它是 Win32环境自身所带的执行体文件格
式。它的一些特性继承自 Unix的 Coff (common object file format)文件格式。"portable executable"
(可移植的执行体)意味着此文件格式是跨win32平台的 :
即使Windows运行在非Intel的CPU上,任何win32平台的PE装载器都能识别和使用该文件格式。当然,移植到不
同的CPU上PE执行体必然得有一些改变。所有 win32执行体
(除了VxD和16位的Dll)都使用PE文件格式,包括NT的内核模式驱动程序(kernel mode
drivers)。因而研究PE文件格式给了我们洞悉Windows结构的良机。“
      好了,上面这段话就是我们为什么要研究PE文件结构。

看图,这张图我相信大家不陌生,第一块是DOS MZ header这是什么呢?
      这张图的每一块都是什么意思呢?
      
那么我们就来开始动手。
 我们要设计我们自己的PE TOOLS----PE Show
 主要功能:1判断文件是否是PE文件。
           2显示pe文件的相关信息。

1.打开文件代码如下:

代码:
if(FALSE==PEfile.Open(m_filename,CFile::typeBinary|CFile::shareDenyNone))   {     MessageBox("文件打不开!");     return;    }

CFile类的使用方法希望大及自己去查找msdn
2。文件我们打开了而且是以Binary方式打开的,下面我们该干什么了?

  编写第一个功能-----检验PE文件的有效性
在Iczelion's的教程中有这样一段话:
“1。首先检验文件头部第一个字的值是否等于 IMAGE_DOS_SIGNATURE,是则 DOS MZ header 有效。
 2。一旦证明文件的 DOS header 有效后,就可用e_lfanew来定位 PE header 了。
 3。比较 PE header 的第一个字的值是否等于 IMAGE_NT_HEADER。
    如果前后两个值都匹配,那我们就认为该文件是一个有效的PE文件。
这就是检验PE文件有效性的流程。
从上面那段话我们看出判断的关键是PE header 的第一个字的值是否等于 IMAGE_NT_HEADER
直到这些我们就倒着找。
PE header 的第一个字的值是什么?
我么就来看一下IMAGE_NT_HEADERS的结构:(查看WINNT.H就找到了)
代码:
typedef struct _IMAGE_NT_HEADERS {     DWORD Signature;     IMAGE_FILE_HEADER FileHeader;     IMAGE_OPTIONAL_HEADER32 OptionalHeader; } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

Signature 一dword类型,值为50h, 45h, 00h, 00h(PE\0\0)。 本域为PE标记,我们可以此识别给定文件是否为有效PE文件。
FileHeader 该结构域包含了关于PE文件物理分布的信息, 比如节数目、文件执行机器等。
OptionalHeader 该结构域包含了关于PE文件逻辑分布的信息,虽然域名有"可选"字样,但实际上本结构总是存在的。
我们目的很明确。如果IMAGE_NT_HEADERS的signature域值等于"PE\0\0",那么就是有效的PE文件。实际上,为了比较方便,Microsoft已定义了常量
代码:
IMAGE_NT_SIGNATURE供我们使用。 IMAGE_DOS_SIGNATURE equ 5A4Dh  IMAGE_OS2_SIGNATURE equ 454Eh  IMAGE_OS2_SIGNATURE_LE equ 454Ch  IMAGE_VXD_SIGNATURE equ 454Ch  IMAGE_NT_SIGNATURE equ 4550h 


判断的问题我们解决了,新的问题又来了我们如何定位IMAGE_NT_HEADERS结构的位置。
MS肯定有办法,PE文件的开头是什么?DOS MZ header结构,我们来看一下他的定义:
代码:
typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header     WORD   e_magic;                     // Magic number     WORD   e_cblp;                      // Bytes on last page of file     WORD   e_cp;                        // Pages in file     WORD   e_crlc;                      // Relocations     WORD   e_cparhdr;                   // Size of header in paragraphs     WORD   e_minalloc;                  // Minimum extra paragraphs needed     WORD   e_maxalloc;                  // Maximum extra paragraphs needed     WORD   e_ss;                        // Initial (relative) SS value     WORD   e_sp;                        // Initial SP value     WORD   e_csum;                      // Checksum     WORD   e_ip;                        // Initial IP value     WORD   e_cs;                        // Initial (relative) CS value     WORD   e_lfarlc;                    // File address of relocation table     WORD   e_ovno;                      // Overlay number     WORD   e_res[4];                    // Reserved words     WORD   e_oemid;                     // OEM identifier (for e_oeminfo)     WORD   e_oeminfo;                   // OEM information; e_oemid specific     WORD   e_res2[10];                  // Reserved words     LONG   e_lfanew;                    // File address of new exe header   } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

  看一下最后一项!!!!!
  发现了吗?他指向的就是PE header
  那么DOS MZ header的位置怎么确定呢?呵呵,他就是文件的开始
  看代码:
代码:
        PEfile.Read(&stPEDosHeader,sizeof(_IMAGE_DOS_HEADER));   if(stPEDosHeader.e_magic!=IMAGE_DOS_SIGNATURE)//"MZ"   {     MessageBox("DOS MZ header无效!");     PEfile.Close();     return;   }   else   {     //-----------------------     //显示DOS Header     //-----------------------     UpdateData(true);     m_Magicnumber.Format(_T("0x%.4X"),stPEDosHeader.e_magic);     m_cblp.Format(_T("0x%.4X"),stPEDosHeader.e_cblp);     m_cp.Format(_T("0x%.4X"),stPEDosHeader.e_cp);     m_crlc.Format(_T("0x%.4X"),stPEDosHeader.e_crlc);     m_cparhdr.Format(_T("0x%.4X"),stPEDosHeader.e_cparhdr);     m_minalloc.Format(_T("0x%.4X"),stPEDosHeader.e_minalloc);     m_maxalloc.Format(_T("0x%.4X"),stPEDosHeader.e_maxalloc);     m_ss.Format(_T("0x%.4X"),stPEDosHeader.e_ss);     m_sp.Format(_T("0x%.4X"),stPEDosHeader.e_sp);     m_csum.Format(_T("0x%.4X"),stPEDosHeader.e_csum);     m_ip.Format(_T("0x%.4X"),stPEDosHeader.e_ip);     m_cs.Format(_T("0x%.4X"),stPEDosHeader.e_cs);     m_lfarlc.Format(_T("0x%.4X"),stPEDosHeader.e_lfarlc);     m_ovno.Format(_T("0x%.4X"),stPEDosHeader.e_ovno);     m_oemid.Format(_T("0x%.4X"),stPEDosHeader.e_oemid);     m_oeminfo.Format(_T("0x%.4X"),stPEDosHeader.e_oeminfo);     m_lfanew.Format(_T("0x%.8X"),stPEDosHeader.e_lfanew);     UpdateData(false);   }   buf=stPEDosHeader.e_lfanew;  //确定_IMAGE_DOS_HEADER偏移   try{PEfile.Seek(buf,CFile::begin);}   catch(...)   {     MessageBox("_IMAGE_DOS_HEADER.e_lfanew不对!");     PEfile.Close();     return;   }   PEfile.Read(&stPEHeader,sizeof(_IMAGE_NT_HEADERS));//----------NT头   if(stPEHeader.Signature!=IMAGE_NT_SIGNATURE)//"PE\0\0"   {     MessageBox("该文件不是PE格式!");     PEfile.Close();     return;   }   else        {            MessageBox("该文件是PE格式!");            PEfile.Close();     return;          }  


好了现在我们已经确定了文件是否是有效的PE文件。顺便我们把IMAGE_DOS_HEADER的结构成员都显示出来了。
我们已经写了一个功能。
下面我们继续顺着往下看:
IMAGE_DOS_HEADER结构结束那么就是IMAGE_NT_HEADERS开始。结构体的成员情况前面我们已经介绍过了
下面我们来提取他们:
代码:
                //---------------------------     //显示IMAGE_FILE_HEADER结构     //---------------------------     UpdateData(true);     m_Machine.Format(_T("0x%.4X"),stPEHeader.FileHeader.Machine);     m_NumberOfSections.Format(_T("0x%.4X"),stPEHeader.FileHeader.NumberOfSections);     m_TimeDateStamp.Format(_T("0x%.8X"),stPEHeader.FileHeader.TimeDateStamp);         m_PointerToSymbolTable.Format(_T("0x%.8X"),stPEHeader.FileHeader.PointerToSymbolTable);     m_NumberOfSymbols.Format(_T("0x%.8X"),stPEHeader.FileHeader.NumberOfSymbols);     m_SizeOfOptionalHeader.Format(_T("0x%.4X"),stPEHeader.FileHeader.SizeOfOptionalHeader);     m_Characteristics.Format(_T("0x%.4X"),stPEHeader.FileHeader.Characteristics);     UpdateData(false);
       
这是IMAGE_FILE_HEADER FileHeader的成员,我们已经将他们提取出来。
看教程我们继续寻找IMAGE_OPTIONAL_HEADER的成员,他是结构体中的结构体我们就顺着找。
代码:
typedef struct _IMAGE_OPTIONAL_HEADER {     //     // Standard fields.     //     WORD    Magic;     BYTE    MajorLinkerVersion;     BYTE    MinorLinkerVersion;     DWORD   SizeOfCode;     DWORD   SizeOfInitializedData;     DWORD   SizeOfUninitializedData;     DWORD   AddressOfEntryPoint;     DWORD   BaseOfCode;     DWORD   BaseOfData;     //     // NT additional fields.     //     DWORD   ImageBase;     DWORD   SectionAlignment;     DWORD   FileAlignment;     WORD    MajorOperatingSystemVersion;     WORD    MinorOperatingSystemVersion;     WORD    MajorImageVersion;     WORD    MinorImageVersion;     WORD    MajorSubsystemVersion;     WORD    MinorSubsystemVersion;     DWORD   Win32VersionValue;     DWORD   SizeOfImage;     DWORD   SizeOfHeaders;     DWORD   CheckSum;     WORD    Subsystem;     WORD    DllCharacteristics;     DWORD   SizeOfStackReserve;     DWORD   SizeOfStackCommit;     DWORD   SizeOfHeapReserve;     DWORD   SizeOfHeapCommit;     DWORD   LoaderFlags;     DWORD   NumberOfRvaAndSizes;     IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;


看到这些成员将他们全部显示出来
代码:
                 //--------------------------------     //显示IMAGE_OPTIONAL_HEADER     //--------------------------------     UpdateData(true);     m_Magic.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.Magic);     m_MajorLinkerVersion.Format(_T("0x%.2X"),stPEHeader.OptionalHeader.MajorLinkerVersion);     m_MinorLinkerVersion.Format(_T("0x%.2X"),stPEHeader.OptionalHeader.MinorLinkerVersion);     m_SizeOfCode.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfCode);     m_SizeOfInitializedData.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfInitializedData);     m_SizeOfUninitializedData.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfUninitializedData);     m_AddressOfEntryPoint.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.AddressOfEntryPoint);     m_BaseOfCode.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.BaseOfCode);     m_BaseOfData.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.BaseOfData);     m_ImageBase.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.ImageBase);     m_SectionAlignment.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SectionAlignment);     m_FileAlignment.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.FileAlignment);     m_MajorOperatingSystemVersion.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.MajorOperatingSystemVersion);     m_MinorOperatingSystemVersion.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.MinorOperatingSystemVersion);     m_MajorImageVersion.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.MajorImageVersion);     m_MinorImageVersion.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.MinorImageVersion);     m_MajorSubsystemVersion.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.MajorSubsystemVersion);     m_MinorSubsystemVersion.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.MinorSubsystemVersion);     m_Win32VersionValue.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.Win32VersionValue);     m_SizeOfImage.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfImage);     m_SizeOfHeaders.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfHeaders);     m_CheckSum.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.CheckSum);     m_Subsystem.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.Subsystem);     m_DllCharacteristics.Format(_T("0x%.4X"),stPEHeader.OptionalHeader.DllCharacteristics);     m_SizeOfStackReserve.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfStackReserve);     m_SizeOfStackCommit.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfStackCommit);     m_SizeOfHeapReserve.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfHeapReserve);     m_SizeOfHeapCommit.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.SizeOfHeapCommit);     m_LoaderFlags.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.LoaderFlags);     m_NumberOfRvaAndSizes.Format(_T("0x%.8X"),stPEHeader.OptionalHeader.NumberOfRvaAndSizes);     UpdateData(false);      

我们已经学了许多关于 DOS header 和 PE header 的知识。接下来就该轮到 section table(节表)了。
节表其实就是紧挨着 PE header 的一结构数组。该数组成员的数目由 file header (IMAGE_FILE_HEADER) 
结构中 NumberOfSections 域的域值来决定。节表结构又命名为 IMAGE_SECTION_HEADER。
代码:
typedef struct _IMAGE_SECTION_HEADER {     BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];     union {             DWORD   PhysicalAddress;             DWORD   VirtualSize;     } Misc;     DWORD   VirtualAddress;     DWORD   SizeOfRawData;     DWORD   PointerToRawData;     DWORD   PointerToRelocations;     DWORD   PointerToLinenumbers;     WORD    NumberOfRelocations;     WORD    NumberOfLinenumbers;     DWORD   Characteristics; } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER


Name     这儿的节名长不超过8字节。记住节名仅仅是个标记而已,我们选择任何名字甚至空着也行,注意这里不用null结束。命名不是一个ASCIIZ字符串,所以不用null结尾。 
VirtualAddress 本节的RVA(相对虚拟地址)。PE装载器将节映射至内存时会读取本值,因此如果域值是1000h,而PE文件装在地址400000h处,那么本节就被载到401000h。 
SizeOfRawData 经过文件对齐处理后节尺寸,PE装载器提取本域值了解需映射入内存的节字节数。(译者注: 假设一个文件的文件对齐尺寸是0x200,如果前面的 VirtualSize域指示本节长度是0x388字节,则本域值为0x400,表示本节是0x400字节长)。 
PointerToRawData 这是节基于文件的偏移量,PE装载器通过本域值找到节数据在文件中的位置。 
Characteristics 包含标记以指示节属性,比如节是否含有可执行代码、初始化数据、未初始数据,是否可写、可读等。


现在我们已知晓 IMAGE_SECTION_HEADER 结构,再来模拟一下 PE装载器的工作吧:

1 读取 IMAGE_FILE_HEADER 的 NumberOfSections域,知道文件的节数目。 
2 SizeOfHeaders 域值作为节表的文件偏移量,并以此定位节表。 
3 遍历整个结构数组检查各成员值。 
4 对于每个结构,我们读取PointerToRawData域值并定位到该文件偏移量。然后再读取SizeOfRawData域值来决定映射内存的字节数。将VirtualAddress域值加上ImageBase域值等于节起始的虚拟地址。然后就准备把节映射进内存,并根据Characteristics域值设置属性。 
5 遍历整个数组,直至所有节都已处理完毕。
代码如下:
代码:
                //------------------------------     //显示Section结构     //------------------------------     nSection=stPEHeader.FileHeader.NumberOfSections;     stSectionHeader=new _IMAGE_SECTION_HEADER[nSection];     m_ListCtrl.DeleteAllItems();     for(int i=0;i<nSection;i++)     {              PEfile.Read(&stSectionHeader[i],sizeof(_IMAGE_SECTION_HEADER));//-----节表       //NO       szTemp.Format(_T("%.2d"), i+1);       m_ListCtrl.InsertItem(i,szTemp,i);       //SectionName       strcpy(chSectionName,(LPCSTR)stSectionHeader[i].Name);              m_ListCtrl.SetItemText(i,1,chSectionName);       //VirtualSize       szTemp.Format(_T("0x%.8X"),stSectionHeader[i].Misc.VirtualSize);                 m_ListCtrl.SetItemText(i,2,szTemp);       //VirtualAddress       szTemp.Format(_T("0x%.8X"),stSectionHeader[i].VirtualAddress );                 m_ListCtrl.SetItemText(i,3,szTemp);       //SizeOfRawData       szTemp.Format(_T("0x%.8X"),stSectionHeader[i].SizeOfRawData );       m_ListCtrl.SetItemText(i,4,szTemp);       //SizeOffset       szTemp.Format(_T("0x%.8X"),stSectionHeader[i].PointerToRawData );       m_ListCtrl.SetItemText(i,5,szTemp);       //Characteristics       szTemp.Format(_T("0x%.8X"),stSectionHeader[i].Characteristics );       m_ListCtrl.SetItemText(i,6,szTemp);                   }     delete stSectionHeader;

好了,我们计划的两个功能已经写完

不早了,我要睡觉了(2003-12-18 2:02)其他的功能下一篇再说吧,
哈哈,对了差点忘了
所有完整代码我都放在压缩文件里了自己看看代码应该对pe有个大概的了解,另外送大家一个我在网上收集的pe结构图。
大家在写的时候别忘了#include <winnt.h>
睡了睡了,,,,困死了!@#$%^&*()
886 
                                                                           WiNrOOt
                                                                       2003-12-18 2:07