首页
社区
课程
招聘
[原创]手工构造典型PE文件
发表于: 2009-8-1 22:31 13084

[原创]手工构造典型PE文件

2009-8-1 22:31
13084

一直以来都在学习PE文件结构,从不敢轻视,但是即使如此还是发现自己在这方面有所不足,于是便想到了用纯手工方式打造一个完整的可执行的PE文件。在这期间我也查了大量资料,但是这些资料都有一个通病就是不完整,看雪得那个只翻译了一部分,加解密技术内幕介绍的更是笼统,而且是打造一个只有180字节的PE文件,是高手们茶余饭后的怡情小游戏。

    鉴于此,心想为什么不自己摸索着手工打造一个完整些的呢?一是加强一下自己对于PE文件的了解,二是写出一篇参考性比较强的文章,给有志于再此发展的朋友们铺一铺路,也算是干了一件利国利民的好事。

    对于手工打造PE文件,我个人认为至少要分为三篇文章来阐述,每篇相对独立,合起来形成一个相对的体系。第一篇文章(也就是本文)用来介绍怎样用手工打造一个最典型、最简单的PE文件,而后两篇文章的问世还要引用潘爱民先生的一句话“还需要时日与机缘”。

    本文介绍的PE文件手工编辑方式,是本着以下三个原则所写的,望读者注意:
1、完整性:对于手工打造PE文件所不需注意的字段也进行了必要的介绍,因此整文可能显得非常臃肿。
2、通用性:完全按照典型的PE文件结构构造,因此对于某些不常见的PE文件结构有一定差距。
3、易学性:对于字段之间的逻辑关系进行了比较细致的介绍,因此对于一部分底子比较好的读者来说可能显得有些啰嗦。

    为了方便各位阅读与查阅,我将文章分成了三各部分,以便各位读者各取所需,不用把宝贵的精力浪费在查找上。
1、PE文件整体信息,提供了一个剖析PE文件的图表,以便于读者对于PE文件有一个整体的了解,并监督自己的工作进度。
2、对于重点字段的介绍,以及字段之间的逻辑关系,建议首先从这里开始看。
3、手工构造PE文件字段清单,此清单包含构造一个完整PE文件的每一个字节,跟着这个清单走就可以构造一个PE文件。

    对于第一次手工打造PE文件的朋友们来说,你们可以以“一、整体性息”为大纲,并参考第三部分一块一块的慢慢打造,如果有不懂的地方就去看第二部分。

选读:为什么要手工打造PE文件?

    我们知道,往往从一个系统可执行文件结构上,就可以看整个操作系统的一些特性。也就是说PE里有Windows操作系统结构与运行机理的影子。由此可见,PE文件必然是一个非常庞杂且逻辑复杂的结构,那么为什么我们还要“自取其辱”来手工制造一个PE文件呢?这就要从PE文件的重要性说起了。
     
    我们现今组成Windows大家庭的主要成员就是PE文件了,里面包括EXE、DLL、OCX、SYS等一切最有价值的文件都是PE文件格式,出于对版权的考虑或对某种技术的渴求,任何一种与Windows系统相关的行为最终都要归集到这里--PE文件。

    特别是对于想学习加壳、破解、搞虚拟机的朋友们来说,熟知PE文件结构更是必不可少的基本功!

    但也正是由于PE文件的复杂性,我们才要采取一些特别的办法来攻克它,其中手工打造PE文件就是一条捷径。

    你可以想像一下,如果你都可以手工打造PE文件的话,那么对于PE文件的了解更是可见一斑了。但是我还想提醒一下各位读者,即便是如此,我们所了解的也仅仅是一部分,不过一般情况下已经足够了。

一、整体性息

    这部分以图表的形式表示PE文件的整体结构。

----------*------------------------------*
          | DOS Header(IMAGE_DOS_HEADER) | -->64 Byte
 DOS头部  --------------------------------
          | DOS Stub                     | -->112 Byte
----------*------------------------------*
          | "PE"00 (Signature)           | -->4 Byte
          --------------------------------
          | IMAGE_FILE_HEADER            | -->20 Byte
 PE文件头 --------------------------------
          | IMAGE_OPTIONAL_HEADER32      | -->96 Byte
          --------------------------------
          | 数据目录表                   | -->128 Byte
----------*------------------------------*
          | IMAGE_SECTION_HEADER         | -->40 Byte
          --------------------------------
   块表   | IMAGE_SECTION_HEADER         | -->40 Byte
          --------------------------------
          | IMAGE_SECTION_HEADER         | -->40 Byte  
----------*------------------------------*
          |.text                         | -->512 Byte
          --------------------------------
    块    |.rdata                        | -->512 Byte
          -------------------------------
          |.data                         | -->512 Byte
----------*------------------------------*
          | COFF行号                     | -->NULL
          --------------------------------
 调试信息 | COFF符号表                   | -->NULL
          --------------------------------
          | Code View 调试信息           | -->NULL
----------*------------------------------*

