1.2 4.PE结构、SEH相关知识掌握

  今天这节课程中,我们来讲解一下部分PE结构,以及SEH[Structured Exception Handling(结构化异常处理)]的知识。因为它在我们病毒编写中也是非常重要的。想象一下,如果我们连我们的被感染对象的文件格式都不熟悉的话,我们如何感染? 那么在上几节课程中,我们在文章中已经穿插了部分PE结构的内容,今天我们就来再深入的熟悉一下PE结构以及SEH的相关知识,为我们后面的文章做好铺垫。


1. 抛弃链接器, 手工定义PE结构程序:

  当然我不喜欢很传统的给大家罗列一堆PE结构,然后来给大家讲解结构成员,我还是喜欢实际的的编写。所以这节课程,我们就通过编写一个自定义PE结构的程序来让大家充分的了解PE结构。(实际上在fasm中也就是抛弃fasm所有的宏,然后以二进制数据形式编译)

  好了,我们了解下我们的PE结构组成部分。

  |----------------|
  |IMAGE_DOS_HEADER|  ------ IMAGE_DOS_HEADER
  |----------------|
  |     Dos STUB   |  ------ 16位DOS 代码
  |                |
  |----------------| 
  |                |  ------ Signature
  |                | 
  |IMAGE_NT_HEADERS|  ------ IMAGE_FILE_HEADER
  |                | 
  |                |  ------ IMAGE_OPTIONAL_HEADER32
  |----------------| 
  |                |
  | SECTION_HEADER |
  |                |  ------ 节表结构
  |----------------|
  |                |
  |    SECTION     |  ------ 节
  |                |
  |----------------|
  OK,了解了以上后,我们就来手工的定义一个PE文件结构的程序,我们仅让它拥有一个节。那么接下来我们来一步一步的构造整个文件结构,首先是IMAGE_DOS_HEADER, 然后是Dos STUB[要不要都行],喜欢的就加上吧,...........一直到Section。

  我们在开始之前要定义几个常量,方便后续的添加。首先是我们的基地址。我们定义为 
        Image_Base = 00400000h
  然后是我们内存对齐值我们设置为1000h,以及我们的节映射到内存基于基地址的偏移 我们也设置为 1000h。那么我们定义
   Code_Base = 1000h

  好了,接下来我们就开始一步步的定义文件结构, 首先是IMAGE_DOS_HEADER 结构。

struct IMAGE_DOS_HEADER 
  e_magic           dw      ?
  e_cblp            dw      ?
  e_cp              dw      ?
  e_crlc            dw      ?
  e_cparhdr         dw      ?
  e_minalloc        dw      ?
  e_maxalloc        dw      ?
  e_ss              dw      ?
  e_sp              dw      ?
  e_csum            dw      ?
  e_ip              dw      ?
  e_cs              dw      ?
  e_lfarlc          dw      ?
  e_ovno            dw      ?
  e_res             dw   4 dup(?)
  e_oemid           dw      ?
  e_oeminfo         dw      ?
  e_res2            dw  10 dup(?)
  e_lfanew          dd      ?
ends


  IMAGE_DOS_HEADER结构中最重要的两个成员是e_magic,和e_lfanew。前者是两个字节的“MZ”标识符,后者是4个字节的指向IMAGE_NT_HEADERS结构的文件偏移。所以其余成员我们可以以0代替。定义如下


DOS_HEADER:
.e_magic           db    'MZ'
.e_cblp            dw      0
.e_cp              dw      0
.e_crlc            dw      0
.e_cparhdr         dw      0
.e_minalloc        dw      0
.e_maxalloc        dw      0
.e_ss              dw      0
.e_sp              dw      0
.e_csum            dw      0
.e_ip              dw      0
.e_cs              dw      0
.e_lfarlc          dw      0
.e_ovno            dw      0
.e_res             dw   4 dup(0)
.e_oemid           dw      0
.e_oeminfo         dw      0
.e_res2            dw  10 dup(0)
.e_lfanew          dd  NT_HEADERS
;-------------------------------------
Dos_Stub:
  mov ah, 4ch
  int 21h
;------------------------------------

NT_HEADERS:


  OK,我们定义完DOS_HEADER,我们来定义NT_HEADERS结构。

[1]:

struct IMAGE_NT_HEADERS
  Signature        dd  ?
  FileHeader        IMAGE_FILE_HEADER
  OptionalHeader      IMAGE_OPTIONAL_HEADER32
