PE格式全分析

我们知道,很多PE分析工具都可以查看一个EXE文件的引用DLL文件函数表,其实,这个本身就是存储在EXE头部的一个重要信息,今天,我们就来研究一下:

我们借用一张PE结构图来分析:

PE格式全分析_第1张图片

一个EXE完整的PE结构分五大部分。见上图.

最开头的是部分是DOS部首,DOS部首由两部分组成:DOS的MZ文件标志和DOS stub(DOS存根程序)。之所以设置DOS部首是微软为了兼容原有的DOS系统下的程序而设立的。紧接着的是真正的PE文件头。值得注意的是PE文件头中的IMAGE_OPTIONAL_HEADER32是一个非常重要的结构,PE文件中的导入表、导出表、资源、重定位表等数据的位置和长度都保存在这个结构里。

我们按照顺序,先说说IMAGE_FILE_HEADER。

IMAGE_FILE_HEADER这个结构的定义如下:

view source
print ?
01. typedef struct _IMAGE_FILE_HEADER {
02.  00h   WORD    Machine;                   //运行平台
03.  02h   WORD    NumberOfSections;          //区块数目
04.  06h   DWORD   TimeDateStamp;            //文件日期时间戳
05.  0Ah   DWORD   PointerToSymbolTable;      //指向符号表
06.  0Eh   DWORD   NumberOfSymbols;          //符号表中的符号数量
07.  12h   WORD    SizeOfOptionalHeader;      //映像可选头结构的大小
08.  14h   WORD    Characteristics;          //文件特征值
09. } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

这个结构体表明一个PE文件的基本特征属性,也是一个PE文件的入口

Machine域说明这个pe文件在什么CPU上运行,具体如下:

    #define IMAGE_FILE_MACHINE_UNKNOWN     0
    #define IMAGE_FILE_MACHINE_I386        0x014c  // Intel 386.
    #define IMAGE_FILE_MACHINE_R3000       0x0162  // MIPS little-endian, 0x160 big-endian
    #define IMAGE_FILE_MACHINE_R4000       0x0166  // MIPS little-endian
    #define IMAGE_FILE_MACHINE_R10000      0x0168  // MIPS little-endian
    #define IMAGE_FILE_MACHINE_WCEMIPSV2   0x0169  // MIPS little-endian WCE v2
    #define IMAGE_FILE_MACHINE_ALPHA       0x0184  // Alpha_AXP
    #define IMAGE_FILE_MACHINE_POWERPC     0x01F0  // IBM PowerPC Little-Endian
    #define IMAGE_FILE_MACHINE_SH3         0x01a2  // SH3 little-endian
    #define IMAGE_FILE_MACHINE_SH3E        0x01a4  // SH3E little-endian
    #define IMAGE_FILE_MACHINE_SH4         0x01a6  // SH4 little-endian
    #define IMAGE_FILE_MACHINE_ARM         0x01c0  // ARM Little-Endian
    #define IMAGE_FILE_MACHINE_THUMB       0x01c2
    #define IMAGE_FILE_MACHINE_IA64        0x0200  // Intel 64
    #define IMAGE_FILE_MACHINE_MIPS16      0x0266  // MIPS
    #define IMAGE_FILE_MACHINE_MIPSFPU     0x0366  // MIPS
    #define IMAGE_FILE_MACHINE_MIPSFPU16   0x0466  // MIPS
    #define IMAGE_FILE_MACHINE_ALPHA64     0x0284  // ALPHA64
    #define IMAGE_FILE_MACHINE_AXP64       IMAGE_FILE_MACHINE_ALPHA64
  

NumberOfSections
    pe文件中区块的数量.
  
TimeDateStamp
    文件日期时间戳,指这个pe文件生成的时间,它的值是从1969年12月31日16:00:00以来的秒数.
  
PointerToSymbolTable
    Coff调试符号表的偏移地址.
  
NumberOfSymbols
    Coff符号表中符号的个数. 这个域和前个域在release版本的程序里是0.
  