这部分内容的意义有二:
1、对于PE文件有一个整体的认识。
2、方便审查自己的构造进度。

    这里我们重点介绍怎样用其审查自己的构造进度,首先希望各位读者明白我们将要手工构造的一个体积为2560字节的这个小家伙,对于初次上手的读者们来说并不是一件小的工程,因此有必要知道自己现在正做什么,以及做到哪里了。

    记得我少年学画时老师教我们构图就要从整体到局部,后来自学编程仍然是先实现大的框架再去解决每一个细节问题。OK,现在到了这里,很显然我们仍然需要本着从之提到局部的思想来构造我们的PE文件。

    那好,我们先搞明白第一个问题“我们的文件体积是怎么计算出来的呢”。

    首先我们要知道,PE文件自始至终都是以一种节的思想来构造的,那么我们就要从节开始。

    对于本文所讲述的PE文件来讲总共有三个区块(节),他们分别用来存放可执行代码、输入表信息以及全局变量,接触过PE文件的朋友对于区块的概念应该不陌生,我们知道Windows下的很多应用程序的文件对齐粒度,也就是大名鼎鼎的FileAlignment字段的值多为200h Byte,也就是十进制的512 Byte。我们同样应该知道,对于不足512字节的区段,余下部分要用00h填充到512字节大小,对与超过部分(例如513字节的区段)我们就要在多分配给他512字节个空间。

    当然,这些基础知识我想有一部分读者应该比较熟悉,那么对于PE文件头部分呢?也是如此吗?例如本例中的PE头就占用了544个字节,但是很显然这要使其填充到1024(400h)字节处才能开始第一个区段.text段。

    正是如此,我们整个文件的体积就是PE头+3个区段的体积之和,也就是PE头(512*2)+3个区段(512*3)=2560,这也就是我们所构造的PE文件的最终大小了。

    其次我们提前搞清楚一些字段与区段的偏移量也是比较重要的,这里对于计算方式不再多说,请大家直接看下面的表:
1、PE头开始处 000000B0h
2、IMAGE_OPTIONAL_HEADER32开始处 000000C8h
3、数据目录表开始处 00000128h
4、块表开始处 000001A8h
5、.text块开始处 00000400h
6、.rdata块开始处 00000600h
7、.data块开始处 00000800h

    当然,上面的那些Offset只是针对本文件而言,并不绝对,具体情况还要具体分析。

    到此,本段就告一段落了,剩下的两段为了提高效率我并没有加以润色,全都是干货,希望各位读者能吃下这两块营养丰富的压缩饼干……

二、重点字段介绍

    这里只对需手工构造的字段进行着重介绍,详细的PE文件结构字段清单请见第三部分。

1、DOS头部
  1-1  DOS Header
    1-1-1   e_magic  [WORD]  -->4D 5A (* DOS可执行文件头标记)
    注释:此处值总是为MZ的16进制码。

    1-1-19  e_lfanew  [DWORD]  -->B0 00 00 00 (* 指向PE文件头的偏移量。0xB0=64+112)
    注释:此处的值正好为为DOS头部的大小,因为DOS头部后面就是PE文件头部分了。

