PE只是学习资料

来源:互联网 发布:删除的数据怎么恢复 编辑:程序博客网 时间:2024/05/13 02:00

转帖处:http://www.netcicala.com/article/sort013/info-102.html 

作者:佚名  来源:不详  发布时间:2007-6-6 16:11:37

 

PE知识学习(一)


  PE 文件的知识是基本的知识.网上有很多这方面的资料.然而系统讲解的却不多.我不是这方面的专家,却希望能抛砖引玉,得到这方面的一些指点.

  计算机这门科学是实践性很强的一门学问,如果想扎扎实实的学会点东西,还是要亲自动手试一试.
  在继续向下看之前,我假定你会基本的C语言和简单的使用VC6.0,以下的例子都用用到这些.除此之外,不再做任何假设.
  关于pe的一些结构可以在winnt.h这个头文件里找到.
 
  声明一下:这里所有的结构及常量定义都是基于intel的x86 CPU的,在其他的系统上可能有所不同,你应该去查看相应的资料.关于这点以后不再声明.
 
  首先在pe文件的开始是这样一个结构(为了方便阅读,我加上了字节偏移):
 
  typedef struct _IMAGE_DOS_HEADER {   // DOS .EXE header
00h  WORD  e_magic;   // Magic number  **DOS头标记
02h  WORD  e_cblp;   // Bytes on last page of file
04h  WORD  e_cp;    // Pages in file
06h  WORD  e_crlc;   // Relocations
08h  WORD  e_cparhdr;  // Size of header in paragraphs
0ah  WORD  e_minalloc; // Minimum extra paragraphs needed
0ch  WORD  e_maxalloc; // Maximum extra paragraphs needed
0eh  WORD  e_ss;    // Initial (relative) SS value
10h  WORD  e_sp;    // Initial SP value
12h  WORD  e_csum;   // Checksum
14h  WORD  e_ip;    // Initial IP value
16h  WORD  e_cs;    // Initial (relative) CS value
18h  WORD  e_lfarlc;  // File address of relocation table
1ah  WORD  e_ovno;   // Overlay number
1ch  WORD  e_res[4];  // Reserved words
24h  WORD  e_oemid;   // OEM identifier (for e_oeminfo)
26h  WORD  e_oeminfo;  // OEM information; e_oemid specific
28h  WORD  e_res2[10]; // Reserved words
3ch  LONG  e_lfanew;  // File address of new exe header **指向PE头部
 } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
 
  这个结构就是DOS MZ头,是为了向下兼容的.当在DOS下运行windows程序不至于出错.
  我们只关心两个域:e_magic 和e_lfanew.
   e_magic 的值应该等于0x5A4D,像下面定义这样的:
       #define IMAGE_DOS_SIGNATURE  0x5A4D   // MZ
      
   e_lfanew是一个指针,指向PE文件头在PE文件中的偏移.
  
   PE文件头是这样一个结构,它包含了许多PE装载器要用到的域.
   typedef struct _IMAGE_NT_HEADERS {
     DWORD Signature;             **PE文件标识
     IMAGE_FILE_HEADER FileHeader;      **映像文件头
     IMAGE_OPTIONAL_HEADER32 OptionalHeader; **映像可选头
   } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
  
   Signature的定义如下:
   #define IMAGE_NT_SIGNATURE  0x00004550 // PE00
  
   好了,让我们实际做点事.写个小程序查看一下PE文件的这些信息,并判断一个PE文件是否有效.
   代码如下,你可以在VC中建一个控制台程序,然后把这段代码拷进去(我建议你手工输入.什么?你不愿意?哈哈,我可是一个一个字母敲进去的,你比我还懒:) )
  
#include "stdafx.h"
#include "windows.h"
#include "stdio.h"