SizeOfOptionalHeader
    IMAGE_OPTIONAL_HEADER32结构的大小(即多少字节).我们接着就要提到这个结构了.事实上,pe文件的大部分重要的域都在IMAGE_OPTIONAL_HEADER结构里.
  
Characteristics
    这个域描述pe文件的一些属性信息,比如是否可执行,是否是一个动态连接库等.具体定义如下:

#define IMAGE_FILE_RELOCS_STRIPPED           0x0001  // 重定位信息被移除
#define IMAGE_FILE_EXECUTABLE_IMAGE          0x0002  // 文件可执行
#define IMAGE_FILE_LINE_NUMS_STRIPPED        0x0004  // 行号被移除
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED       0x0008  // 符号被移除
#define IMAGE_FILE_AGGRESIVE_WS_TRIM         0x0010  // Agressively trim working set
#define IMAGE_FILE_LARGE_ADDRESS_AWARE       0x0020  // 程序能处理大于2G的地址
#define IMAGE_FILE_BYTES_REVERSED_LO         0x0080  // Bytes of machine word are reversed.
#define IMAGE_FILE_32BIT_MACHINE             0x0100  // 32位机器
#define IMAGE_FILE_DEBUG_STRIPPED            0x0200  // .dbg文件的调试信息被移除
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP   0x0400  // 如果在移动介质中,拷到交换文件中运行
#define IMAGE_FILE_NET_RUN_FROM_SWAP         0x0800  // 如果在网络中,拷到交换文件中运行
#define IMAGE_FILE_SYSTEM                    0x1000  // 系统文件
#define IMAGE_FILE_DLL                       0x2000  // 文件是一个dll
#define IMAGE_FILE_UP_SYSTEM_ONLY            0x4000  // 文件只能运行在单处理器上
#define IMAGE_FILE_BYTES_REVERSED_HI         0x8000  // Bytes of machine word are reversed.
 

 

所以,根据这个结构体的信息,我们就可以判断一个文件究竟是不是一个真正的PE文件,该PE文件的类型是可执行的还是可调用的(DLL)

我们可以写个简单的小程序来读取这个信息:

view source
print ?
01. #include "stdafx.h"
02. #include "windows.h"
03. #include "stdio.h"
04. #include "conio.h"
05.   
06. int main(int argc, char* argv[])
07. {
08.         FILE *p;
09.         LONG e_lfanew;  //指向IMAGE_NT_HEADERS32结构在文件中的偏移
10.         IMAGE_FILE_HEADER myfileheader;
11.          
12.         p = fopen("test1.exe","r+b");//自定义读取的exe文件
13.         if(p == NULL)return -1;//如果打开失败就返回
14.   
15.         fseek(p,0x3c,SEEK_SET);//注意这里是指针偏移,也就是绕过开头的DOS区块
16.         fread(&e_lfanew,4,1,p);
17.         fseek(p,e_lfanew+4,SEEK_SET);  //指向IMAGE_FILE_HEADER结构的偏移
18.         fread(&myfileheader,sizeof(myfileheader),1,p);
19.   
20.         printf("IMAGE_FILE_HEADER结构:\n");
21.         printf("Machine              : %04X\n",myfileheader.Machine);
22.         printf("NumberOfSections     : %04X\n",myfileheader.NumberOfSections);
23.         printf("TimeDateStamp        : %08X\n",myfileheader.TimeDateStamp);
24.         printf("PointerToSymbolTable : %08X\n",myfileheader.PointerToSymbolTable);
25.         printf("NumberOfSymbols      : %08X\n",myfileheader.NumberOfSymbols);
26.         printf("SizeOfOptionalHeader : %04X\n",myfileheader.SizeOfOptionalHeader);
27.         printf("Characteristics      : %04X\n",myfileheader.Characteristics);
28.         getch();
29.         return 0;
30. }

注释比较详细了,大家根据这个就可以读取一个PE文件的基本特征信息了.以上代码VC6编译通过

紧接着上一节,我们来研究下IMAGE_OPTIONAL_HEADER32,这个属于PE中附加结构信息,同样是很重要的。