2  PE文件头
  2-1  "PE"00
    2-1-1  Signature  [DWORD] -->50450000h (* PE文件头标记)
    注释:此处的值总是为PE的16进制值加0000h。

  2-2  IMAGE_FILE_HEADER
    2-2-1  Machine  [WORD]  -->4C 01 (* 可执行文件的目标CPU类型)
    注释:此PE文件可以运行于哪个CPU下,其标志就为相应的值。
    *------------------------*
    |     机器      |  标志  |
    --------------------------
    | Intel i386    | 14Ch   |
    --------------------------
    | MIPS R3000    | 162h   |
    --------------------------
    | MIPS R4000    | 166h   |
    --------------------------
    | Alpha AXP     | 184h   |
    --------------------------
    | Power PC      | 1F0h   |
    *------------------------*

    2-2-2  NumberOfSections  [WORD]  -->03 00 (* 区块数目)
    注释:此值决定此PE文件的区块数目,本文件为3个区块。

    2-2-6  SizeOfOptionalHeadr  [WORD]  -->E0 00 (* PE头(IMAGE_OPTIONAL_HEADER32)大小)
    注释:此值表示PE文件头的大小。

    2-2-7  Characteristics  [WORD]  -->0F 01 (* 文件属性)
    注释:此值为文件的执行属性。EXE文件此值一般为010Fh,DLL文件此值一般为0210h。

  2-3  IMAGE_OPTIONAL_HEADER32
    2-3-1   Magic  [WORD]  -->0B 01 (* 标记字)
    注释:此处是一个标记字,用于描述次PE文件的映像类型。ROM映像为0107h;普通可执行映像010Bh;PE32+则是020Bh。

    2-3-7   AddressOfEntryPoint  [DWORD] -->00 10 00 00 (* 程序执行入口RAV)
    注释:通俗的说就是指向可执行代码区块(例如.text)的首地址。

    2-3-10  ImageBase  [DWORD] -->00 00 40 00 (* 程序默认装入基地址)
    注释:是指文件在内存中首选的装入地址。

    2-3-11  SectionAlignment  [DWORD] -->00 10 00 00 (* 内存中区块对齐值)
    注释:PE文件被装入内存中时的块对齐大小,也叫做块粒度。其默认的对其尺寸是CPU的页尺寸。

    2-3-12  FileAlignment  [DWORD] -->00 02 00 00 (* 文件中区块对齐值)
    注释:磁盘上PE文件的区块对齐大小。这个值必须是2的幂,并且最小为200h。

    2-3-17  MajorSubsystemVersion  [WORD]  -->04 00 (* 运行所需最低子系统主版本号)
    注释:要求最低的子系统主版本号,一般情况下都为4。

    2-3-18  MinorSubsystemVersion  [WORD]  -->00 00 (* 运行所需最低子系统次版本号)
    注释:要求最低的子系统次版本号,一般情况下都为0。

    2-3-20  SizeOfImage  [DWORD] -->00 40 00 00 (* 映像装入内存后的总尺寸)
    注释:指的是装入文件从Image Base到最后一个区块的总大小。

    2-3-21  SizeOfHeaders  [DWORD] -->00 40 00 00 (* DOS头、PE头、区块表的总大小)
    注释:指的是DOS头、PE头与区块表的总大小,并且所有这些项目都出现在PE文件中任何代码或数据块之前。此值遵守文件对齐粒度。

    2-3-23  Subsystem [WORD]  -->03 00 (* 文件子系统)
    注释:标明可执行文件所期望的子系统(用户界面类型)。
          *----*----------------------------*
          | 值 |          子系统            |
          *----*----------------------------*
          | 0  | 未知                       |
          -----------------------------------
          | 1  | 不需要子系统(如驱动程序) |
          -----------------------------------
          | 2  | 图形接口子系统(GUI)      |
          -----------------------------------
          | 3  | 字符子系统(CUI)          |
          -----------------------------------
          | 5  | OS/2字符子系统             |
          -----------------------------------
          | 7  | POSIX字符子系统            |
          -----------------------------------
          | 8  | 保留                       |
          -----------------------------------
          | 9  | Windows CE图形界面         |
          *----*----------------------------*

    2-3-30  NumberOfRvaAndSizes  [DWORD] -->10 00 00 00 (* 数据目录标的项数,默认总为16)
    注释:数据目录的项数。这个字段字NT系统发布以来就一直是16。

  2-4  数据目录表
    2-4-2   Import Table
    注释:输入表

      2-4-2-1  VirtualAddress [DWORD] -->10 20 00 00 (* 数据块的起始RAV)
      注释:输入表的起始地址,要去除IAT所占空间,直接从第一个IID开始。

      2-4-2-2  Size  [DWORD] -->3C 00 00 00 (* 数据块的长度)
      注释:从第一个IID到最后一个IMAGE_IMPORT_BY_NAME的总长度。

3  块表
  3-1  IMAGE_SECTION_HEADER (1 .text)
    3-1-1   Name  [BYTE]  -->2E 74 65 78 74 00 00 00 (* 8个字节的块名)
    注释:此区块的名称,限制在8字节以内。

    3-1-2   VirtualSize  [DWORD] -->26 00 00 00 (* 被实际使用的区块大小)
    注释:此区块包含数据的大小。

    3-1-10  Characteristics  [DWORD] -->20 00 00 60 (* 该区块的读写、执行属性)
    注释:*---------------------------------------------*
          |  字段值   |              用 途              |
          -----------------------------------------------
          | 00000020h | 包含代码,常与10000000h一起设置 |
          -----------------------------------------------
          | 00000040h | 包含已初始化数据                |
          -----------------------------------------------
          | 00000080h | 包含未初始化数据                |
          -----------------------------------------------
          | 02000000h | 可以被丢弃                      |
          -----------------------------------------------
          | 10000000h | 共享块                          |
          -----------------------------------------------
          | 20000000h | 可执行                          |
          -----------------------------------------------
          | 40000000h | 可读                            |
          -----------------------------------------------
          | 80000000h | 可写                            |
          -----------------------------------------------