int main(int argc, char* argv[])
{
    FILE *p;
    IMAGE_DOS_HEADER mydosheader;
    unsigned long sig;

    p = fopen("test1.exe","r+b");
    if(p == NULL)return -1;

    fread(&mydosheader,sizeof(mydosheader),1,p);
    fseek(p,mydosheader.e_lfanew,SEEK_SET);
    fread(&sig,4,1,p);
    fclose(p);

    printf("IMAGE_DOS_HEADER dump:/n");
    printf("e_magic  : %04x/n",mydosheader.e_magic);
    printf("e_cblp  : %04x/n",mydosheader.e_cblp);
    printf("e_cp   : %04x/n",mydosheader.e_cp);
    printf("e_crlc  : %04x/n",mydosheader.e_crlc);
    printf("e_cparhdr : %04x/n",mydosheader.e_cparhdr);
    printf("e_minalloc: %04x/n",mydosheader.e_minalloc);
    printf("e_maxalloc: %04x/n",mydosheader.e_maxalloc);
    printf("e_ss   : %04x/n",mydosheader.e_ss);
    printf("e_sp   : %04x/n",mydosheader.e_sp);
    printf("e_csum  : %04x/n",mydosheader.e_csum);
    printf("e_ip   : %04x/n",mydosheader.e_ip);
    printf("e_cs   : %04x/n",mydosheader.e_cs);
    printf("e_lfarlc : %04x/n",mydosheader.e_lfarlc);
    printf("e_ovno  : %04x/n",mydosheader.e_ovno);
    printf("e_res[0] : %04x/n",mydosheader.e_res[0]);
    printf("e_oemid  : %04x/n",mydosheader.e_oemid);
    printf("e_oeminfo : %04x/n",mydosheader.e_oeminfo);
    printf("res2[0]  : %04x/n",mydosheader.e_res2[0]);
    printf("lfanew  : %08x/n",mydosheader.e_lfanew);


    if((mydosheader.e_magic ==IMAGE_DOS_SIGNATURE) &&
        (sig == IMAGE_NT_SIGNATURE))
       printf("有效的PE文件/n");
    else
      printf("无效的PE文件/n");
    return 0;
}

运行附件中dump1.exe请在dos窗口下.


PE知识学习(二)


  上一贴我们了解了pe头部的dos部首部分,我们知道在这个结构里e_magic和e_lfanew这两个域对我们来说很重要.同时我们也提到了e_lfanew域指向IMAGE_NT_HEADERS32结构在pe文件的偏移.
  补充声明一下:这里的知识是适用于32位字的机器上的.
 
  下面我们接着看IMAGE_NT_HEADERS32结构,这个部分在pe文件的学习里至关重要.
 
  IMAGE_NT_HEADERS32的结构定义如下:
 
  typedef struct _IMAGE_NT_HEADERS {
     DWORD Signature;             **PE文件标识 "PE",0,0
     IMAGE_FILE_HEADER FileHeader;      **映像文件头
     IMAGE_OPTIONAL_HEADER32 OptionalHeader; **映像可选头
   } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
  
  这其中又包含了两个结构.我们一点一点的往下看.
  IMAGE_FILE_HEADER这个结构的定义如下:
  typedef struct _IMAGE_FILE_HEADER {
   00h  WORD  Machine;          **运行平台
   02h  WORD  NumberOfSections;     **区块数目
   06h  DWORD  TimeDateStamp;       **文件日期时间戳
   0Ah  DWORD  PointerToSymbolTable;   **指向符号表
   0Eh  DWORD  NumberOfSymbols;      **符号表中的符号数量
   12h  WORD  SizeOfOptionalHeader;   **映像可选头结构的大小
   14h  WORD  Characteristics;      **文件特征值
  } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
 
  我们看一下这几个域:
 
  1) 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
 
  2) NumberOfSections
  pe文件中区块的数量,关于区块下面还要讲到,这里先有个印象就可以了.
 
  3)TimeDateStamp
  文件日期时间戳,指这个pe文件生成的时间,它的值是从1969年12月31日16:00:00以来的秒数.
 
  4)PointerToSymbolTable
  Coff调试符号表的偏移地址.
 
  5)NumberOfSymbols
  Coff符号表中符号的个数. 这个域和前个域在release版本的程序里是0.
 
  6)SizeOfOptionalHeader
  IMAGE_OPTIONAL_HEADER32结构的大小(即多少字节).我们接着就要提到这个结构了.事实上,pe文件的大部分重要的域都在IMAGE_OPTIONAL_HEADER结构里.
 
  7)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文件的特征值就是这些属性值加在一起的.
 
  希望这些没有让你头晕,其实内容不多,只是一个IMAGE_FILE_HEADER结构,而这个结构包含7个域而已.
  让我们先熟悉这个结构,我们编个程序来显示这些信息.

#include "stdafx.h"
#include "windows.h"
#include "stdio.h"
#include "conio.h"

int main(int argc, char* argv[])
{
    FILE *p;
    LONG e_lfanew; //指向IMAGE_NT_HEADERS32结构在文件中的偏移
    IMAGE_FILE_HEADER myfileheader;
   
    p = fopen("test1.exe","r+b");
    if(p == NULL)return -1;

    fseek(p,0x3c,SEEK_SET);
    fread(&e_lfanew,4,1,p);
    fseek(p,e_lfanew+4,SEEK_SET); //指向IMAGE_FILE_HEADER结构的偏移
    fread(&myfileheader,sizeof(myfileheader),1,p);

    printf("IMAGE_FILE_HEADER结构:/n");
    printf("Machine       : %04X/n",myfileheader.Machine);
    printf("NumberOfSections   : %04X/n",myfileheader.NumberOfSections);
    printf("TimeDateStamp    : %08X/n",myfileheader.TimeDateStamp);
    printf("PointerToSymbolTable : %08X/n",myfileheader.PointerToSymbolTable);
    printf("NumberOfSymbols   : %08X/n",myfileheader.NumberOfSymbols);
    printf("SizeOfOptionalHeader : %04X/n",myfileheader.SizeOfOptionalHeader);
    printf("Characteristics   : %04X/n",myfileheader.Characteristics);
    getch();
    return 0;
}
 
