QQ登录

只需一步,快速开始

PE文件结构之DOS头组成及编程读取

[ 复制链接 ]
PE文件总的可以理解为后缀为EXE的可以执行的文件。
但实际还有很多其他类型文件,如ROM文件,dll文件等等。
个人正在熟悉PE文件的结构与编写代码读取与修改。
汇编与PE文件与逆向绕不过去的坎,所以决定在这里记录学习过程的心得,与代码编写的源代码。

我们知道文件在磁盘中是以二进制形式保存,包括可执行程序。
这些数据以指定的规则保存与加载,学习熟悉这些规则才能够很好的修改这些文件。
很基础的电脑常识,
二进制数8个组成一个字节BYTE,
二进制数16个组成一个字WORD,
二进制数32个组成一个双字DWORD,
为了更加直观方便的操作二进制数,可以以十六进制数来表示二进制数。
4个二进制数可以用一个16进制数表示。
这样一个可执行文件通过ultraedit程序打开,就可以以十六 进制形式来显示文件数据。
(ultraedit网络都可以搜索下载)
如下图:

PE文件结构之DOS头组成及编程读取

PE文件结构之DOS头组成及编程读取

在界面上可以看到文件以十六进制的形式,从左到右,从上到下,
以字节为单位依次展开。
第0个字节地址为00000000H,从左到右共16个字节,从上到下以16个字节递增。

这些数据以一定的格式存储,按指定的规则被系统读取使用。
格式如何?
好比一个人的组成,由头与身躯两大部分组成,但头与身躯又有细分。
头有眼睛,嘴巴等,身躯有手脚,头控制身体。
或许通过书来打比喻更形象。
打开一本书,我们关心的内容主要由头部与内容两大部分组成。
头部又会有封面,前言,简介,目录等等,从这些部分我们可以大概了解书的相关介绍,
作者是谁,书适合什么人,内容有什么,然后是目录,通过目录可以检索书的具体内容。
另一部分是书的内容,内容具体细分章节,小节,可由目录索引。


PE文件也可总分为两部分:
PE文件头+节区。
PE文件头记录此PE文件相关的信息与实现访问节区类似目录的功能。
节区类似于书的内容,保存有代码,资料,数据等文件具体内容。
PE文件头与节区具体都是可以在细分的。
如下:

PE文件结构之DOS头组成及编程读取

PE文件结构之DOS头组成及编程读取

总的来说,PE文件头由DOS头,PE头,节表三部分组成。
DOS头又细分为DOS_MZ头(对应结构体IMAGE_DOS_HEADER), 与DOS_Stub.
PE头实际为一个结构体IAMGE_NT_HEADER,
又细分为Signature(固定为"PE00"),
标准PE头(对应结构体IMAGE_FILE_HEADER),
可选PE头(对应结构体IMAGE_OPTIONAL_HEADER).
节表由众多节表项组成,类似目录,记录PE文件节区对应节相关信息。

一个PE文件大概由上面部分组成,每个部分在文件中前后按顺序存储。
如何编写代码来访问每个部分呢?
这里编写代码来实现打开PE文件,读取与显示DOS部分数据。
PE头部分的读取放在下一帖子来实现。
理论配合实践才能更好理解PE结构。
例程界面如下:

PE文件结构之DOS头组成及编程读取

PE文件结构之DOS头组成及编程读取

首先点击界面OPEN打开一个PE文件。
然后就可以点击DOS头,DOS Stub两按钮来显示读取的DOS头信息与DOS stub信息。
例程集成一个类CPE来处理PE文件相关功能。
打开PE文件函数为
  1. bool CPE::Open(CString sFullFileName)
  2. {
  3.         PIMAGE_DOS_HEADER pDosHead=NULL;
  4.         sFullFileName.TrimRight();
  5.         CFile f;
  6.         if(!f.Open(sFullFileName,CFile::modeRead|CFile::shareDenyNone|CFile::typeBinary))
  7.         {
  8.                 Print(_T("PE文件打开失败。"));
  9.                 return false;
  10.         }
  11.         UINT lFileLen = f.GetLength();
  12.         if(lFileLen<=0)
  13.         {
  14.                 Print(_T("PE文件大小异常。"));
  15.                 return false;
  16.         }
  17.         Close();//释放事先分配的空间;
  18.         pDosHead = (PIMAGE_DOS_HEADER)new char[lFileLen + 10];
  19.         if(pDosHead == NULL)
  20.         {
  21.                 Print(_T("PE文件分配空间失败。"));
  22.                 return false;
  23.         }
  24.         if(lFileLen != f.Read(pDosHead,lFileLen))
  25.         {
  26.                 delete pDosHead;
  27.                 Print(_T("PE文件读取失败。"));
  28.                 return false;
  29.         }
  30.         if(!IsPEValid(pDosHead))
  31.         {
  32.                 delete pDosHead;
  33.                 Print(_T("PE文件无效。"));
  34.                 return false;
  35.         }
  36.         //
  37.         m_pDosHead       = pDosHead;
  38.         m_PEFileFullName = sFullFileName;
  39.         m_lPEFileLen     = lFileLen;
  40.         //
  41.         f.Close();
  42.         return true;
  43. }