4  块
  4-1  .text (* 此区段是一段汇编代码的16进制形式,功能是弹出一个MessageBox提示框)
    -->HEX
    6A 00 68 00 30 40 00 68  07 30 40 00 6A 00 E8 07
    00 00 00 6A 00 E8 06 00  00 00 FF 25 08 20 40 00
    FF 25 00 20 40 00 
    注释:这是下面汇编指令的16进制模式。

    -->ASM
    00401000  6A 00         PUSH 0
    00401002  68 00304000   PUSH first_PE.00403000
    00401007  68 07304000   PUSH first_PE.00403007
    0040100C  6A 00         PUSH 0
    0040100E  E8 07000000   CALL <JMP.&user32.MessageBoxA>
    00401013  6A 00         PUSH 0
    00401015  E8 06000000   CALL <JMP.&kernel32.ExitProcess>
    0040101A  FF25 08204000 JMP DWORD PTR DS:[<&user32.MessageBoxA>]
    00401020  FF25 00204000 JMP DWORD PTR DS:[<&kernel32.ExitProcess>]

  4-2  .rdata (* 该区块包含输入表)
    4-2-1  IMAGE_THUNK_DATA32 (IAT 1)
    注释:这里有很多人都搞不明白,其实IMAGE_THUNK_DATA32是一个联合体,可以同时代表IAT与INT。

      4-2-1-1 AddressOfData [DWORD] -->76 20 00 00 (* 指向IMAGE_IMPORT_BY_NAME的RVA)
      注释:作为IAT时我们就会使用他的AddressOfData成员,用来存放指向IMAGE_IMPORT_BY_NAME的RVA,当程序装入内存后,只与IAT交换信息,输入表的其他部分就不再需要了。

      【由于本例子两个API函数引自与两个不同的DLL文件,所以要补00000000h结束。】
       注释:这里同样有很多人不明白,每当属于某一个DLL文件的API罗列完毕后,就要输入00加以结束。

    4-2-3  IMAGE_IMPORT_DESCRIPTOR (IID 1)
    注释:这里稍微复杂些,它的作用就是使用INT指定某个DLL文件中的API函数,并配合IAT指向相关API的地址。

      4-2-3-1 OriginalFirstThunk [DWORD] -->4C 20 00 00 (* 指向输入名称表INT的RVA)
      注释:这里指定某个系统DLL文件中的API函数。

      4-2-3-4 Name  [DWORD] -->6A 20 00 00 (* 指向DLL名字的RVA与指针)
      注释:这里指定某个系统DLL文件。

      4-2-3-5 FirstThunk  [DWORD] -->08 20 00 00 (* 指向输入地址表IAT的RVA)
      注释:这里指定相关的IAT,并由IAT在IMAGE_IMPORT_BY_NAME中获得相应API的地址。

    4-2-4  IMAGE_IMPORT_DESCRIPTOR (IID 2)
      4-2-4-1 OriginalFirstThunk [DWORD] -->54 20 00 00 (* 指向输入名称表INT的RVA)
      4-2-4-4 Name               [DWORD] -->84 20 00 00 (* 指向DLL名字的RVA与指针)
      4-2-4-5 FirstThunk         [DWORD] -->00 20 00 00 (* 指向输入地址表IAT的RVA)
      【填充20个00h空字节做结尾标记】
      注释:关于填充20个字节作为IID的结尾标记只是一个规律,还没找到相关资料证实。

    4-2-5  IMAGE_THUNK_DATA32 (INT 1)
      4-2-5-1  ForwarderString [DWORD] -->5C 20 00 00 (* 指向一个转向字符的RVA)
      注释:直接指向相关API函数。

      【由于本例子两个API函数引自与两个不同的DLL文件,所以要补00000000h结束。】
       注释:与上面的IAT原理一样,就是这样的一个格式。

    4-2-7  IMAGE_IMPORT_BY_NAME ( 1 )
      4-2-7-2  Name [BYTE] -->4D 65 73 73 61 67 65 42 6F 78 41 (* MessageBoxA的16进制码)
      注释:相关API函数的16进制代码。

      【后跟输出此函数的DLL名称16进制码00 75 73 65 72 33 32 2E 64 6C 6C 00 00 user32.dll】
      注释:相关系统的API函数罗列完毕后,通常都在最后一个API后空00h,并跟着这个DLL名称的16进制数据。

      总结:这里罗列了一些需要手工构建的部分结构,如果想看更加详细的请关注下面的内容。

三、PE文件结构字段清单

1  DOS头部
  1-1  DOS Header
    1-1-1   e_magic    [WORD]       -->4D 5A (* DOS可执行文件头标记)
    1-1-2   e_cblp     [WORD]        ->00 00 (文件最后页的字节数)
    1-1-3   e_cp       [WORD]        ->00 00 (文件页数)
    1-1-4   e_crlc     [WORD]        ->00 00 (重定位元素个数)
    1-1-5   e_cparhdr  [WORD]        ->00 00 (以段落为单位的头部大小)
    1-1-6   e_minalloc [WORD]        ->00 00 (所需的最小附加段)
    1-1-7   e_maxalloc [WORD]        ->00 00 (所需的最大附加段)
    1-1-8   e_ss       [WORD]        ->00 00 (初始的堆栈段(SS)相对偏移量值)
    1-1-9   e_sp       [WORD]        ->00 00 (初始的堆栈指针(SP)值)
    1-1-10  e_csum     [WORD]        ->00 00 (校验和)
    1-1-11  e_ip       [WORD]        ->00 00 (初始的指令指针(IP)值)
    1-1-12  e_cs       [WORD]        ->00 00 (初始的代码段(CS)相对偏移量值)
    1-1-13  e_lfarlc   [WORD]        ->00 00 (重定位表在文件中的偏移地址)
    1-1-14  e_ovno     [WORD]        ->00 00 (覆盖号)
    1-1-15  e_res      [WORD] 4 dup  ->00 00 (保留字,一般都是为确保对齐而预留)
    1-1-16  e_oemid    [WORD]        ->00 00 (OEM 标识符,相对于 e_oeminfo)
    1-1-17  e_oeminfo  [WORD]        ->00 00 (OEM 信息,即 e_oemid 的细节)
    1-1-18  e_res2     [WORD] 10 dup ->00 00 (保留字,一般都是为确保对齐而预留)
    1-1-19  e_lfanew   [DWORD]      -->B0 00 00 00 (* 指向PE文件头的偏移量。0xB0=64+112)
  1-2  DOS Stub
    全部填00h