此程序在win98 + vc6.0 环境下编译通过.


pe知识学习(三)



  前两个贴子我们已经介绍了pe文件的两个结构,希望还没有让你看晕.下面我把pe文件的结构列出来,让我们有个全局的印象.
  _______________________________
  |  IMAGE_DOS_HEADER     |    <-- Dos部首
  -------------------------------
  |  PE,0,0         |    <-- PE文件标志
  -------------------------------
  |  IMAGE_FILE_HEADER     |    <-- 映像文件头
  -------------------------------
  |  IMAGE_OPTIONAL_HEADER32  |    <-- 映像可选头
  -------------------------------
  |  Section Table       |    <-- 节表
  -------------------------------
  |  .text           |    <-- 代码区段
  -------------------------------
  |  .data           |    <-- 数据区段
  -------------------------------
  |  .idata          |    <-- 输入表
  -------------------------------
  |  .edata          |    <-- 输出表
  -------------------------------
  |  .reloc          |    <-- 重定位表区段
  -------------------------------
  |  ....           |
  -------------------------------
  |  调试信息         |
  -------------------------------
 
  好了,我们接着看看IMAGE_OPTIONAL_HEADER32结构.这个结构的域比较多,但是和后面要讲到的节表一样,非常重要.希望你能够用心体会,并动手实践一下.
  IMAGE_OPTIONAL_HEADER32的结构定义如下:
 
  typedef struct _IMAGE_OPTIONAL_HEADER {
  //
  // Standard fields.
  //

  00h WORD  Magic;          //幻数,32位pe文件总为010bh
  02h BYTE  MajorLinkerVersion;   //连接器主版本号
  03h BYTE  MinorLinkerVersion;   //连接器副版本号
  04h DWORD  SizeOfCode;       //代码段总大小
  08h DWORD  SizeOfInitializedData;  //已初始化数据段总大小
  0ch DWORD  SizeOfUninitializedData; //未初始化数据段总大小
  10h DWORD  AddressOfEntryPoint;   //程序执行入口地址(RVA)
  14h DWORD  BaseOfCode;       //代码段起始地址(RVA)
  18h DWORD  BaseOfData;       //数据段起始地址(RVA)

  //
  // NT additional fields.
  //

  1ch DWORD  ImageBase;        //程序默认的装入起始地址
  20h DWORD  SectionAlignment;    //内存中区块的对齐单位
  24h DWORD  FileAlignment;      //文件中区块的对齐单位
  28h WORD  MajorOperatingSystemVersion; //所需操作系统主版本号
  2ah WORD  MinorOperatingSystemVersion; //所需操作系统副版本号
  2ch WORD  MajorImageVersion;    //自定义主版本号
  2eh WORD  MinorImageVersion;    //自定义副版本号
  30h WORD  MajorSubsystemVersion;  //所需子系统主版本号
  32h WORD  MinorSubsystemVersion;  //所需子系统副版本号
  34h DWORD  Win32VersionValue;    //总是0
  38h DWORD  SizeOfImage;       //pe文件在内存中的映像总大小
  3ch DWORD  SizeOfHeaders;      //从pe文件开始到节表(包含节表)的总大小
  40h DWORD  CheckSum;        //pe文件CRC校验和
  44h WORD  Subsystem;        //用户界面使用的子系统类型
  46h WORD  DllCharacteristics;   //为0
  48h DWORD  SizeOfStackReserve;   //为线程的栈初始保留的虚拟内存的默认值
  4ch DWORD  SizeOfStackCommit;    //为线程的栈初始提交的虚拟内存的大小
  50h DWORD  SizeOfHeapReserve;    //为进程的堆保留的虚拟内存的大小
  54h DWORD  SizeOfHeapCommit;    //为进程的堆初始提交的虚拟内存的大小
  58h DWORD  LoaderFlags;       //为0
  5ch DWORD  NumberOfRvaAndSizes;   //数据目录结构数组的项数,总为 00000010h
  60h IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
                   //数据目录结构数组
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;


  下面再具体解释一下各个域的含义.不要闲罗嗦,后面这些知识实在是太重要了.
1)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
 
2)MajorLinkerVersion 连接程序的主版本号 如vc6.0的为06h

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

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

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

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

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

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

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

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

