学习Polymorphours的代码,研究一下pe文件格式

 

这个是pe文件个格式,先有了一个形象的概念。

两个基本概念
section
    就是段,不同的应用程序都有基本的段。我们也可以通过程序修改段,一般.text段放着程序的源代码,.data 存放初始化信息的地方。包括全局变量、字符串常量和静态变量,这些变量在编译时期就给定初值。连接器把obj和lib中的所有.data组合起来放到exe的.data中。而变量放在执行过程中的堆栈中。等等了。可以通过域numbersofsection来获得一个应用程序的段

相对偏移量(rav)
        RVA是虚拟空间中到参考点的一段距离。举例说明,如果PE文件装入虚拟地址(VA)空间的400000h处,且进程从虚址401000h开始执行,我们可以说进程执行起始地址在RVA 1000h 为什么PE文件格式要用到RVA? 这是为了减少PE装载器的负担。因为每个模块多有可能被重载到任何虚拟地址空间

第一部分:MS_DOS头部,它的定义为IMAGE_DOS_HEADER结构体,具体定义如下:

typedef struct _IMAGE_DOS_HEADER
{
  USHORT e_magic;       // 魔术数字
  USHORT e_cblp;        // 文件最后页的字节数
  USHORT e_cp;          // 文件页数
  USHORT e_crlc;        // 重定义元素个数
  USHORT e_cparhdr;     // 头部尺寸,以段落为单位
  USHORT e_minalloc;    // 所需的最小附加段
  USHORT e_maxalloc;    // 所需的最大附加段
  USHORT e_ss;          // 初始的SS值(相对偏移量)
  USHORT e_sp;          // 初始的SP值
  USHORT e_csum;        // 校验和
  USHORT e_ip;          // 初始的IP值
  USHORT e_cs;          // 初始的CS值(相对偏移量)
  USHORT e_lfarlc;      // 重分配表文件地址
  USHORT e_ovno;        // 覆盖号
  USHORT e_res[4];      // 保留字
  USHORT e_oemid;       // OEM标识符(相对e_oeminfo)
  USHORT e_oeminfo;     // OEM信息
  USHORT e_res2[10];    // 保留字
  LONG   e_lfanew;      // 新exe头部的文件地址
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

该结构体一共有64个字节大小。对于windows应用程序这个头部有用的域只有e_lfanew它标识着windows应用程序的真正的文件地址,还有就是e_lfarlc,定义为重新分配表文件地址,表示实模式残留程序的起始地址。这两个域定位都只是给出了文件的偏移量,这点要记住。一个具体的例子:

00000000  5A4D    签名: MZ

00000002  0090    额外字节

00000004  0003   

00000006  0000    重定位项目

00000008  0004    标头大小

0000000A  0000    最小允许

0000000C  FFFF    最大允许

0000000E  0000    初始 SS

00000010  00B8    初始 SP

00000012  0000    校验

00000014  0000    初始 IP

00000016  0000    初始 CS

00000018  0040    重定位表

0000001A  0000    覆盖
00000001C   0000     保留字(8个字节)

000000024   0000     OEM标识符(相对e_oeminfo)
000000026   0000     OEM信息

00000028     0000         保留字(20个字节)

0000003C    000000C8 新exe头部的文件地址(8个字节)

第二部分:实模式残留程序

    实模式残余程序是一个在装载时能够被MS-DOS运行的实际程序。对于一个MS-DOS的可执行映像文件,应用程序就是从这里执行的。对于Windows、OS/2、Windows NT这些操作系统来说,MS-DOS残余程序就代替了主程序的位置被放在这里。这种残余程序通常什么也不做,而只是输出一行文本。还是举个实模式残留程序的代码例子吧,如下:

00000040    0E 1F BA 0E 00 B4 09 CD - 21 B8 01 4C CD 21 54 68     ...........L..Th

00000050    69 73 20 70 72 6F 67 72 - 61 6D 20 63 61 6E 6E 6F     is.program.canno

00000060    74 20 62 65 20 72 75 6E - 20 69 6E 20 44 4F 53 20     t.be.run.in.DOS.

00000070    6D 6F 64 65 2E 0D 0D 0A - 24 00 00 00 00 00 00 00     mode............

00000080    C8 A4 79 1C 8C C5 17 4F - 8C C5 17 4F 8C C5 17 4F     ..y....O...O...O

00000090    E6 D9 15 4F 9B C5 17 4F - D5 E6 04 4F 83 C5 17 4F     ...O...O...O...O

000000A0    8C C5 16 4F 28 C5 17 4F - A6 CD 11 4F 8D C5 17 4F     ...O...O...O...O

000000B0    8C C5 17 4F 99 C5 17 4F - 52 69 63 68 8C C5 17 4F     ...O...ORich...O

000000C0    00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00     ................

000000D0    00 00 00 00 00 00 00 00 - 50 45 00 00 4C 01 03 00     ........PE..L...

 


第三部分:pe头部
    这个头部的结构如下:
 

typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER OptionalHeader; } IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;