我们先来看看它的结构:

view source
print ?
01. typedef struct _IMAGE_OPTIONAL_HEADER {
02.     //
03.     // Standard fields.
04.     //
05.   
06.     00h WORD    Magic;                   //幻数,32位pe文件总为010bh
07.     02h BYTE    MajorLinkerVersion;      //连接器主版本号
08.     03h BYTE    MinorLinkerVersion;      //连接器副版本号
09.     04h DWORD   SizeOfCode;              //代码段总大小
10.     08h DWORD   SizeOfInitializedData;   //已初始化数据段总大小
11.     0ch DWORD   SizeOfUninitializedData; //未初始化数据段总大小
12.     10h DWORD   AddressOfEntryPoint;     //程序执行入口地址(RVA)
13.     14h DWORD   BaseOfCode;              //代码段起始地址(RVA)
14.     18h DWORD   BaseOfData;              //数据段起始地址(RVA)
15.   
16.     //
17.     // NT additional fields.
18.     //
19.   
20.     1ch DWORD   ImageBase;               //程序默认的装入起始地址
21.     20h DWORD   SectionAlignment;        //内存中区块的对齐单位
22.     24h DWORD   FileAlignment;           //文件中区块的对齐单位
23.     28h WORD    MajorOperatingSystemVersion; //所需操作系统主版本号
24.     2ah WORD    MinorOperatingSystemVersion; //所需操作系统副版本号
25.     2ch WORD    MajorImageVersion;       //自定义主版本号
26.     2eh WORD    MinorImageVersion;       //自定义副版本号
27.     30h WORD    MajorSubsystemVersion;   //所需子系统主版本号
28.     32h WORD    MinorSubsystemVersion;   //所需子系统副版本号
29.     34h DWORD   Win32VersionValue;       //总是0
30.     38h DWORD   SizeOfImage;             //pe文件在内存中的映像总大小
31.     3ch DWORD   SizeOfHeaders;           //从pe文件开始到节表(包含节表)的总大小
32.     40h DWORD   CheckSum;                //pe文件CRC校验和
33.     44h WORD    Subsystem;               //用户界面使用的子系统类型
34.     46h WORD    DllCharacteristics;      //为0
35.     48h DWORD   SizeOfStackReserve;      //为线程的栈初始保留的虚拟内存的默认值
36.     4ch DWORD   SizeOfStackCommit;       //为线程的栈初始提交的虚拟内存的大小
37.     50h DWORD   SizeOfHeapReserve;       //为进程的堆保留的虚拟内存的大小
38.     54h DWORD   SizeOfHeapCommit;        //为进程的堆初始提交的虚拟内存的大小
39.     58h DWORD   LoaderFlags;             //为0
40.     5ch DWORD   NumberOfRvaAndSizes;     //数据目录结构数组的项数,总为 00000010h
41.     60h IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];  //数据目录结构数组
42. } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

这个结构体非常的庞大,大家通过注释可以看出,这个结构体保存了相当全面的PE附件信息。

下面针对重要内容进行一个解释:

Magic 幻数,32位pe文件总为010bh
这个常数的定义如下:


  #define IMAGE_NT_OPTIONAL_HDR32_MAGIC      0x10b
  #define IMAGE_NT_OPTIONAL_HDR64_MAGIC      0x20b
  #define IMAGE_ROM_OPTIONAL_HDR_MAGIC       0x107
 

MajorLinkerVersion 连接程序的主版本号 如vc6.0的为06h

MinorLinkerVersion 连接程序的次版本号 如vc6.0的为00h

SizeOfCode pe文件代码段的大小.是FileAlignment的整数倍.

SizeOfInitializedData 所有含已初始化数据的块的大小,一般在.data段中.

SizeOfUninitializedData 所有含未初始化数据的块的大小,一般在.bss段中.

AddressOfEntryPoint 程序开始执行的地址,这是一个RVA(相对虚拟地址).对于exe文件,这里是启动代码;对于dll文件,这里是libMain()的地址.
     在脱壳时第一件事就是找入口点,指的就是这个值.
    