13)MajorOperatingSystemVersion
14)MinorOperatingSystemVersion
  上面两个域是指运行这个pe文件所需的操作系统的最低版本号.windows95/98和windows nt 4.0 的内部版本号都是 4.0 ,而windows2000的内部版本号是5.0
 
15)MajorImageVersion
16)MinorImageVersion
  上面两个域是指用户自定义的pe文件的版本号.可以通过连接程序来设置,如: LINK /VERSION:2.0 MyApp.obj一般在升级时使用.
 
17)MajorSubsystemVersion 
18)MinorSubsystemVersion
  上面两个域是指运行这个pe文件所要求的子系统的版本号. 
 
19)Win32VersionValue 总是0

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

22)CheckSum pe文件的CRC校验和.

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

#define IMAGE_SUBSYSTEM_UNKNOWN       0  // Unknown subsystem.
#define IMAGE_SUBSYSTEM_NATIVE        1  // Image doesnt 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.

24)DllCharacteristics 总为0

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

26)SizeOfStackCommit 为线程的栈初始提交的虚拟内存的大小.微软的连接程序把这个值置为 1000h.
 
27)SizeOfHeapReserve 为进程的堆保留的虚拟内存的大小.默认值为 00100000h.

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

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

typedef struct _IMAGE_DATA_DIRECTORY {
  DWORD  VirtualAddress; 相对虚拟地址
  DWORD  Size;      大小
} 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知识学习(四)


  首先在这里感谢laoxuetong的支持,没有你我可能不会继续写下去了.
 
  下面我们要学习的可以说是pe文件的核心内容了.即块表(section table)和各种块(区段)的结构.这些内容比较多且长.因此我会把写完的先发出来,然后在慢慢的续完.希望你有耐心看下去.学习有时是很枯燥的.因此在适当的时候我也会给出点应用的实例.

  你可能还记得,区块的数量在IMAGE_FILE_HEADER结构的NumberOfSections域定义.好了,我们看看和区块密切相关的块表的结构定义.
  块表结构的定义如下:
 
#define IMAGE_SIZEOF_SHORT_NAME       8

typedef struct _IMAGE_SECTION_HEADER {
  00h BYTE  Name[IMAGE_SIZEOF_SHORT_NAME]; //块名,8个字节长
  08h union {
      DWORD  PhysicalAddress; //obj文件中,区段的实际地址
      DWORD  VirtualSize;  //exe和dll文件中区段在文件中对齐前的大小
    } Misc;
  0ch DWORD  VirtualAddress;     //块的RVA(相对虚拟地址)
  10h DWORD  SizeOfRawData;     //在文件中对齐后的大小
  14h DWORD  PointerToRawData;    //在文件中的偏移
  18h DWORD  PointerToRelocations;  //重定位的偏移(obj文件中使用)
  1ch DWORD  PointerToLinenumbers;  //行号表的偏移(调试用)
  1eh WORD  NumberOfRelocations;  //重定位项数目(obj文件中使用)
  20h WORD  NumberOfLinenumbers;  //行号表中行号的数目
  24h DWORD  Characteristics;    //块属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

  块表结构描述了区段的一些重要的属性,下面具体解释一下各个域的含义.
1)Name[8] 8个字节的区段名,不足8个后面用0补齐.

2)VirtualSize 在exe和dll文件中这个域包含的是区段还没有按FileAlignment域对齐前的大小.如果这个结构描述的是代码段,那么这个域的值就是实际的代码量的大小.在pe文件的diy时,这个域很有用.它指出了区段中有多少没有使用的空间.我们可以在没有使用的空间里插入自己的代码.好多病毒也是把代码插入剩余的空间里.(呵呵,不要学坏.)
 
3)VirtualAddress 在exe文件中,这个域是pe文件映射到虚拟内存后该区段的RVA地址.这个值加上基地址(IMAGE_OPTIONAL_HEADER32.ImageBase)后,就得到了该区段在内存中的实际起始地址.
 
4)SizeOfRawData 这个域是它描述的区段按IMAGE_OPTIONAL_HEADER32.FileAlignment域对齐后在文件中的大小.如果FileAlignment为 0200h,VirtualSize为035Ah,则这个值为 0400h.
 
5)PointerToRawData 它描述的区段的起始地址在pe文件中的偏移. 

6)PointerToRelocations
7)PointerToLinenumbers
8)NumberOfRelocations
9)NumberOfLinenumbers 
  上面这四个域在发行版本的程序里都是0.

10)Characteristics 该区段的属性信息.用于表示这个区段是代码、数据、可读、可写等等.
  这个域定义如下(重要的已经做了中文注释):