2  PE文件头
  2-1  "PE"00
    2-1-1  Signature [DWORD] -->50450000h (* PE文件头标记)
  2-2  IMAGE_FILE_HEADER
    2-2-1  Machine              [WORD]  -->4C 01 (* 可执行文件的目标CPU类型)
    2-2-2  NumberOfSections     [WORD]  -->03 00 (* 区块数目)
    2-2-3  TimeDateStamp        [DWORD]  ->00 00 00 00 (文件创建的时间与日期)
    2-2-4  PointerToSymbolTable [DWORD]  ->00 00 00 00 (指向符号表,用于调试)
    2-2-5  NumberOfSymbols      [DWORD]  ->00 00 00 00 (符号表中的符号个数,用于调试)
    2-2-6  SizeOfOptionalHeadr  [WORD]  -->E0 00 (* PE头(IMAGE_OPTIONAL_HEADER32)大小)
    2-2-7  Characteristics      [WORD]  -->0F 01 (* 文件属性)
  2-3  IMAGE_OPTIONAL_HEADER32
    2-3-1   Magic                       [WORD]  -->0B 01 (* 标记字)
    2-3-2   MajorLinkerVersion          [BYTE]   ->00 (连接程序主版本号)
    2-3-3   MinorLinkerVersion          [BYTE]   ->00 (连接程序次版本号)
    2-3-4   SizeOfCode                  [DWORD]  ->00 00 00 00 (所有含代码区块的总大小)
    2-3-5   SizeOfInitializedData       [DWORD]  ->00 00 00 00 (所有初始化数据区块大总大小)
    2-3-6   SizeOfUninitializedData     [DWORD]  ->00 00 00 00 (所有未初始化数据区块大总大小)
    2-3-7   AddressOfEntryPoint         [DWORD] -->00 10 00 00 (* 程序执行入口RAV)
    2-3-8   BaseOfCode                  [DWORD]  ->00 00 00 00 (代码区块起始RAV)
    2-3-9   BaseOfData                  [DWORD]  ->00 00 00 00 (数据区块起始RAV)
    2-3-10  ImageBase                   [DWORD] -->00 00 40 00 (* 程序默认装入基地址)
    2-3-11  SectionAlignment            [DWORD] -->00 10 00 00 (* 内存中区块对齐值)
    2-3-12  FileAlignment               [DWORD] -->00 02 00 00 (* 文件中区块对齐值)
    2-3-13  MajorOperatingSystemVersion [WORD]   ->00 00 (操作系统主版本号)
    2-3-14  MinorOperatingSystemVersion [WORD]   ->00 00 (操作系统次版本号)
    2-3-15  MajorImageVersion           [WORD]   ->00 00 (用户自定义主版本号)
    2-3-16  MinorImageVersion           [WORD]   ->00 00 (用户自定义次版本号)
    2-3-17  MajorSubsystemVersion       [WORD]  -->04 00 (* 运行所需最低子系统主版本号)
    2-3-18  MinorSubsystemVersion       [WORD]  -->00 00 (* 运行所需最低子系统次版本号)
    2-3-19  Win32VersionValue           [DWORD]  ->00 00 00 00 (保留值,通常为0)
    2-3-20  SizeOfImage                 [DWORD] -->00 40 00 00 (* 映像装入内存后的总尺寸)
    2-3-21  SizeOfHeaders               [DWORD] -->00 40 00 00 (* DOS头  PE头  区块表的总大小)
    2-3-22  CheckSum                    [DWORD]  ->00 00 00 00 (映像效验和)
    2-3-23  Subsystem                   [WORD]  -->03 00 (* 文件子系统)
    2-3-24  DllCharacteristics          [WORD]   ->00 00 (显示DLL特性的旗标)
    2-3-25  SizeOfStackReserve          [DWORD]  ->00 00 00 00 (初始化堆栈总大小)
    2-3-26  SizeOfStackCommit           [DWORD]  ->00 00 00 00 (初始化实际提交堆栈大小)
    2-3-27  SizeOfHeapReserve           [DWORD]  ->00 00 00 00 (初始化保留堆栈大小)
    2-3-28  SizeOfHeapCommit            [DWORD]  ->00 00 00 00 (初始化实际保留堆栈大小)
    2-3-29  LoaderFlags                 [DWORD]  ->00 00 00 00 (与调试相关,默认值为0)
    2-3-30  NumberOfRvaAndSizes         [DWORD] -->10 00 00 00 (* 数据目录标的项数,默认总为16)
  2-4  数据目录表
    2-4-1   Export Table
      2-4-1-1   VirtualAddress [DWORD]  ->00 00 00 00 (数据块的起始RAV)
      2-4-1-2   Size           [DWORD]  ->00 00 00 00 (数据块的长度)
    2-4-2   Import Table
      2-4-2-1   VirtualAddress [DWORD] -->10 20 00 00 (* 数据块的起始RAV)
      2-4-2-2   Size           [DWORD] -->3C 00 00 00 (* 数据块的长度)
    2-4-3   Resources Table
      2-4-3-1   VirtualAddress [DWORD]  ->00 00 00 00 (数据块的起始RAV)
      2-4-3-2   Size           [DWORD]  ->00 00 00 00 (数据块的长度)
    2-4-4   Exception Table
      2-4-4-1   VirtualAddress [DWORD]  ->00 00 00 00 (数据块的起始RAV)
      2-4-4-2   Size           [DWORD]  ->00 00 00 00 (数据块的长度)
    2-4-5   Security Table
      2-4-5-1   VirtualAddress [DWORD]  ->00 00 00 00 (数据块的起始RAV)
      2-4-5-2   Size           [DWORD]  ->00 00 00 00 (数据块的长度)
    2-4-6   Base relocation Table
      2-4-6-1   VirtualAddress [DWORD]  ->00 00 00 00 (数据块的起始RAV)
      2-4-6-2   Size           [DWORD]  ->00 00 00 00 (数据块的长度)
    2-4-7   Debug
      2-4-7-1   VirtualAddress [DWORD]  ->00 00 00 00 (数据块的起始RAV)
      2-4-7-2   Size           [DWORD]  ->00 00 00 00 (数据块的长度)
    2-4-8   Copyright
      2-4-8-1   VirtualAddress [DWORD]  ->00 00 00 00 (数据块的起始RAV)
      2-4-8-2   Size           [DWORD]  ->00 00 00 00 (数据块的长度)
    2-4-9   Global ptr
      2-4-9-1   VirtualAddress [DWORD]  ->00 00 00 00 (数据块的起始RAV)
      2-4-9-2   Size           [DWORD]  ->00 00 00 00 (数据块的长度)
    2-4-10  Threda local storage(TLS)
      2-4-10-1  VirtualAddress [DWORD]  ->00 00 00 00 (数据块的起始RAV)
      2-4-10-2  Size           [DWORD]  ->00 00 00 00 (数据块的长度)
    2-4-11  Load configuration
      2-4-11-1  VirtualAddress [DWORD]  ->00 00 00 00 (数据块的起始RAV)
      2-4-11-2  Size           [DWORD]  ->00 00 00 00 (数据块的长度)
    2-4-12  Bound import
      2-4-12-1  VirtualAddress [DWORD]  ->00 00 00 00 (数据块的起始RAV)
      2-4-12-2  Size           [DWORD]  ->00 00 00 00 (数据块的长度)
    2-4-13  Import Address Table(IAT)
      2-4-13-1  VirtualAddress [DWORD]  ->00 00 00 00 (数据块的起始RAV)
      2-4-13-2  Size           [DWORD]  ->00 00 00 00 (数据块的长度)
    2-4-14  Delay import      
      2-4-14-1  VirtualAddress [DWORD]  ->00 00 00 00 (数据块的起始RAV)
      2-4-14-2  Size           [DWORD]  ->00 00 00 00 (数据块的长度)
    2-4-15  COM descriptor
      2-4-15-1  VirtualAddress [DWORD]  ->00 00 00 00 (数据块的起始RAV)
      2-4-15-2  Size           [DWORD]  ->00 00 00 00 (数据块的长度)
    2-4-16  保留
      2-4-16-1  VirtualAddress [DWORD]  ->00 00 00 00 (数据块的起始RAV)
      2-4-16-2  Size           [DWORD]  ->00 00 00 00 (数据块的长度)