ends

  1. 首先第一个成员是Signature4个字节PE文件标志,它被定义为00004550h。ASCII码表示法则为dd “PE”。
  2. 紧接着是我们的FileHeader成员。

[2]:


struct IMAGE_FILE_HEADER
  Machine      dw  ?
  NumberOfSections      dw  ?
  TimeDateStamp    dd  ?
  PointerToSymbolTable  dd  ?
  NumberOfSymbols    dd  ?
  SizeOfOptionalHeader  dw  ?
  Characteristics    dw  ?
ends

  
  (1): 第一个2字节成员Machine,表示运行平台。我们一般设置为14ch,表示 Intel I386。
  (2): 第二个2字节成员NumberOfSections,表示我们的节数量,这个程序中我们设置一个Section,所以我们填写为1。
  (3): 3 - 5 , 4字节成员因为没有什么用,所以我们设置为0.TimeDateStamp(文件创建时间) ,PointerToSymbolTable(COFF符号表的文件偏移量),NumberOfSymbols(符号表符号数量)。
  (4): SizeOfOptionalHeader成员表示的是下面IMAGE_OPTIONAL_HEADER32结构的字节大小。我们用默认的IMAGE_OPTIONAL_HEADER32字节大小,0E0h。
  (5): Characteristics成员表示的是文件的属性。我们普通的EXE文件一般是010fh。
    

[3:]
  接下来是我们的OptionalHeader结构成员。

struct IMAGE_OPTIONAL_HEADER32
  Magic        dw  ?
  MajorLinkerVersion    db  ?
  MinorLinkerVersion    db  ?
  SizeOfCode      dd  ?
  SizeOfInitializedData    dd  ?
  SizeOfUninitializedData    dd  ?
  AddressOfEntryPoint    dd  ?
  BaseOfCode      dd  ?
  BaseOfData      dd  ?
  ImageBase      dd  ?
  SectionAlignment    dd  ?
  FileAlignment      dd  ?
  MajorOperatingSystemVersion  dw  ?
  MinorOperatingSystemVersion  dw  ?
  MajorImageVersion               dw       ?
  MinorImageVersion               dw       ?
  MajorSubsystemVersion           dw       ?
  MinorSubsystemVersion           dw       ?
  Win32VersionValue               dd      ?
  SizeOfImage                     dd      ?
  SizeOfHeaders                   dd      ?
  CheckSum                        dd      ?
  Subsystem                       dw       ?
  DllCharacteristics              dw       ?
  SizeOfStackReserve              dd      ?
  SizeOfStackCommit               dd      ?
  SizeOfHeapReserve               dd      ?
  SizeOfHeapCommit                dd      ?
  LoaderFlags                     dd      ?
  NumberOfRvaAndSizes             dd      ?
  DataDirectory      IMAGE_DATA_DIRECTORY
  rb       sizeof.IMAGE_DATA_DIRECTORY*15