//   IMAGE_SCN_TYPE_REG         0x00000000 // Reserved.
//   IMAGE_SCN_TYPE_DSECT        0x00000001 // Reserved.
//   IMAGE_SCN_TYPE_NOLOAD       0x00000002 // Reserved.
//   IMAGE_SCN_TYPE_GROUP        0x00000004 // Reserved.
#define IMAGE_SCN_TYPE_NO_PAD       0x00000008 // Reserved.
//   IMAGE_SCN_TYPE_COPY        0x00000010 // Reserved.

#define IMAGE_SCN_CNT_CODE         0x00000020 // Section contains code.
                            //区段包含代码
#define IMAGE_SCN_CNT_INITIALIZED_DATA   0x00000040 // Section contains initialized data.
                            //区段包含已初始化数据
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA  0x00000080 // Section contains uninitialized data.
                            //区段包含未初始化数据
#define IMAGE_SCN_LNK_OTHER        0x00000100 // Reserved.
#define IMAGE_SCN_LNK_INFO         0x00000200 // Section contains comments
                            // or some other type of information.
//   IMAGE_SCN_TYPE_OVER        0x00000400 // Reserved.
#define IMAGE_SCN_LNK_REMOVE        0x00000800 // Section contents will not become part of image.
#define IMAGE_SCN_LNK_COMDAT        0x00001000 // Section contents comdat.
//                     0x00002000 // Reserved.
//   IMAGE_SCN_MEM_PROTECTED - Obsolete 0x00004000
#define IMAGE_SCN_NO_DEFER_SPEC_EXC    0x00004000 // Reset speculative exceptions handling bits
                            // in the TLB entries for this section.
#define IMAGE_SCN_GPREL          0x00008000 // Section content can be accessed relative to GP
#define IMAGE_SCN_MEM_FARDATA       0x00008000
//   IMAGE_SCN_MEM_SYSHEAP - Obsolete 0x00010000
#define IMAGE_SCN_MEM_PURGEABLE      0x00020000
#define IMAGE_SCN_MEM_16BIT        0x00020000
#define IMAGE_SCN_MEM_LOCKED        0x00040000
#define IMAGE_SCN_MEM_PRELOAD       0x00080000

#define IMAGE_SCN_ALIGN_1BYTES       0x00100000 //
#define IMAGE_SCN_ALIGN_2BYTES       0x00200000 //
#define IMAGE_SCN_ALIGN_4BYTES       0x00300000 //
#define IMAGE_SCN_ALIGN_8BYTES       0x00400000 //
#define IMAGE_SCN_ALIGN_16BYTES      0x00500000 // Default alignment if no others are specified.
#define IMAGE_SCN_ALIGN_32BYTES      0x00600000 //
#define IMAGE_SCN_ALIGN_64BYTES      0x00700000 //
#define IMAGE_SCN_ALIGN_128BYTES      0x00800000 //
#define IMAGE_SCN_ALIGN_256BYTES      0x00900000 //
#define IMAGE_SCN_ALIGN_512BYTES      0x00A00000 //
#define IMAGE_SCN_ALIGN_1024BYTES     0x00B00000 //
#define IMAGE_SCN_ALIGN_2048BYTES     0x00C00000 //
#define IMAGE_SCN_ALIGN_4096BYTES     0x00D00000 //
#define IMAGE_SCN_ALIGN_8192BYTES     0x00E00000 //
// Unused                 0x00F00000

#define IMAGE_SCN_LNK_NRELOC_OVFL     0x01000000 // Section contains extended relocations.
#define IMAGE_SCN_MEM_DISCARDABLE     0x02000000 // Section can be discarded.
                            //该区段可丢弃
#define IMAGE_SCN_MEM_NOT_CACHED      0x04000000 // Section is not cachable.
#define IMAGE_SCN_MEM_NOT_PAGED      0x08000000 // Section is not pageable.
#define IMAGE_SCN_MEM_SHARED        0x10000000 // Section is shareable.
                            //该区段可共享
#define IMAGE_SCN_MEM_EXECUTE       0x20000000 // Section is executable.
                            //该区段可执行
#define IMAGE_SCN_MEM_READ         0x40000000 // Section is readable.
                            //该区段可读
#define IMAGE_SCN_MEM_WRITE        0x80000000 // Section is writeable.
                            //该区段可写
                           
  下面我们看一下pe文件里常用的一下区段:
1).text
  code
  这里一般放的是代码.
 
2).data
  这里一般放的是已初始化的数据.
 
3).idata
  这里一般放的是输入表.这个后面还要详细讲.
 
4).rsrc
  这里一般放的是资源.
 
5).reloc
  这里一般放的是基地址重定位表.

6).edata
  这里一般放的是输出表.
 
7).tls
  这里一般是线程局部存储数据.
 
8).bbs
  这里一般放的是未初始化的数据.