3  块表
  3-1  IMAGE_SECTION_HEADER (1 .text)
    3-1-1   Name                 [BYTE]  -->2E 74 65 78 74 00 00 00 (* 8个字节的块名)
    3-1-2   VirtualSize          [DWORD] -->26 00 00 00 (* 被实际使用的区块大小)
    3-1-3   VirtualAddress       [DWORD] -->00 10 00 00 (* 区块的RAV地址)
    3-1-4   SizeOfRawData        [DWORD] -->00 02 00 00 (* 该块在磁盘中所占的大小)
    3-1-5   PointerToRawData     [DWORD] -->00 04 00 00 (* 该块在磁盘文件中的偏移)
    3-1-6   PointerToRelocations [DWORD]  ->00 00 00 00 (在OBJ文件中使用,重定位偏移)
    3-1-7   PointerToLinenumbers [DWORD]  ->00 00 00 00 (行号表的偏移,调试中使用)
    3-1-8   NumberOfRelocations  [WORD]   ->00 00 (在OBJ文件中使用,重定位项数目)
    3-1-9   NumberOfLinenumbers  [WORD]   ->00 00 (行号表中行号的数目)
    3-1-10  Characteristics      [DWORD] -->20 00 00 60 (* 该区块的读写  执行属性)
  3-2  IMAGE_SECTION_HEADER (2 .rdata)
    3-2-1   Name                 [BYTE]  -->2E 72 64 61 74 61 00 00 (* 8个字节的块名)
    3-2-2   VirtualSize          [DWORD] -->92 00 00 00 (* 被实际使用的区块大小)
    3-2-3   VirtualAddress       [DWORD] -->00 20 00 00 (* 区块的RAV地址)
    3-2-4   SizeOfRawData        [DWORD] -->00 02 00 00 (* 该块在磁盘中所占的大小)
    3-2-5   PointerToRawData     [DWORD] -->00 06 00 00 (* 该块在磁盘文件中的偏移)
    3-2-6   PointerToRelocations [DWORD]  ->00 00 00 00 (在OBJ文件中使用,重定位偏移)
    3-2-7   PointerToLinenumbers [DWORD]  ->00 00 00 00 (行号表的偏移,调试中使用)
    3-2-8   NumberOfRelocations  [WORD]   ->00 00 (在OBJ文件中使用,重定位项数目)
    3-2-9   NumberOfLinenumbers  [WORD]   ->00 00 (行号表中行号的数目)
    3-2-10  Characteristics      [DWORD] -->40 00 00 40 (* 该区块的读写  执行属性)
  3-3  IMAGE_SECTION_HEADER (3 .data)
    3-3-1   Name                 [BYTE]  -->2E 64 61 74 61 00 00 00 (* 8个字节的块名)
    3-3-2   VirtualSize          [DWORD] -->3E 00 00 00 (* 被实际使用的区块大小)
    3-3-3   VirtualAddress       [DWORD] -->00 30 00 00 (* 区块的RAV地址)
    3-3-4   SizeOfRawData        [DWORD] -->00 02 00 00 (* 该块在磁盘中所占的大小)
    3-3-5   PointerToRawData     [DWORD] -->00 08 00 00 (* 该块在磁盘文件中的偏移)
    3-3-6   PointerToRelocations [DWORD]  ->00 00 00 00 (在OBJ文件中使用,重定位偏移)
    3-3-7   PointerToLinenumbers [DWORD]  ->00 00 00 00 (行号表的偏移,调试中使用)
    3-3-8   NumberOfRelocations  [WORD]   ->00 00 (在OBJ文件中使用,重定位项数目)
    3-3-9   NumberOfLinenumbers  [WORD]   ->00 00 (行号表中行号的数目)
    3-3-10  Characteristics      [DWORD] -->40 00 00 C0 (* 该区块的读写、执行属性)
    【由于FileAlignment为0x200大小,而此时整个PE头已经超过200,所以要填0到0x400处】