它包括三个域,第一个域它是一个固定的格式,"PE/0/0",用来标识pe文件。
第二个域,pe文件头部,一个结构体,IMAGE_FILE_HEADER。该结构体一个20个字节。它具体的定义如下:

typedef struct _IMAGE_FILE_HEADER
{
       USHORT Machine;              //指定运行平台
       USHORT NumberOfSections;     //文件的节表(Section)数目
       ULONG  TimeDateStamp;        //文件创建日期和时间
       ULONG  PointerToSymbolTable; //用于调试
       ULONG  NumberOfSymbols;      //用于调试
       USHORT SizeOfOptionalHeader; //指示紧随本结构之后的 OptionalHeader 结构大小,必须为有效值
       USHORT Characteristics;      //关于文件信息的标记,比如文件是 exe还是 dll
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

还是给一个具体的例子吧:

000000D8        4550             PE标识
000000DA        0000             PE标识
000000DC
        014C             运行平台: 014C=I386

000000DE        0003             区段数

000000E0        37F6657C 时期/日期戳

000000E4        00000000 指示到符号表

000000E8        00000000 符号数

000000EC        00E0             可选标头大小

000000EE        030F             特性

第三个域,pe可选头部,一个结构体,IMAGE_OPTIONAL_HEADER。虽说是可选,但还是必不可少的,它包含了很多的信息,如可执行映像的重要信息,例如初始的堆栈大小、程序入口点的位置、首选基地址、操作系统版本、段对齐的信息等等。这个结构体分为两块,第一块是标准域,是和UNIX可执行文件的COFF格式所公共的部分。第二块就是Windows NT特定的进程行为提供了装载器的支持。这个结构体的具体定义如下:


typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode; //可执行代码尺寸
DWORD SizeOfInitializedData; //已初始化的数据尺寸
DWORD SizeOfUninitializedData; //未初始化的数据尺寸
DWORD AddressOfEntryPoint; //这个域表示应用程序入口点的位置。并且,对于系统黑客来说,这个位置就是导入地址表(IAT)的末尾
DWORD BaseOfCode; //已载入映像的代码(“.text”段)的相对偏移量
DWORD BaseOfData; //已载入映像的未初始化数据(“.bss”段)的相对偏移量
DWORD ImageBase; //进程映像地址空间中的首选基地址
DWORD SectionAlignment; //SectionAlignment则规定了装载时段能够占据的最小空间数量
DWORD FileAlignment; //映像文件首先装载的最小的信息块间隔。
WORD MajorOperatingSystemVersion; //表示Windows NT操作系统的主版本号
WORD MinorOperatingSystemVersion; //表示Windows NT Win32子系统的次版本号
WORD MajorImageVersion; //用来表示应用程序的主版本号
WORD MinorImageVersion; //用来表示应用程序的次版本号
WORD MajorSubsystemVersion; //表示Windows NT Win32子系统的主版本号
WORD MinorSubsystemVersion; //表示Windows NT Win32子系统的次版本号
DWORD Win32VersionValue; //通常不被系统使用,并被链接器设为0
DWORD SizeOfImage; //表示载入的可执行映像的地址空间中要保留的地址空间大小,这个数字很大程度上受SectionAlignment的影响
DWORD SizeOfHeaders; //这个域表示文件中有多少空间用来保存所有的文件头部,包括MS-DOS头部、PE文件头部、PE可选头部以及PE段头部
DWORD CheckSum; //校验和是用来在装载时验证可执行文件的,它是由链接器设置并检验的
WORD Subsystem; //用于标识该可执行文件目标子系统的域
WORD DllCharacteristics; //用来表示一个DLL映像是否为进程和线程的初始化及终止包含入口点的标记
DWORD SizeOfStackReserve; 这些域控制要保留的地址空间数量,并且负责栈和默认堆的申请
DWORD SizeOfStackCommit; 这些域控制要保留的地址空间数量,并且负责栈和默认堆的申请
DWORD SizeOfHeapReserve; 这些域控制要保留的地址空间数量,并且负责栈和默认堆的申请
DWORD SizeOfHeapCommit; 这些域控制要保留的地址空间数量,并且负责栈和默认堆的申请
DWORD LoaderFlags; //告知装载器是否在装载时中止和调试
DWORD NumberOfRvaAndSizes; 这个域标识了接下来的DataDirectory数组
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; } IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;
AddressOfEntryPoint
程序的入口点, PE装载器准备运行的PE文件的第一个指令的RVA。若您要改变整个执行的流程,
可以将该值指定到新的
RVA,这样新RVA处的指令首先被执行。
ImageBase
PE文件的优先装载地址。比如,如果该值是400000hPE装载器将尝试把文件装到虚拟地址空间的400000h处。
字眼
"优先"表示若该地址区域已被其他模块占用,那PE装载器会选用其他空闲地址。
SectionAlignment
内存中节对齐的粒度。例如,如果该值是4096 (1000h),那么每节的起始地址必须是4096的倍数。
若第一节从401000h开始且大小是10个字节,则下一节必定从402000h开始,即使401000h402000h
之间还有很多空间没被使用。
FileAlignment
文件中节对齐的粒度。例如,如果该值是(200h),,那么每节的起始地址必须是512的倍数。若第一节从文件偏移量
200h开始且大小是10个字节,则下一节必定位于偏移量400h: 即使偏移量5121024之间还有很多空间没被使用/定义。
SizeOfImage
内存中整个PE映像体的尺寸。它是所有头和节经过节对齐处理后的大小。
SizeOfHeaders
所有头+节表的大小,也就等于文件尺寸减去文件中所有节的尺寸。
可以以此值作为PE文件第一节的文件偏移量。
Subsystem
NT用来识别PE文件属于哪个子系统。 对于大多数Win32程序,只有两类值: Windows GUI Windows CUI (控制台)
DataDirectory
这是一个结构体,定义如下:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
 