pe知识学习(五)


  从这贴开始,我介绍几个常用的区段:输入表,输出表和重定位表.
 
  我们知道,程序调用外部的dll函数通常都是下面这种形式:
  call my_label
  ...
my_label: jmp dword ptr [xxxxxxxx]
  对一个dll中的函数的调用总是通过一个地址间接的调用的.这些地址就放在输入表里.
   
  输入表(Import Table),简而言之,就是描述该pe文件从哪几个动态连接库导入了什么函数的一组结构数组.在这里我希望能用最简洁的语言让你明白什么是输入表.输入表的组成并不复杂,只用到三个结构.它们是:IMAGE_IMPORT_DESCRIPTOR,IMAGE_THUNK_DATA,IMAGE_IMPORT_BY_NAME.
  我们先看一下框图.
 
             IMAGE_IMPORT_DESCRIPTOR
             |--------------------|
|-------------------------| OriginalFirstThunk |
|             |--------------------|
|             |  TimeDateStamp  |
|             |--------------------|
|             |  ForwarderChain  |
|             |--------------------|
|             |    Name     |----> "USER32.DLL"
|             |--------------------|
|             |   FirstThunk   |---------------------------|
|             |--------------------|              |
|                                     |
|   Hint-name table   IMAGE_IMPORT_BY_NAME import address table(IAT) |
|  |------------------|  |--------------------|  |------------------|  |
|-> | IMAGE_THUNK_DATA |-->| 44 | "GetMessage" |<--| IMAGE_THUNK_DATA |<---|
  |------------------|  |----|---------------|  |------------------|
  | IMAGE_THUNK_DATA |-->| 72 | "LoadIcon"  |<--| IMAGE_THUNK_DATA |
  |------------------|  |----|---------------|  |------------------|
  |   ......    |-->| .. |  ......   |<--|   ......   |
  |------------------|  |----|---------------|  |------------------|
  |    NULL    |              |    NULL    |
  |------------------|              |------------------|
 
  当然,这是描述从一个dll中引入函数的情形.从几个dll中引入函数,那么就有几个这样的结构.同时,这也是磁盘文件上的结构.装入内存后FirstThunk指向的结构数组会被修改.可以看下面的图.
  我们先来熟悉一下这三个结构的定义:

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
  union {
    DWORD  Characteristics;  // 0 for terminating null import descriptor
    DWORD  OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
  };
  DWORD  TimeDateStamp;     // 0 if not bound,
                  // -1 if bound, and real date/time stamp
                  //   in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
                  // O.W. date/time stamp of DLL bound to (Old BIND)

  DWORD  ForwarderChain;     // -1 if no forwarders
  DWORD  Name;
  DWORD  FirstThunk;       // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR; 

typedef struct _IMAGE_THUNK_DATA32 {
  union {
    PBYTE ForwarderString;
    PDWORD Function;
    DWORD Ordinal;
    PIMAGE_IMPORT_BY_NAME AddressOfData;
  } u1;
} IMAGE_THUNK_DATA32;

typedef struct _IMAGE_IMPORT_BY_NAME {
  WORD  Hint;       //指出函数在所在的dll的输出表中的序号
  BYTE  Name[1];      //指出要输入的函数的函数名
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

  下面我们讲解一下IMAGE_IMPORT_DESCRIPTOR结构的各个域的含义:
1)union {
    DWORD  Characteristics; 
    DWORD  OriginalFirstThunk;
 };
   这个联合指向一个 IMAGE_THUNK_DATA 类型的结构数组.这个联合不是很重要,可以为0.
 
2)TimeDateStamp
  该dll的时间日期戳,一般为0.
 
3)ForwarderChain
  正向连接索引.一般为0.
 
4)Name
  dll名字的RVA.
 
5)FirstThunk
  这个域也是一个RVA,指向一个DWORD数组,数组以NULL结束.数组中的每个DWORD实际上是一个IMAGE_THUNK_DATA结构的联合体。IMAGE_THUNK_DATA联合体通常被解释为一个指向IMAGE_IMPORT_BY_NAME结构的RVA.

  从上图我们可以看出有两个并行的指针数组都指向IMAGE_IMPORT_BY_NAME结构.事实上,OriginalFirstThunk指向的IMAGE_THUNK_DATA结构数组从来不被修改,该数组有时也叫提示名表(Hint-name table),提示名表总是指向IMAGE_IMPORT_BY_NAME结构数组.而FirstThunk指向的IMAGE_THUNK_DATA结构数组在该pe文件被加载时,加载程序会修改该数组的内容.加载程序迭代搜索数组的每一个指针,找到每一个IMAGE_IMPORT_BY_NAME结构所对应的输入函数的地址,然后加载程序用找到的地址修改相应的IMAGE_THUNK_DATA结构.
  如前面提到的
     call my_label
     ...