复制代码
PE文件成功打开后,头64个字节就为DOS头数据,对应结构体为IMAGE_DOS_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;

对应下图第一行第0个字节到第4行最后一字节。
此结构体在例程中,鼠标选中,按F12就可跳转到定义位置。

如上边打开PE文件的代码,打开时就对全局变量PIMAGE_DOS_HEADER m_pDosHead进行了赋值。
这样的话,想要显示结构体数据,只要简单在界面上通过控件显示就好。
  1. void CDosHeadDlg::UpdateData(PIMAGE_DOS_HEADER pDosHeader,bool bSetGet)
  2. {
  3.         if(pDosHeader == NULL)
  4.                 return;
  5.         CString sText,sTemp;
  6.         int i=0;
  7.         if(bSetGet)//设置控件数据;
  8.         {
  9.                 sText.Format("0x%04X",pDosHeader->e_magic);
  10.                 SetDlgItemText(IDC_EDIT1,sText);
  11.                 sText.Format("0x%04X",pDosHeader->e_cblp);
  12.                 SetDlgItemText(IDC_EDIT2,sText);
  13.                 sText.Format("0x%04X",pDosHeader->e_cp);
  14.                 SetDlgItemText(IDC_EDIT3,sText);
  15.                 sText.Format("0x%04X",pDosHeader->e_crlc);
  16.                 SetDlgItemText(IDC_EDIT4,sText);
  17.                 sText.Format("0x%04X",pDosHeader->e_cparhdr);
  18.                 SetDlgItemText(IDC_EDIT5,sText);
  19.                 sText.Format("0x%04X",pDosHeader->e_minalloc);
  20.                 SetDlgItemText(IDC_EDIT6,sText);
  21.                 sText.Format("0x%04X",pDosHeader->e_maxalloc);
  22.                 SetDlgItemText(IDC_EDIT7,sText);
  23.                 sText.Format("0x%04X",pDosHeader->e_ss);
  24.                 SetDlgItemText(IDC_EDIT8,sText);
  25.                 sText.Format("0x%04X",pDosHeader->e_sp);
  26.                 SetDlgItemText(IDC_EDIT9,sText);
  27.                 sText.Format("0x%04X",pDosHeader->e_csum);
  28.                 SetDlgItemText(IDC_EDIT10,sText);
  29.                 sText.Format("0x%04X",pDosHeader->e_ip);
  30.                 SetDlgItemText(IDC_EDIT11,sText);
  31.                 sText.Format("0x%04X",pDosHeader->e_cs);
  32.                 SetDlgItemText(IDC_EDIT12,sText);
  33.                 sText.Format("0x%04X",pDosHeader->e_lfarlc);
  34.                 SetDlgItemText(IDC_EDIT13,sText);
  35.                 sText.Format("0x%04X",pDosHeader->e_ovno);
  36.                 SetDlgItemText(IDC_EDIT14,sText);
  37.                 sText.Empty();
  38.                 for(i=0;i<4;i++)
  39.                 {
  40.                         sTemp.Format("%04X",pDosHeader->e_res[i]);
  41.                         sText+=sTemp;
  42.                 }
  43.                 SetDlgItemText(IDC_EDIT15,sText);
  44.                 sText.Format("0x%04X",pDosHeader->e_oemid);
  45.                 SetDlgItemText(IDC_EDIT16,sText);
  46.                 sText.Format("0x%04X",pDosHeader->e_oeminfo);
  47.                 SetDlgItemText(IDC_EDIT17,sText);
  48.                 sText.Empty();
  49.                 for(i=0;i<10;i++)
  50.                 {
  51.                         sTemp.Format("%04X",pDosHeader->e_res2[i]);
  52.                         sText+=sTemp;
  53.                 }
  54.                 SetDlgItemText(IDC_EDIT18,sText);
  55.                 sText.Format("0x%08X",pDosHeader->e_lfanew);
  56.                 SetDlgItemText(IDC_EDIT19,sText);
  57.         }
  58.         else//从控件获取数据;
  59.         {

  60.         }
  61. }