每个结构给出一个重要数据结构的RVA,windows总共定义了16个,但是常用的为11个,如下:
//WINNT.H
 
// 目录入口
// 导出目录
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0
// 导入目录
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1
// 资源目录
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2
// 异常目录
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3
// 安全目录
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4
// 重定位基本表
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5
// 调试目录
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6
// 描述字串
#define IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7
// 机器值(MIPS GP)
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8
// TLS目录
#define IMAGE_DIRECTORY_ENTRY_TLS 9
// 载入配置目录
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10
    基本上,每个数据目录都是一个被定义为IMAGE_DATA_DIRECTORY的结构。虽然数据目录入口本身是相同的,
但是每个特定的目录种类却是完全唯一的。每个数据目录的定义在本文的以后部分被描述为“预定义段”。
如果你要定义一个特定的目录的话,就需要从可选头部中的数据目录数组中决定相对的地址,然后使用虚拟
地址来决定该目录位于哪个段中。一旦你决定了哪个段包含了该目录,该段的段头部就会被用于查找数据目录的精确文
件偏移量位置。
如果PE文件各段的根目录的话,也可以认为 data directory 是存储在这些节里的逻辑元素的根目录。
还是继续给一个例子吧,便于理解:

// 标准域----------------------------

000000F0        010B             Magic: 010B=普通可执行,0107=ROM 映像