my_label: jmp dword ptr [xxxxxxxx]
  其中的xxxxxxxx就是FirstThunk指向的IMAGE_THUNK_DATA数组中的一个的值.因为FirstThunk所指向的数组在加载后是所有输入函数的地址,因此它被称为输入地址表(Import Address Table,IAT).
  pe文件加载后输入表的情形如下:
             IMAGE_IMPORT_DESCRIPTOR
             |--------------------|
|-------------------------| OriginalFirstThunk |
|             |--------------------|
|             |  TimeDateStamp  |
|             |--------------------|
|             |  ForwarderChain  |
|             |--------------------|
|             |    Name     |----> "USER32.DLL"
|             |--------------------|
|             |   FirstThunk   |---------------------------|
|             |--------------------|              |
|                                     |
|   Hint-name table   IMAGE_IMPORT_BY_NAME import address table(IAT) |
|  |------------------|  |--------------------|  |------------------|  |
|-> | IMAGE_THUNK_DATA |-->| 44 | "GetMessage" |  |ptr of GetMessage |<---|
  |------------------|  |----|---------------|  |------------------|
  | IMAGE_THUNK_DATA |-->| 72 | "LoadIcon"  |  | ptr of LoadIcon |
  |------------------|  |----|---------------|  |------------------|
  |   ......    |-->| .. |  ......   |  |   ......   |
  |------------------|  |----|---------------|  |------------------|
  |    NULL    |              |    NULL    |
  |------------------|              |------------------|
 
  输入表在pe知识里是最重要的一部分.希望你能够结合一下pe工具实际理解这部分的内容.

pe知识学习(六)


  有输入表就有输出表,本贴开始介绍输出表.

  大部分dll都会输出一些函数.有些pe文件也会有输出表.通常输出表都是放在.edata区段的.因此.edata区段的注要成分是函数名表,入口点地址,输出函数的序号.
  输出表的开始部分是一个IMAGE_EXPORT_DIRECTORY结构,之后紧接着是由该结构中的某个域所指向的数据.
  IMAGE_EXPORT_DIRECTORY结构定义如下:
 
  typedef struct _IMAGE_EXPORT_DIRECTORY {
  DWORD  Characteristics;
  DWORD  TimeDateStamp;
  WORD  MajorVersion;
  WORD  MinorVersion;
  DWORD  Name;
  DWORD  Base;
  DWORD  NumberOfFunctions;
  DWORD  NumberOfNames;
  DWORD  AddressOfFunctions;   // RVA from base of image
  DWORD  AddressOfNames;     // RVA from base of image
  DWORD  AddressOfNameOrdinals; // RVA from base of image
  } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
 
1)Characteristics
  这个值总为0.

2)TimeDateStamp
  含有这个导出表的文件被生成的时间.

3)MajorVersion
4)MinorVersion
  版本信息.总为0.

5)Name
  含有这个导出表的pe文件的名字的RVA.
 
6)Base
  输出函数序号的开始值.
 
7)NumberOfFunctions
  数组 AddressOfFunctions 中元素的个数.这个值就是导出表中导出函数的个数.
 
8)NumberOfNames
  以名字输出的函数的个数.
 
9)AddressOfFunctions
  这是一个RVA,指向一个由函数地址组成的数组.每一个函数地址是本模块中的一个输出函数的入口地址.

10)AddressOfNames
  这是一个RVA,指向一个由字符串指针组成的数组,每个字符串是本模块中以名字输出的输出函数的函数名.
 
11)AddressOfNameOrdinals
  这是一个RVA,指向一个word类型的数组,该word类型数组是本模块中所有以名字输出的输出函数的输出序号.
   
  假设一个dll有三个导出函数,分别如下:
  
  序号  函数名
  1   "myfun1"
  2   
  3   "myfun2"
  其中序号为2的函数只能通过序号导出.图示如下:
 
  IMAGE_EXPORT_DIRECTORY            函数地址表
  |---------------------------| |------->|------------------|
  |   Characteristics   | |    | 0x400042"myfun1" |
  |---------------------------| |    |------------------|
  |     ......      | |    |  0x400085   |
  |---------------------------| |    |------------------|
  |  NumberOfFunctions = 3  | |    | 0x400197"myfun2" |
  |---------------------------| |    |------------------|
  |  NumberOfNames = 2   | |    
  |---------------------------| |       函数名表
  |  AddressOfFunctions   |--| |----->|------------|
  |---------------------------|  |   | 0xXXXXXXXX |->"myfun1"
  |  AddressOfNames     |----|   |------------|
  |---------------------------|      | 0xXXXXXXXX |->"myfun2"
  |  AddressOfNameOrdinals  |----|   |------------|
  |---------------------------|  |
                  |   函数名称地址索引表
                  |----->|-----------|
                      |   1   |
                      |-----------|
                      |   3   |
                      |-----------|
                     
  我们来看一下pe加载程序的工作机制.假设它知道函数名"myfun2",那么加载程序将首先遍历函数名表,找到匹配的函数名"myfun2".由于"myfun2"在函数名表里的索引是2,所以加载函数将在函数名称地址索引表的第二个元素里取得函数在函数地址表里的索引3,然后加载程序就会在函数地址表的第三个元素里取得函数的入口地址0x400197.
  这就是以名称导出函数的过程.
  如果是以序号导出函数地址的,那将更简单.加载程序将直接用序号在函数地址表里取出函数的入口地址.可以看出,以序号导出函数比以名称导出函数快,但以序号导出函数地址会带来维护的问题.有些api函数在不同的系统上导出序号并不相同.所以微软不推荐使用序号来导出函数.
  这里讲的是Base为1时的情形,如果Base域大于1,则在取得函数在函数地址表中的索引后,用这个索引值减去Base就可以得到函数在函数地址表中的偏移值.