ends



  (1): 首先第一个成员.Magic是一个标记,一般我们普通的可执行文件,这个标记值为 010Bh。
  (2): 下面两个1字节MajorLinkerVersion一般为0.它表示为我们的链接程序的版本号。
  (3): SizeOfCode表示代码段的大小。这个大小值是代码段对齐后的值。这里我们设置的文件对齐值是200h,所以我们这里填写为200h。(也就是512byte)。
  (4): SizeOfInitializedData表示初始化数据的字节大小,不包括代码段。我们这里填写为0。
  (5): SizeOfUninitializedData表示未初始化数据的字节大小。即我们常说的.bbs节,这里我们没用。所以填写0。
  (6): AddressOfEntryPoint成员指向我们程序的OEP,这个值是一个RVA地址,相对于我们基地址的偏移。这里我们把这个值设置为我们定义的Code节的偏移。由于我们Code段节我们设置它映射内存的虚拟地址是1000h,所以我们需要通过这个1000h - Code段文件偏移  = 相对于Code段的偏移。 通过这个相对偏移 + 文件偏移 = RVA地址。我们把这个相对偏移定义为Rva_Del = Code_Base - @Code。
  (7): BaseOfCode表示代码段起始RVA地址。我们直接用Code_Base即可。
  (8): BaseOfData表示数据段的起始RVA地址。我们可以设置为0.
  (9): ImageBase表示我们的基地址。我们这里设置我们上面定义好的Image_Base。
  (10): MajorOperatingSystemVersion和MinorOperatingSystemVersion表示的是操作系统的主版本号,和次版本好,我们一般用不上。所以这两个成员可以设置为0.
  (11): MajorImageVersion, MinorImageVersion 表示用户自定义主、次版本号。对我们来说没用,设置为0.
  (12): MajorSubsystemVersion和MinorSubsystemVersion表示最低子系统版本号,我们通常设置MajorSubsystemVersion为4。
  (13): Win32VersionValue通常是保留值,一般为0.
  (14): SizeOfImage表示映像文件载入内存后的总大小。我们这里一个节 + 我们的PE头部。我们设置为2000h
  (15): SizeOfHeaders表示size(DosHeader + DosStub + NtHeader + Section Header)
  (16): CheckSum效验和值。一般EXE文件为0.
  (17): Subsystem表示子系统值。 2 GUI, 3 CUI。
  (18): DllCharacteristics指定DLLMain函数何时被调用。默认0.
  (19): SizeOfStackReserve 在exe文件中默认的线程堆栈保留大小。这里我们设置为10000h,因为它在需要时才被提交。
  (20): SizeOfStackCommit 表示开始委派的堆栈大小。默认是4KB。
  (21): SizeOfHeapReserve表示在exe文件中默认的堆空间保留大小,同样我们设置为10000h。
  (22): SizeOfHeapCommit表示开始委派的堆空间大小。默认是4KB。
  (23): LoaderFlags默认为0。
  (24): NumberOfRvaAndSizes表示下面数据目录段的成员数量。10h 
  (25): DataDirectory数据目录表。表中成员为16个IMAGE_DATA_DIRECTORY结构的成员。我们不需要任何结构,这里全部填0.

[4:]
  接下来是节表结构。
struct IMAGE_SECTION_HEADER
    Name1 rb 8
    union
        PhysicalAddress dd  ?
        VirtualSize dd      ?
    ends
    VirtualAddress dd       ?
    SizeOfRawData dd        ?
    PointerToRawData dd     ?
    PointerToRelocations dd ?
    PointerToLinenumbers dd ?
    NumberOfRelocations dw  ?
    NumberOfLinenumbers dw  ?
    Characteristics dd      ?
ends

  (1):首先第一个8字节成员表示节名称。
  (2):VirtualSize表示节映射内存的大小。我们这里填写为1000h。
  (3):VirtualAddress表示节空间映射到内存基于基地址的偏移。这里我们填写我们之前定义好的Code_Base。
  (4):SizeOfRawData表示节在磁盘空间中的大小。我们通过定义节代码指令的最后偏移 - 节起始偏移即可求出。
  (5): PointerToRawData表示节所在文件中的物理文件偏移。我们直接用我们定义的@Code 标号即可。
  (6): PointerToRelocations PointerToLinenumbers NumberOfRelocations 没什么用处直接填充0.
  (7): Characteristics表示节属性,这个属性很重要,它决定了节空间映射到内存后的内存空间属性。我们这里用E0000020(可读可写可执行)




  OK.到这里我们的结构就完成了,然后就是在我们的代码段偏移中写代码了。我们仅写一个ret指令。

OK。我们来看下我们最后的程序代码。

代码:
;-----------------------------------------
; Format Pe File  By:XFish
;------------------------------------------
 Code_Base   = 1000h
 File_Algin  = 200h
 Rva_Del     = Code_Base - @Code
 Image_Base  = 00400000h
;________________________________________________________________
DOS_HEADER:
.e_magic    db  'MZ'
.e_cblp           dw      0
.e_cp             dw      0
.e_crlc           dw      0
.e_cparhdr        dw      0
.e_minalloc       dw      0
.e_maxalloc       dw      0
.e_ss             dw      0
.e_sp             dw      0
.e_csum           dw      0
.e_ip             dw      0
.e_cs             dw      0
.e_lfarlc           dw      0
.e_ovno           dw      0
.e_res            dw   4 dup(0)
.e_oemid          dw      0
.e_oeminfo        dw      0
.e_res2           dw  10 dup(0)
.e_lfanew         dd  NT_HEADERS



Dos_Stub:
  mov ah, 4ch
  int 21h
  
  