000000F2        05               主版本号连接

000000F3        0C               副版本号连接

000000F4        00006600 代码段大小

000000F8        00005A00 已初始化数据大小

000000FC        00000000 未初始化数据大小

00000100        00006420 登录指示 RVA

00000104        00001000 代码基部

00000108        00008000 数据基部

// NT附加域--------------------------

0000010C        01000000 映像基数

00000110        00001000 区段队列

00000114        00000200 文件队列

00000118        0005             操作系统主版本

0000011A        0000             操作系统副版本

0000011C        0005             用户主版本

0000011E        0000             用户副版本

00000120        0004             子系统主版本

00000122        0000             子系统副版本

00000124        00000000 已保留

00000128        00010000 映像大小

0000012C        00000600 标头大小

00000130        0001102A 文件校验

00000134        0002             子系统: 1=本地,2=Windows GUI3=Windows CUI4=POSIX CUI

00000136        8000             DLL 标记 (废弃)

00000138        00040000 堆栈已保留大小

0000013C        00001000 堆栈提交大小

00000140        00100000 堆积保留大小

00000144        00001000 堆积提交大小

00000148        00000000 加载器标记 (废弃)

0000014C        00000010 数据目录数

00000150        00000000 输出表地址

00000154        00000000 & 大小

00000158        00006650 输入表地址

0000015C        000000B4 & 大小

00000160        0000A000 资源表地址

00000164        000045E4 & 大小

00000168        00000000 异常表地址

0000016C        00000000 & 大小

00000170        00000000 安全表地址

00000174        00000000 & 大小

00000178        00000000 基部重定位表地址

0000017C        00000000 & 大小

00000180        00001300 调试数据地址

00000184        0000001C & 大小

00000188        00000000 版权数据地址

0000018C        00000000 & 大小

00000190        00000000 全局 Ptr

00000194        00000000 & 大小

00000198        00000000 TLS 表地址

0000019C        00000000 & 大小

000001A0        00000000 载入配置表地址

000001A4        00000000 & 大小

最后一部分,各个段
    段包含了文件的内容,包括代码、数据、资源以及其它可执行信息,每个段都有一个头部和一个实体(原始数据)。我将在下面描述段头部的有关信息,但是段实体则缺少一个严格的文件结构。因此,它们几乎可以被链接器按任何的方法组织,只要它的头部填充了足够能够解释数据的信息。每个段头部为40个字节长,并且没有任何的填充信息。它的定义如下:

#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER
{
       UCHAR Name[IMAGE_SIZEOF_SHORT_NAME];  //每个段都有一个8字符长的名称域,并且第一个字符必须是一个句点
       union                                 //nion域,现在已不使用了
       {                                    
              ULONG PhysicalAddress;           
              ULONG VirtualSize;               
       }Misc;                               
       ULONG  VirtualAddress;                //标识了进程地址空间中要装载这个段的虚拟地址
       ULONG  SizeOfRawData;                 //标识了相对FileAlignment的段实体尺寸
       ULONG  PointerToRawData;              //标识了文件中段实体位置的偏移量
       ULONG  PointerToRelocations;          //在PE格式中不使用
       ULONG  PointerToLinenumbers;          //在PE格式中不使用
       USHORT NumberOfRelocations;           //在PE格式中不使用
       USHORT NumberOfLinenumbers;           //在PE格式中不使用
       ULONG  Characteristics;               //定义了段的特征
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
   整个格式的组成:一个MS-DOS的MZ头部,之后是一个实模式的残余程序、PE文件标志、PE文件头部、PE可选头部、
所有的段头部,最后是所有的段实体。 可选头部的末尾是一个数据目录入口的数组,这些相对虚拟地址指向段实体
之中的数据目录。每个数据目录都表示了一个特定的段实体数据是如何组织的。PE文件格式有11个预定义段,这是
对Windows NT应用程序所通用的,但是每个应用程序可以为它自己的代码以及数据定义它自己独特的段。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值