复制代码
代码都是例程的内容,可以在后边下载代码来参考使用。
我们知道不PE文件的每个部分都是连续存储的,我们知道了IMAGE_DOS_HEADER地址,
自然就可以定位到紧跟其后的DOS_Stub数据。
DOS_Stub部分是一连串长度不固定的字节块,没有对应的结构体。
由链接器任意填充,我们定位到它的地址,如何丰富它的长度?
这里我们就要借助IMAGE_DOS_HEADER结构体的成员变量e_lfanew。
我们知道IMAGE_DOS_HEADER跟着DOS_Stub,再跟着PE头,
e_lfanew表示PE头地址,这样通过已选条件就知道DOS_Stub长度。
具体参考例程代码:
  1. void CDosStubDlg::UpdateData(PIMAGE_DOS_HEADER pDosHeader,bool bSetGet)
  2. {
  3.         if(pDosHeader == NULL)
  4.                 return;
  5.         int nOffset=sizeof(IMAGE_DOS_HEADER);
  6.         int nEnd = pDosHeader->e_lfanew-nOffset,i=0;
  7.         byte* p = (byte*)pDosHeader+nOffset;
  8.         CString sText,sTemp;
  9.         if(bSetGet)
  10.         {
  11.                
  12.                 for(i=0;i<nEnd;i++)
  13.                 {                        
  14.                         if(i%16 == 0 && i>0)
  15.                                 sText += "\r\n";                        
  16.                         sTemp.Format("%02X   ",*p);                        
  17.                         sText+=sTemp;
  18.                         p++;                        
  19.                 }
  20.                 SetDlgItemText(IDC_EDIT1,sText);
  21.         }
  22.         else
  23.         {

  24.         }
  25. }
复制代码
这样,例程就实现了打开PE文件,显示DOS_MZ,与DOS_Stub数据信息了。
DOS_MZ紧跟着DOS_Stub,再跟着PE头,PE头我们放在下一帖子实现读取。
DOS_MZ加DOS_Stub组成DOS头,在DOS系统会有实际意义,剩余无意义。
但在Windows NT系统却相反,DOS头基本没作用。
但全用到DOS_MZ的两个成员:
e_lfanew:表示PE头开始地址。
e_magic:可执行文件标志,固定为"MZ",据说是一开发人员名字缩写。
虽说DOS头没多大作用,IMAGE_DOS_HEADER结构体也只用到两个成员。
但了解一下每个成员含义也无妨。
IMAGE_DOS_HEADER STRUCT
{
+0h WORD e_magic   // EXE标志,固定 MZ(4Dh 5Ah)
+2h   WORD  e_cblp  // 最后(部分)百中的字节数  
+4h WORD  e_cp   // 文件中的全部和部分页数
+6h WORD  e_crlc   // 重定位表中的指针数
+8h WORD  e_cparhdr   // 头部尺寸,以段落为单位
+0ah WORD  e_minalloc  // 所需最小附加段
+0ch WORD  e_maxalloc  // 所需最大附加段
+0eh WORD  e_ss    // DOS代码的初始化堆栈SS
+10h WORD  e_sp    //  DOS代码的初始化堆栈指针SP
+12h WORD  e_csum    // 补码校验值
+14h WORD  e_ip    //  DOS代码的初始化指令入口[指针IP]
+16h WORD  e_cs    // DOS代码的初始堆栈入口
+18h WORD  e_lfarlc    // 重定位表字节偏移量
+1ah WORD  e_ovno        // 覆盖号
+1ch WORD  e_res[4]    // 系统保留字
+24h WORD  e_oemid    //    OEM标识符(相对e_oeminfo)
+26h WORD      e_oeminfo   //    OEM 信息
+29h WORD  e_res2[10]   //    系统保留字
+3ch DWORD   e_lfanew     // PE头相对文件的偏移地址。
} IMAGE_DOS_HEADER ENDS


PE文件中DOS头还是比较简单的,只要关心两个变量。
DOS头紧跟着的是PE头,PE头又由三部分组成,这些部分又如何读取与显示呢?
我们会在下一帖子,介绍其中标准PE头的读取与显示。
例程下载地址:
请点击此处下载

请先注册会员后在进行下载

已注册会员,请先登录后下载

文件名称:PEEdit.rar 
文件大小:67.75 KB  售价:3金币
下载权限: 不限 以上或 VIP会员   [购买捐助会员]   [充值积分]   有问题联系我


回复

使用道具 举报

点击查看
快速回复 返回列表 客服中心 搜索