NT_HEADERS:
.Signature    dd  'PE'
;++          ;IMAGE_FILE_HEADER
.Machine    dw  14ch     ;.Inten I386
.NumberOfSections   dw  1
.TimeDateStamp    dd  0
.PointerToSymbolTable  dd  0
.NumberOfSymbols  dd  0  
.SizeOfOptionalHeader  dw  0E0h
.Characteristics  dw  010fh    ;.exe Signature
;--
;++          ;IMAGE_OPTIONAL_HEADER32
.Magic      dw  10Bh   ;.exe file
.MajorLinkerVersion  db  0
.MinorLinkerVersion  db  0
.SizeOfCode    dd  File_Algin
.SizeOfInitializedData  dd  0
.SizeOfUninitializedData dd  0
.AddressOfEntryPoint  dd  @Code + Rva_Del ;oep
.BaseOfCode    dd  Code_Base  ;Code Section Rva
.BaseOfData     dd  0    ;Data Section Rva
.ImageBase    dd  Image_Base
.SectionAlignment  dd  Code_Base  ;Section Mem Align
.FileAlignment    dd  File_Algin  ;Section Disk Align
.MajorOperSystemVersion dw  0    ;system version 主
.MinorOperSystemVersion dw  0    ;system version 次
.MajorImageVersion  dw  0    ;user version   主
.MinorImageVersion  dw  0    ;user version   次
.MajorSubsystemVersion  dw  4
.MinorSubsystemVersion  dw  0
.Win32VersionValue  dd  0    ;Reserved 0
.SizeOfImage    dd  2000h
.SizeOfHeaders    dd  File_Algin  ;DosHeader + DosStub + NtHeader + Section Header
.CheckSum    dd  0
.SubSystem    dw  2    ;Gui
.DllCharacteristics  dw  0
.SizeOfStackReserve  dd  10000h    
.SizeOfStackCommit  dd  1000h    ;Stack = 4kb
.SizeOfHeapReserve  dd  10000h
.SizeOfHeapCommit  dd  1000h    ;Heap = 4kb
.LoaderFlags    dd  0
.NumberOfRvaAndSizes  dd  10h    ;16
.Export                 rd   2
.Import      rd  2
.Misc_Section    rd  28
  

_SECTION_HEADER:
.Name1      db  '.code', 0, 0, 0
.VirtualSize    dd  1000h
.VirtualAddress    dd  Code_Base
.SizeOfRawData    dd  @Code_End - @Code
.PointerToRawData  dd  @Code
.PointerToRelocations   dd   0
.PointerToLinenumbers   dd   0
.NumberOfRelocations    dw   0
.NumberOfLinenumbers    dw   0
.Characteristics  dd  0E0000020h

align  200h
;------------------------------------

@Code:

  ret
  

@Code_End:
  这里留个问题给大家想想,此代码编译完后程序体积是多少字节呢?

  好了,通过以上的编码,相信你对PE结构已经很熟悉了。那么我们继续往下吧,我们来学习SEH技术的利用。


(2): SEH技术的利用。

  关于seh技术,很久之前Hume就总结过了,所以我在这里不想过多总结了。大家看可以在这个地址中看下Hume的文章。
    http://www.pediy.com/bbshtml/bbs4/kanxue310.htm

  我要讲解下关于seh技术的利用,例如如何在我们的汇编自程序当中利用技巧,方便的排错。

  
  传统的SEH的框架我想我就不多说了。
  如下:
  push    SEH_Handler
  push    dword ptr fs:[0]
  mov  [fs:0], esp