BaseOfCode 代码段基地址,微软的连接程序生成的程序一般把这个值置为1000h,  
    
BaseOfData 数据段基地址

ImageBase pe文件默认的装入地址.windows9x中exe文件为400000h,dll文件为10000000h.

SectionAlignment 内存中区块的对齐单位.区块总是对齐到这个值的整数倍.x86的32位系统上默认值位1000h

FileAlignment pe文件中区块的对齐单位.pe文件中默认值为 200h.

MajorOperatingSystemVersion
MinorOperatingSystemVersion

    上面两个域是指运行这个pe文件所需的操作系统的最低版本号.windows95/98和windows nt 4.0 的内部版本号都是 4.0 ,而windows2000的内部版本号是5.0
  
MajorImageVersion
MinorImageVersion

    上面两个域是指用户自定义的pe文件的版本号.可以通过连接程序来设置,如: LINK /VERSION:2.0 MyApp.obj一般在升级时使用.
  
MajorSubsystemVersion  
MinorSubsystemVersion

   上面两个域是指运行这个pe文件所要求的子系统的版本号. 
  
Win32VersionValue 总是0

SizeOfImage pe文件装入内存后映像的总大小.如果SectionAlignment域和FileAlignment域相等,那么这个值也是pe文件在硬盘上的大小.
  
SizeOfHeaders 从文件开始到节表(包含节表)的总大小.其后是各个区段的数据.              

CheckSum pe文件的CRC校验和.

Subsystem pe文件的用户界面使用的子系统类型.定义如下:

#define IMAGE_SUBSYSTEM_UNKNOWN              0   // Unknown subsystem.
#define IMAGE_SUBSYSTEM_NATIVE               1   // Image doesn't require a subsystem.
#define IMAGE_SUBSYSTEM_WINDOWS_GUI          2   // Image runs in the Windows GUI subsystem.
#define IMAGE_SUBSYSTEM_WINDOWS_CUI          3   // Image runs in the Windows character subsystem.
#define IMAGE_SUBSYSTEM_OS2_CUI              5   // image runs in the OS/2 character subsystem.
#define IMAGE_SUBSYSTEM_POSIX_CUI            7   // image runs in the Posix character subsystem.
#define IMAGE_SUBSYSTEM_NATIVE_WINDOWS       8   // image is a native Win9x driver.
#define IMAGE_SUBSYSTEM_WINDOWS_CE_GUI       9   // Image runs in the Windows CE subsystem.


DllCharacteristics 总为0

SizeOfStackReserve 为线程的栈初始保留的虚拟内存的大小,默认为00100000h.如果在调用CreateThread函数时指定堆栈的大小为0,被创建的线程的堆栈的初始大小就与这个值相同.

SizeOfStackCommit 为线程的栈初始提交的虚拟内存的大小.微软的连接程序把这个值置为 1000h.
  

SizeOfHeapReserve 为进程的堆保留的虚拟内存的大小.默认值为 00100000h.

SizeOfHeapCommit  为进程的堆初始提交的虚拟内存的大小.微软的连接程序把这个值置为1000h.
  
LoaderFlags 通常为0

NumberOfRvaAndSizes 数据目录结构数组的项数,总为 00000010h
   这个值定义如下:
   #define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16
  
IMAGE_DATA_DIRECTORY DataDirectory[0x10] 数据目录结构数组
   IMAGE_DATA_DIRECTORY结构定义如下:

view source
print ?
1. typedef struct _IMAGE_DATA_DIRECTORY {
2.     DWORD   VirtualAddress;// 相对虚拟地址
3.     DWORD   Size;           //大小
4. } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;


这个结构包含了pe文件中重要部分的RVA地址和大小.这个数组使操作系统的加载程序能够快速定位特定的区段.具体定义如下:

 

#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
//      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor

 通过上一节的例子。我们也依旧可以从这个结构体中读出需要的信息.

你可能感兴趣的:(PE格式全分析)