pe知识学习(七)--完

  这一贴介绍一下pe文件中的重定位表.
  重定位的概念不难理解.简单的说,就是因为程序被连接后一些变量或者函数调用或跳转指令使用了绝对地址,当装载程序不能把pe映像装到预定的地址(ImageBase)时,那么这些绝对地址就需要调整.否则程序将访问到错误的地址.
  exe文件一般不需要重定位,因为每个exe文件映像都有自己独立的地址空间,它总能被映射到预定的地址.而dll文件一般是映射到exe文件的地址空间的.当多个dll文件的预定地址发生冲突时,就不能保证会被映射到预定的地址了.所以dll文件一般都需要重定位的.
  那么重定位是怎样实现的呢?
  在pe文件里用这样一个结构来描述一个重定位数据项:
  typedef struct _IMAGE_BASE_RELOCATION {
  DWORD  VirtualAddress;
  DWORD  SizeOfBlock;
// WORD  TypeOffset[1];
} IMAGE_BASE_RELOCATION;

1)VirtualAddress
  这个域包含这个重定位数据项的起始RVA值,紧跟在结构后面的偏移值要加上这个值才是一个真正的需要重定位的数据的RVA值.
  如果这个域为0,则代表一系列重定位数据项的结束.

2)SizeOfBlock
  重定位数据项的大小.

3)TypeOffset[1]
  这是一个WORD类型的数组.数组的元素个数由(SizeOfBlock - 8 ) / 2 得到.每个元素的低12位代表一个偏移值,该偏移值加上VirtualAddress就是需要修正的数据的RVA值.而高4位代表该偏移值的类型.该类型定义如下:
#define IMAGE_REL_BASED_ABSOLUTE       0
#define IMAGE_REL_BASED_HIGH         1
#define IMAGE_REL_BASED_LOW          2
#define IMAGE_REL_BASED_HIGHLOW        3
#define IMAGE_REL_BASED_HIGHADJ        4
#define IMAGE_REL_BASED_MIPS_JMPADDR     5
#define IMAGE_REL_BASED_SECTION        6
#define IMAGE_REL_BASED_REL32         7

#define IMAGE_REL_BASED_MIPS_JMPADDR16    9
#define IMAGE_REL_BASED_IA64_IMM64      9
#define IMAGE_REL_BASED_DIR64         10
#define IMAGE_REL_BASED_HIGH3ADJ       11
  其中和intel的cpu有关的只有两种类型.其他的都用于i386以外的cpu.
 0 (IMAGE_REL_BASED_ABSOLUTE):代表该偏移值无意义.只是为了使所有重定位数据项的大小位DWORD的整数倍.
 3 (IMAGE_REL_BASED_HIGHLOW): 把该偏移值加上 VirtualAddress就是要修正的数据的RVA值.
 
  由于WORD中只有低12位表示偏移值,因此一个重定位项只能修正一页的数据(4k).如果需要重定位的数据超过4k,那么一个pe文件里就有多个重定位项.
 
  加载程序修正过程如下:假设IMAGE_OPTIONAL_HEADER.ImageBase的值为0x400000,而实际pe映像被加载的地址为0x500000,实际加载的地址比预定的高0x100000,那么需要修正的数据都会被加上0x100000.
  这里所说的需要修正的数据是指前面提到的变量的绝对地址和调用或跳转指令里含有的绝对地址.


  到这里pe知识学习就告一段落了.希望这些帖子能帮助你了解pe文件的大概知识.更细节的知识可以到msdn里查找.我的水平有限,错误之处还望你能够给予指正.我将不胜感激.

原创粉丝点击