4  块
  4-1  .text (* 此区段是一段汇编代码的16进制形式,功能是弹出一个MessageBox提示框)
    -->HEX
    6A 00 68 00 30 40 00 68  07 30 40 00 6A 00 E8 07
    00 00 00 6A 00 E8 06 00  00 00 FF 25 08 20 40 00
    FF 25 00 20 40 00 
    -->ASM
    00401000  6A 00         PUSH 0
    00401002  68 00304000   PUSH first_PE.00403000
    00401007  68 07304000   PUSH first_PE.00403007
    0040100C  6A 00         PUSH 0
    0040100E  E8 07000000   CALL <JMP.&user32.MessageBoxA>
    00401013  6A 00         PUSH 0
    00401015  E8 06000000   CALL <JMP.&kernel32.ExitProcess>
    0040101A  FF25 08204000 JMP DWORD PTR DS:[<&user32.MessageBoxA>]
    00401020  FF25 00204000 JMP DWORD PTR DS:[<&kernel32.ExitProcess>]
    【由于FileAlignment为0x200大小,而此时整个PE头未超过200,所以要填0到0x600处】
  4-2  .rdata (* 该区块包含输入表)
    4-2-1  IMAGE_THUNK_DATA32 (IAT 1)
      4-2-1-1 AddressOfData [DWORD] -->76 20 00 00 (* 指向IMAGE_IMPORT_BY_NAME的RVA)
      【由于本例子两个API函数引自与两个不同的DLL文件,所以要补00000000h结束。】
    4-2-2  IMAGE_THUNK_DATA32 (IAT 2)
      4-2-1-2 AddressOfData [DWORD] -->5C 20 00 00 (* 指向IMAGE_IMPORT_BY_NAME的RVA)
      【由于本例子两个API函数引自与两个不同的DLL文件,所以要补00000000h结束。】
    4-2-3  IMAGE_IMPORT_DESCRIPTOR (IID 1)
      4-2-3-1 OriginalFirstThunk [DWORD] -->4C 20 00 00 (* 指向输入名称表INT的RVA)
      4-2-3-2 TimeDateStamp      [DWORD]  ->00 00 00 00 (32位的时间标志)
      4-2-3-3 ForwarderChain     [DWORD]  ->00 00 00 00 (被转向API的索引)
      4-2-3-4 Name               [DWORD] -->6A 20 00 00 (* 指向DLL名字的RVA与指针)
      4-2-3-5 FirstThunk         [DWORD] -->08 20 00 00 (* 指向输入地址表IAT的RVA)
    4-2-4  IMAGE_IMPORT_DESCRIPTOR (IID 2)
      4-2-4-1 OriginalFirstThunk [DWORD] -->54 20 00 00 (* 指向输入名称表INT的RVA)
      4-2-4-2 TimeDateStamp      [DWORD]  ->00 00 00 00 (32位的时间标志)
      4-2-4-3 ForwarderChain     [DWORD]  ->00 00 00 00 (被转向API的索引)
      4-2-4-4 Name               [DWORD] -->84 20 00 00 (* 指向DLL名字的RVA与指针)
      4-2-4-5 FirstThunk         [DWORD] -->00 20 00 00 (* 指向输入地址表IAT的RVA)
      【填充20个00h空字节做结尾标记】
    4-2-5  IMAGE_THUNK_DATA32 (INT 1)
      4-2-5-1  ForwarderString [DWORD] -->5C 20 00 00 (* 指向一个转向字符的RVA)
      【由于本例子两个API函数引自与两个不同的DLL文件,所以要补00000000h结束。】
    4-2-6  IMAGE_THUNK_DATA32 (INT 2)
      4-2-6-1  ForwarderString [DWORD] -->76 20 00 00 (* 指向一个转向字符的RVA)
      【由于本例子两个API函数引自与两个不同的DLL文件,所以要补00000000h结束。】
    4-2-7  IMAGE_IMPORT_BY_NAME ( 1 )
      4-2-7-1  Hint [WORD] -->00 00 (* 此函数所驻留DLL的输出表序号)
      4-2-7-2  Name [BYTE] -->4D 65 73 73 61 67 65 42 6F 78 41 (* MessageBoxA的16进制码)
      【后跟输出此函数的DLL名称16进制码00 75 73 65 72 33 32 2E 64 6C 6C 00 00 user32.dll】
    4-2-8  IMAGE_IMPORT_BY_NAME ( 2 )
      4-2-8-1  Hint [WORD] -->00 00h  (* 此函数所驻留DLL的输出表序号)
      4-2-8-2  Name [BYTE] -->45 78 69 74 50 72 6F 63 65 73 73 (* ExitProcess的16进制码)
      【后跟输出此函数的DLL名称16进制码00 6B 65 72 6E 65 6C 33 32 2E 64 6C 6C 00 00 kernel32.dll】
      【由于FileAlignment为0x200大小,而此时整个PE头未超过200,所以要填0到0x800处】
  4-3  .data
       填充数据
       -->HEX
       CF FB CF A2 BF F2 00 54  68 65 20 66 69 72 73 74
       20 50 45 20 66 69 6C 65  21 20 42 59 3A 41 31 50
       61 73 73 20 48 74 74 70  3A 2F 2F 61 31 70 61 73
       73 2E 62 6C 6F 67 2E 31  36 33 2E 63 6F 6D 00
       -->TEXT
       消息框 The first PE file! BY:A1Pass Http://a1pass.blog.163.com
      【由于FileAlignment为0x200大小,而此时整个PE头未超过200,所以要填0到0x9FF处】

    到此,介绍手工构造典型PE文件的知识就讲完了,虽然过程枯燥,但是当第一个first_PE.EXE从你手中诞生时,你会觉得这一切都太值了!这点你应该相信我,因为我当时就是这种感觉,最后祝各位成功!


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 7
支持
分享
最新回复 (7)
雪    币: 54
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
重温一遍,这东西多了去了。
如果用fasm写一个程序,对IAT表就会认识得更清楚。
2009-8-2 02:18
0
雪    币: 168
活跃值: (152)
能力值: ( LV11,RANK:180 )
在线值:
发帖
回帖
粉丝
3
路过……
过来支持一下啊……
2010-5-11 22:18
0
雪    币: 1731
活跃值: (22)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
好文!厉害啊!
2010-5-12 05:57
0
雪    币: 211
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
好好了解PE原理
2010-5-14 22:39
0
雪    币: 61
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
ding,jiayou啊
2010-8-8 16:38
0
雪    币: 50
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
老实说,看不懂哦!
不知道有什么好的建议没?
我想我基础太差了点,,,
2010-8-10 19:23
0
雪    币: 212
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
一次不懂再看一次,一直看到懂去,我也是看不懂滴,多看几次好象就有点理解了。
2010-8-11 17:17
0
游客
登录 | 注册 方可回帖
返回
//