`  
大家看如下代码:

代码:
        call  .SetupSEH  
.ErrorHandler:
  mov  esp, [esp+8]  ;[esp+8],指向lpseh。lpseh是我们EXCEPTION_REGISTRATION结构的地址。
  xor  eax, eax
  pop  dword [fs:eax]  ;取出EXCEPTION_REGISTRATION->prev ;前一个_EXCEPTION_REGISTRATION结构
  pop  ecx    ;修正堆栈
  ret      ;ExitThread
  
.SetupSEH:
  xor  edx, edx
  push  dword [fs:edx]
  mov  [fs:edx], esp
  int  3
  这段代码是如果异常发生,则我们的异常过程将esp堆栈指针修复,然后恢复[fs:0]的成员。然后修正堆栈,退出线程。我们平时封装子程序的时候或者是写线程处理过程的时候完全可以利用以上代码方便的封装。

  OK,写一段我们自定义PE文件结构的异常处理过程代码。
代码:
;-----------------------------------------
; Format Pe File  By:XFish
;------------------------------------------
 include 'win32a.inc'
 Code_Base   = 1000h
 File_Algin  = 200h
 Rva_Del     = Code_Base - @Code
 Image_Base  = 00400000h
;________________________________________________________________
DOS_HEADER:
.e_magic    db  'MZ'
.e_cblp           dw      0
.e_cp             dw      0
.e_crlc           dw      0
.e_cparhdr        dw      0
.e_minalloc       dw      0
.e_maxalloc       dw      0
.e_ss             dw      0
.e_sp             dw      0
.e_csum           dw      0
.e_ip             dw      0
.e_cs             dw      0
.e_lfarlc           dw      0
.e_ovno           dw      0
.e_res            dw   4 dup(0)
.e_oemid          dw      0
.e_oeminfo        dw      0
.e_res2           dw  10 dup(0)
.e_lfanew         dd  NT_HEADERS



Dos_Stub:
  mov ah, 4ch
  int 21h
  
  

NT_HEADERS:
.Signature    dd  'PE'
;++          ;IMAGE_FILE_HEADER
.Machine    dw  14ch     ;.Inten I386
.NumberOfSections   dw  1
.TimeDateStamp    dd  0
.PointerToSymbolTable  dd  0
.NumberOfSymbols  dd  0  
.SizeOfOptionalHeader  dw  0E0h
.Characteristics  dw  010fh    ;.exe Signature
;--
;++          ;IMAGE_OPTIONAL_HEADER32
.Magic      dw  10Bh   ;.exe file
.MajorLinkerVersion  db  0
.MinorLinkerVersion  db  0
.SizeOfCode    dd  File_Algin
.SizeOfInitializedData  dd  0
.SizeOfUninitializedData dd  0
.AddressOfEntryPoint  dd  @Code + Rva_Del ;oep
.BaseOfCode    dd  Code_Base  ;Code Section Rva
.BaseOfData     dd  0    ;Data Section Rva
.ImageBase    dd  Image_Base
.SectionAlignment  dd  Code_Base  ;Section Mem Align
.FileAlignment    dd  File_Algin  ;Section Disk Align
.MajorOperSystemVersion dw  0    ;system version 主
.MinorOperSystemVersion dw  0    ;system version 次
.MajorImageVersion  dw  0    ;user version   主
.MinorImageVersion  dw  0    ;user version   次
.MajorSubsystemVersion  dw  4
.MinorSubsystemVersion  dw  0
.Win32VersionValue  dd  0    ;Reserved 0
.SizeOfImage    dd  @Code_End - @Code + 1000h
.SizeOfHeaders    dd  File_Algin  ;DosHeader + DosStub + NtHeader + Section Header
.CheckSum    dd  0
.SubSystem    dw  2    ;Gui
.DllCharacteristics  dw  0
.SizeOfStackReserve  dd  10000h    
.SizeOfStackCommit  dd  1000h    ;Stack = 4kb
.SizeOfHeapReserve  dd  10000h
.SizeOfHeapCommit  dd  1000h    ;Heap = 4kb
.LoaderFlags    dd  0
.NumberOfRvaAndSizes  dd  10h    ;16
.Export                 rd   2
.Import      rd  2
.Misc_Section    rd  28
  

_SECTION_HEADER:
.Name1      db  '.code', 0, 0, 0
.VirtualSize    dd  1000h
.VirtualAddress    dd  Code_Base
.SizeOfRawData    dd  @Code_End - @Code
.PointerToRawData  dd  @Code
.PointerToRelocations   dd   0
.PointerToLinenumbers   dd   0
.NumberOfRelocations    dw   0
.NumberOfLinenumbers    dw   0
.Characteristics  dd  0E0000020h

align  200h
;------------------------------------

@Code:
  db  0E8h    ;call opcode
  dd  SetupSEH - ErrorHandler  
ErrorHandler:
  db  8bh
  db  64h
  db  24h
  db  08h     ;mov esp, [esp+8]
  db  31h
  db  0c0h    ;xor eax, eax
  db  64h
  db  8fh
  db  00    ;pop dword [fs:0]
  db  59h    ;pop ecx
  ret
  
SetupSEH:
  db  31h
  db  0d2h    ;xor edx, edx
  db  64h    
  db  0ffh
  db  32h    ;push dword [fs:0]
  db  64h
  db  89h
  db  22h    ;mov dword [fs:0], esp
  db  0cdh    
  db  03h    ;int 3
  ret
  
@Code_End:
  OK,今天的这篇文章就到这里了。我们下篇文章再见。以下是随文的程序和代码。。
上传的附件 bin.rar
seh.rar