科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网安全频道一步一步教你加密解密技术——压缩与脱壳(1)

一步一步教你加密解密技术——压缩与脱壳(1)

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

PE 的意思就是 Portable Executable(可移植的执行体)。它是 Win32环境自身所带的执行体文件格式。它的一些特性继承自 Unix的 Coff (common object file format)文件格式。

作者:看雪 来源:看雪 2008年10月16日

关键字: 压缩 脱壳 加密解密

  • 评论
  • 分享微博
  • 分享邮件

在本页阅读全文(共2页)

  压缩与脱壳

  第一节 PE文件格式

  PE教程1: PE文件格式一览

  PE 的意思就是 Portable Executable(可移植的执行体)。它是 Win32环境自身所带的执行体文件格式。它的一些特性继承自 Unix的 Coff (common object file format)文件格式。"portable executable"(可移植的执行体)意味着此文件格式是跨win32平台的 : 即使Windows运行在非Intel的CPU上,任何win32平台的PE装载器都能识别和使用该文件格式。当然,移植到不同的CPU上PE执行体必然得有一些改变。所有 win32执行体 (除了VxD和16位的Dll)都使用PE文件格式,包括NT的内核模式驱动程序(kernel mode drivers)。因而研究PE文件格式给了我们洞悉Windows结构的良机。

  本教程就让我们浏览一下 PE文件格式的概要。

  

DOS MZ header
DOS stub
PE header
Section table
Section 1
Section 2
Section ...
Section n

  上图是 PE文件结构的总体层次分布。所有 PE文件(甚至32位的 DLLs) 必须以一个简单的 DOS MZ header 开始。我们通常对此结构没有太大兴趣。有了它,一旦程序在DOS下执行,DOS就能识别出这是有效的执行体,然后运行紧随 MZ header 之后的 DOS stub。DOS stub实际上是个有效的 EXE,在不支持 PE文件格式的操作系统中,它将简单显示一个错误提示,类似于字符串 "This program requires Windows" 或者程序员可根据自己的意图实现完整的 DOS代码。通常我们也不对 DOS stub 太感兴趣: 因为大多数情况下它是由汇编器/编译器自动生成。通常,它简单调用中断21h服务9来显示字符串"This program cannot run in DOS mode"。

  紧接着 DOS stub 的是PE header。 PE header 是PE相关结构IMAGE_NT_HEADERS的简称,其中包含了许多PE装载器用到的重要域。当我们更加深入研究PE文件格式后,将对这些重要域耳目能详。执行体在支持PE文件结构的操作系统中执行时,PE装载器将从 DOS MZ header中找到 PE header的起始偏移量。因而跳过了 DOS stub 直接定位到真正的文件头 PE header。

  PE文件的真正内容划分成块,称之为sections(节)。每节是一块拥有共同属性的数据,比如代码/数据、读/写等。我们可以把PE文件想象成一逻辑磁盘,PE header 是磁盘的boot扇区,而sections就是各种文件,每种文件自然就有不同属性如只读、系统、隐藏、文档等等。值得我们注意的是 ---- 节的划分是基于各组数据的共同属性: 而不是逻辑概念。重要的不是数据/代码是如何使用的,如果PE文件中的数据/代码拥有相同属性,它们就能被归入同一节中。不必关心节中类似于"data", "code"或其他的逻辑概念: 如果数据和代码拥有相同属性,它们就可以被归入同一个节中。(译者注:节名称仅仅是个区别不同节的符号而已,类似"data", "code"的命名只为了便于识别,惟有节的属性设置决定了节的特性和功能)如果某块数据想付为只读属性,就可以将该块数据放入置为只读的节中,当PE装载器映射节内容时,它会检查相关节属性并置对应内存块为指定属性。

  如果我们将PE文件格式视为一逻辑磁盘,PE header是boot扇区而sections是各种文件,但我们仍缺乏足够信息来定位磁盘上的不同文件,譬如,什么是PE文件格式中等价于目录的东东?别急,那就是 PE header 接下来的数组结构section table(节表)。 每个结构包含对应节的属性、文件偏移量、虚拟偏移量等。如果PE文件里有5个节,那么此结构数组内就有5个成员。因此,我们便可以把节表视为逻辑磁盘中的根目录,每个数组成员等价于根目录中目录项。

  以上就是PE文件格式的物理分布,下面将总结一下装载一PE文件的主要步骤:

  当PE文件被执行,PE装载器检查 DOS MZ header 里的 PE header 偏移量。如果找到,则跳转到 PE header。

  PE装载器检查 PE header 的有效性。如果有效,就跳转到PE header的尾部。

  紧跟 PE header 的是节表。PE装载器读取其中的节信息,并采用文件映射方法将这些节映射到内存,同时付上节表里指定的节属性。

  PE文件映射入内存后,PE装载器将处理PE文件中类似 import table(引入表)逻辑部分。

  上述步骤是基于本人观察后的简述,显然还有一些不够精确的地方,但基本明晰了执行体被处理的过程。

  PE教程2: 检验PE文件的有效性

  理论:

  如何才能校验指定文件是否为一有效PE文件呢? 这个问题很难回答,完全取决于想要的精准程度。您可以检验PE文件格式里的各个数据结构,或者仅校验一些关键数据结构。大多数情况下,没有必要校验文件里的每一个数据结构,只要一些关键数据结构有效,我们就认为是有效的PE文件了。下面我们就来实现前面的假设。

  我们要验证的重要数据结构就是 PE header。从编程角度看,PE header 实际就是一个 IMAGE_NT_HEADERS 结构。定义如下:

  IMAGE_NT_HEADERS STRUCT

  Signature dd ?

  FileHeader IMAGE_FILE_HEADER <>

  OptionalHeader IMAGE_OPTIONAL_HEADER32 <>

  IMAGE_NT_HEADERS ENDS

  Signature一dword类型,值为50h, 45h, 00h, 00h(PE\0\0)。本域为PE标记,我们可以此识别给定文件是否为有效PE文件。

  FileHeader 该结构域包含了关于PE文件物理分布的信息,比如节数目、文件执行机器等。

  OptionalHeader该结构域包含了关于PE文件逻辑分布的信息,虽然域名有"可选"字样,但实际上本结构总是存在的。

  我们目的很明确。如果IMAGE_NT_HEADERS的signature域值等于"PE\0\0",那么就是有效的PE文件。实际上,为了比较方便,Microsoft已定义了常量IMAGE_NT_SIGNATURE供我们使用。

  IMAGE_DOS_SIGNATURE equ 5A4Dh

  IMAGE_OS2_SIGNATURE equ 454Eh

  IMAGE_OS2_SIGNATURE_LE equ 454Ch

  IMAGE_VXD_SIGNATURE equ 454Ch

  IMAGE_NT_SIGNATURE equ 4550h

  接下来的问题是: 如何定位 PE header? 答案很简单: DOS MZ header 已经包含了指向 PE header 的文件偏移量。DOS MZ header 又定义成结构IMAGE_DOS_HEADER。查询windows.inc,我们知道 IMAGE_DOS_HEADER结构的e_lfanew成员就是指向 PE header 的文件偏移量。

  现在将所有步骤总结如下:

  首先检验文件头部第一个字的值是否等于 IMAGE_DOS_SIGNATURE是则 DOS MZ header 有效。

  一旦证明文件的 DOS header 有效后,就可用e_lfanew来定位 PE header 了。

  比较 PE header 的第一个字的值是否等于IMAGE_NT_HEADER。如果前后两个值都匹配,那我们就认为该文件是一个有效的PE文件。

  Example:

  .386

  .model flat,stdcall

  option casemap:none

  include \masm32\include\windows.inc

  include \masm32\include\kernel32.inc

  include \masm32\include\comdlg32.inc

  include \masm32\include\user32.inc

  includelib \masm32\lib\user32.lib

  includelib \masm32\lib\kernel32.lib

  includelib \masm32\lib\comdlg32.lib

  SEH struct

  PrevLink dd ? ;the address of the previous seh structure

  CurrentHandler dd ? ;the address of the exception handler

  SafeOffset dd ? ;The offset where it's safe to continue execution

  PrevEsp dd ? ;the old value in esp

  PrevEbp dd ? ;The old value in ebp

  SEH ends

  .data

  AppName db "PE tutorial no.2",0

  ofn OPENFILENAME <>

  FilterString db "Executable Files (*.exe, *.dll)",0,"*.exe;*.dll",0

  db "All Files",0,"*.*",0,0

  FileOpenError db "Cannot open the file for reading",0

  FileOpenMappingError db "Cannot open the file for memory mapping",0

  FileMappingError db "Cannot map the file into memory",0

  FileValidPE db "This file is a valid PE",0

  FileInValidPE db "This file is not a valid PE",0

  .data?

  buffer db 512 dup(?)

  hFile dd ?

  hMapping dd ?

  pMapping dd ?

  ValidPE dd ?

  .code

  start proc

  LOCAL seh:SEH

  mov ofn.lStructSize,SIZEOF ofn

  mov ofn.lpstrFilter, OFFSET FilterString

  mov ofn.lpstrFile, OFFSET buffer

  mov ofn.nMaxFile,512

  mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY

  invoke GetOpenFileName, ADDR ofn

  .if eax==TRUE

  invoke CreateFile, addr buffer, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL

  .if eax!=INVALID_HANDLE_VALUE

  mov hFile, eax

  invoke CreateFileMapping, hFile, NULL, PAGE_READONLY,0,0,0

  .if eax!=NULL

  mov hMapping, eax

  invoke MapViewOfFile,hMapping,FILE_MAP_READ,0,0,0

  .if eax!=NULL

  mov pMapping,eax

  assume fs:nothing

  push fs:[0]

  pop seh.PrevLink

  mov seh.CurrentHandler,offset SEHHandler

  mov seh.SafeOffset,offset FinalExit

  lea eax,seh

  mov fs:[0], eax

  mov seh.PrevEsp,esp

  mov seh.PrevEbp,ebp

  mov edi, pMapping

  assume edi:ptr IMAGE_DOS_HEADER

  .if [edi].e_magic==IMAGE_DOS_SIGNATURE

  add edi, [edi].e_lfanew

  assume edi:ptr IMAGE_NT_HEADERS

  .if [edi].Signature==IMAGE_NT_SIGNATURE

  mov ValidPE, TRUE

  .else

  mov ValidPE, FALSE

  .endif

  .else

  mov ValidPE,FALSE

  .endif

  FinalExit:

  .if ValidPE==TRUE

  invoke MessageBox, 0, addr FileValidPE, addr AppName, MB_OK+MB_ICONINFORMATION

  .else

  invoke MessageBox, 0, addr FileInValidPE, addr AppName, MB_OK+MB_ICONINFORMATION

  .endif

  push seh.PrevLink

  pop fs:[0]

  invoke UnmapViewOfFile, pMapping

  .else

  invoke MessageBox, 0, addr FileMappingError, addr AppName, MB_OK+MB_ICONERROR

  .endif

  invoke CloseHandle,hMapping

  .else

  invoke MessageBox, 0, addr FileOpenMappingError, addr AppName, MB_OK+MB_ICONERROR

  .endif

  invoke CloseHandle, hFile

  .else

  invoke MessageBox, 0, addr FileOpenError, addr AppName, MB_OK+MB_ICONERROR

  .endif

  .endif

  invoke ExitProcess, 0

  start endp

  SEHHandler proc uses edx pExcept:DWORD, pFrame:DWORD, pContext:DWORD, pDispatch:DWORD

  mov edx,pFrame

  assume edx:ptr SEH

  mov eax,pContext

  assume eax:ptr CONTEXT

  push [edx].SafeOffset

  pop [eax].regEip

  push [edx].PrevEsp

  pop [eax].regEsp

  push [edx].PrevEbp

  pop [eax].regEbp

  mov ValidPE, FALSE

  mov eax,ExceptionContinueExecution

  ret

  SEHHandler endp

  end start

  分析:

  本例程打开一文件,先检验DOS header是否有效,有效就接着检验PE header的有效性,ok就认为是有效的PE文件了。这里,我们还运用了结构异常处理(SEH),这样就不必检查每个可能的错误: 如果有错误出现,就认为PE检测失效所致,于是给出我们的报错信息。其实Windows内部普遍使用SEH来检验参数传递的有效性。若对SEH感兴趣的话,可阅读Jeremy Gordon的文章。

  程序调用打开文件通用对话框,用户选定执行文件后,程序便打开文件并映射到内存。并在有效性检验前建立一SEH:

  assume fs:nothing

  push fs:[0]

  pop seh.PrevLink

  mov seh.CurrentHandler,offset SEHHandler

  mov seh.SafeOffset,offset FinalExit

  lea eax,seh

  mov fs:[0], eax

  mov seh.PrevEsp,esp

  mov seh.PrevEbp,ebp

  一开始就假设寄存器fs为空(assume fs:nothing)。记住这一步不能省却,因为MASM假设fs寄存器为ERROR。接下来保存Windows使用的旧SEH处理函数地址到我们自己定义的结构中,同时保存我们的SEH处理函数地址和异常处理时的执行恢复地址,这样一旦错误发生就能由异常处理函数安全地恢复执行了。同时还保存当前esp及ebp的值,以便我们的SEH处理函数将堆栈恢复到正常状态。

  mov edi, pMapping

  assume edi:ptr IMAGE_DOS_HEADER

  .if [edi].e_magic==IMAGE_DOS_SIGNATURE

  成功建立SEH后继续校验工作。置目标文件的首字节地址给edi,使其指向DOS header的首字节。为便于比较,我们告诉编译器可以假定edi正指向IMAGE_DOS_HEADER结构(事实亦是如此)。然后比较DOS header的首字是否等于字符串"MZ",这里利用了windows.inc中定义的IMAGE_DOS_SIGNATURE常量。若比较成功,继续转到PE header,否则设ValidPE值为FALSE,意味着文件不是有效PE文件。

  add edi, [edi].e_lfanew

  assume edi:ptr IMAGE_NT_HEADERS

  .if [edi].Signature==IMAGE_NT_SIGNATURE

  mov ValidPE, TRUE

  .else

  mov ValidPE, FALSE

  .endif

  要定位到PE header,需要读取DOS header中的e_lfanew域值。该域含有PE header在文件中相对文件首部的偏移量。edi加上该值正好定位到PE header的首字节。这儿可能会出错,如果文件不是PE文件,e_lfanew值就不正确,加上该值作为指针就可能导致异常。若不用SEH,我们必须校验e_lfanew值是否超出文件尺寸,这不是一个好办法。如果一切OK,我们就比较PE header的首字是否是字符串"PE"。这里在此用到了常量IMAGE_NT_SIGNATURE,相等则认为是有效的PE文件。

  如果e_lfanew的值不正确导致异常,我们的SEH处理函数就得到执行控制权,简单恢复堆栈指针和基栈指针后,就根据safeoffset的值恢复执行到FinalExit标签处。

  FinalExit:

  .if ValidPE==TRUE

  invoke MessageBox, 0, addr FileValidPE, addr AppName, MB_OK+MB_ICONINFORMATION

  .else

  invoke MessageBox, 0, addr FileInValidPE, addr AppName, MB_OK+MB_ICONINFORMATION

  .endif

  上述代码简单明确,根据ValidPE的值显示相应信息。

  push seh.PrevLink

  pop fs:[0]

  一旦SEH不再使用,必须从SEH链上断开。

  摘要:PE 的意思就是 Portable Executable(可移植的执行体)。它是 Win32环境自身所带的执行体文件格式。它的一些特性继承自 Unix的 Coff (common object file format)文件格式。

  标签:加密 解密 crack 破解

  PE教程3: File Header (文件头)

  本课我们将要研究 PE header 的 file header(文件头)部分。

  至此,我们已经学到了哪些东东,先简要回顾一下:

  DOS MZ header 又命名为IMAGE_DOS_HEADER.。其中只有两个域比较重要: e_magic包含字符串"MZ",e_lfanew包含PE header在文件中的偏移量。

  比较e_magic是否为IMAGE_DOS_SIGNATURE以验证是否是有效的DOS header。比对符合则认为文件拥有一个有效的DOS header。

  为了定位PE header,移动文件指针到e_lfanew所指向的偏移。

  PE header的第一个双字包含字符串"PE\0\0"。该双字与IMAGE_NT_SIGNATURE比对,符合则认为PE header有效。

  本课我们继续探讨关于 PE header 的知识。 PE header 的正式命名是 IMAGE_NT_HEADERS。再来回忆一下这个结构。

  IMAGE_NT_HEADERS STRUCT

  Signature dd ?

  FileHeader IMAGE_FILE_HEADER <>

  OptionalHeader IMAGE_OPTIONAL_HEADER32 <>

  IMAGE_NT_HEADERS ENDS

  SignaturePE标记,值为50h, 45h, 00h, 00h(PE\0\0)。

  FileHeader该结构域包含了关于PE文件物理分布的一般信息。

  OptionalHeader 该结构域包含了关于PE文件逻辑分布的信息。

  最有趣的东东在OptionalHeader里。不过,FileHeader里的一些域也很重要。本课我们将学习FileHeader下一课研究OptionalHeader

  IMAGE_FILE_HEADER STRUCT

  Machine WORD ?

  NumberOfSections WORD ?

  TimeDateStamp dd ?

  PointerToSymbolTable dd ?

  NumberOfSymbols dd ?

  SizeOfOptionalHeader WORD ?

  Characteristics WORD ?

  IMAGE_FILE_HEADER ENDS

  

Field name Meanings
Machine 该文件运行所要求的CPU。对于Intel平台,该值是IMAGE_FILE_MACHINE_I386 (14Ch)。我们尝试了LUEVELSMEYERpe.txt声明的14Dh14Eh,但Windows不能正确执行。看起来,除了禁止程序执行之外,本域对我们来说用处不大。
NumberOfSections 文件的节数目。如果我们要在文件中增加或删除一个节,就需要修改这个值。
TimeDateStamp 文件创建日期和时间。我们不感兴趣。
PointerToSymbolTable 用于调试。
NumberOfSymbols 用于调试。
SizeOfOptionalHeader 指示紧随本结构之后的 OptionalHeader 结构大小,必须为有效值。
Characteristics 关于文件信息的标记,比如文件是exe还是dll

  简言之,只有三个域对我们有一些用:Machine, NumberOfSectionsCharacteristics。通常不会改变MachineCharacteristics的值,但如果要遍历节表就得使用NumberOfSections

  为了更好阐述 NumberOfSections 的用处,这里简要介绍一下节表。

  节表是一个结构数组,每个结构包含一个节的信息。因此若有3个节,数组就有3个成员。 我们需要NumberOfSections值来了解该数组中到底有几个成员。也许您会想检测结构中的全0成员起到同样效果。Windows确实采用了这种方法。为了证明这一点,可以增加NumberOfSections的值,Windows仍然可以正常执行文件。据我们的观察,Windows读取NumberOfSections的值然后检查节表里的每个结构,如果找到一个全0结构就结束搜索,否则一直处理完NumberOfSections指定数目的结构。为什么我们不能忽略NumberOfSections的值? 有几个原因。PE说明中没有指定节表必须以全0结构结束。Thus there may be a situation where the last array member is contiguous to the first section, without empty space at all. Another reason has to do with bound imports. The new-style binding puts the information immediately following the section table's last structure array member. 因此您仍然需要NumberOfSections。

  摘要:PE 的意思就是 Portable Executable(可移植的执行体)。它是 Win32环境自身所带的执行体文件格式。它的一些特性继承自 Unix的 Coff (common object file format)文件格式。

  标签:加密 解密 crack 破解

  PE教程4: Optional Header

  我们已经学习了关于DOS header 和 PE header 中部分成员的知识。这里是PE header 中最后、最大或许也是最重要的成员,optional header。

  回顾一下,optional header 结构是IMAGE_NT_HEADERS中的最后成员。包含了PE文件的逻辑分布信息。该结构共有31个域,一些是很关键,另一些不太常用。这里只介绍那些真正有用的域。

  这儿有个关于PE文件格式的常用术语: RVA

  RVA 代表相对虚拟地址。知道什么是虚拟地址吗?相对那些简单的概念而言,RVA有些晦涩。简言之,RVA是虚拟空间中到参考点的一段距离。我打赌您肯定熟悉文件偏移量: RVA就是类似文件偏移量的东西。当然它是相对虚拟空间里的一个地址,而不是文件头部。举例说明,如果PE文件装入虚拟地址(VA)空间的400000h处,且进程从虚址401000h开始执行,我们可以说进程执行起始地址在RVA 1000h。每个RVA都是相对于模块的起始VA的。

  为什么PE文件格式要用到RVA呢? 这是为了减少PE装载器的负担。因为每个模块多有可能被重载到任何虚拟地址空间,如果让PE装载器修正每个重定位项,这肯定是个梦魇。相反,如果所有重定位项都使用RVA,那么PE装载器就不必操心那些东西了: 它只要将整个模块重定位到新的起始VA。这就象相对路径和绝对路径的概念: RVA类似相对路径,VA就象绝对路径。

  

Field Meanings
AddressOfEntryPoint PE装载器准备运行的PE文件的第一个指令的RVA。若您要改变整个执行的流程,可以将该值指定到新的RVA,这样新RVA处的指令首先被执行。
ImageBase PE文件的优先装载地址。比如,如果该值是400000h,PE装载器将尝试把文件装到虚拟地址空间的400000h处。字眼"优先"表示若该地址区域已被其他模块占用,那PE装载器会选用其他空闲地址。
SectionAlignment 内存中节对齐的粒度。例如,如果该值是4096 (1000h),那么每节的起始地址必须是4096的倍数。若第一节从401000h开始且大小是10个字节,则下一节必定从402000h开始,即使401000h和402000h之间还有很多空间没被使用。
FileAlignment

文件中节对齐的粒度。例如,如果该值是(200h),,那么每节的起始地址必须是512的倍数。若第一节从文件偏移量200h开始且大小是10个字节,则下一节必定位于偏移量400h: 即使偏移量512和1024之间还有很多空间没被使用/定义。

MajorSubsystemVersion
MinorSubsystemVersion
win32子系统版本。若PE文件是专门为Win32设计的,该子系统版本必定是4.0否则对话框不会有3维立体感。
SizeOfImage 内存中整个PE映像体的尺寸。它是所有头和节经过节对齐处理后的大小。
SizeOfHeaders 所有头+节表的大小,也就等于文件尺寸减去文件中所有节的尺寸。可以以此值作为PE文件第一节的文件偏移量。
Subsystem NT用来识别PE文件属于哪个子系统。 对于大多数Win32程序,只有两类值: Windows GUI 和 Windows CUI (控制台)。
DataDirectory IMAGE_DATA_DIRECTORY 结构数组。每个结构给出一个重要数据结构的RVA,比如引入地址表等。

  摘要:PE 的意思就是 Portable Executable(可移植的执行体)。它是 Win32环境自身所带的执行体文件格式。它的一些特性继承自 Unix的 Coff (common object file format)文件格式。

  标签:加密 解密 crack 破解

  PE教程5: Section Table(节表)

  理论:

  到本课为止,我们已经学了许多关于 DOS header 和 PE header 的知识。接下来就该轮到 section table(节表)了。节表其实就是紧挨着 PE header 的一结构数组。该数组成员的数目由 file header (IMAGE_FILE_HEADER) 结构中 NumberOfSections域的域值来决定。节表结构又命名为 IMAGE_SECTION_HEADER

  IMAGE_SIZEOF_SHORT_NAME equ 8

  IMAGE_SECTION_HEADER STRUCT

  Name1 db IMAGE_SIZEOF_SHORT_NAME dup(?)

  union Misc

  PhysicalAddress dd ?

  VirtualSize dd ?

  ends

  VirtualAddress dd ?

  SizeOfRawData dd ?

  PointerToRawData dd ?

  PointerToRelocations dd ?

  PointerToLinenumbers dd ?

  NumberOfRelocations dw ?

  NumberOfLinenumbers dw ?

  Characteristics dd ?

  IMAGE_SECTION_HEADER ENDS

  同样,不是所有成员都是很有用的,我们只关心那些真正重要的。

  

Field Meanings
Name1 事实上本域的名称是"name",只是"name"已被MASM用作关键字,所以我们只能用"Name1"代替。这儿的节名长不超过8字节。记住节名仅仅是个标记而已,我们选择任何名字甚至空着也行,注意这里不用null结束。命名不是一个ASCIIZ字符串,所以不用null结尾。
VirtualAddress 本节的RVA(相对虚拟地址)。PE装载器将节映射至内存时会读取本值,因此如果域值是1000h,而PE文件装在地址400000h处,那么本节就被载到401000h
SizeOfRawData 经过文件对齐处理后节尺寸,PE装载器提取本域值了解需映射入内存的节字节数。(译者注: 假设一个文件的文件对齐尺寸是0x200,如果前面的 VirtualSize域指示本节长度是0x388字节,则本域值为0x400,表示本节是0x400字节长)。
PointerToRawData 这是节基于文件的偏移量,PE装载器通过本域值找到节数据在文件中的位置。
Characteristics 包含标记以指示节属性,比如节是否含有可执行代码、初始化数据、未初始数据,是否可写、可读等。

  现在我们已知晓 IMAGE_SECTION_HEADER结构,再来模拟一下 PE装载器的工作吧:

  读取 IMAGE_FILE_HEADERNumberOfSections域,知道文件的节数目。

  SizeOfHeaders域值作为节表的文件偏移量,并以此定位节表。

  遍历整个结构数组检查各成员值。

  对于每个结构,我们读取PointerToRawData域值并定位到该文件偏移量。然后再读取SizeOfRawData域值来决定映射内存的字节数。将VirtualAddress域值加上ImageBase域值等于节起始的虚拟地址。然后就准备把节映射进内存,并根据Characteristics域值设置属性。

  遍历整个数组,直至所有节都已处理完毕。

  注意我们并没有使用节名: 这其实并不重要。

  示例:

  本例程打开一PE文件遍历其节表,并在列表框控件显示各节的信息。

  .386

  .model flat,stdcall

  option casemap:none

  include \masm32\include\windows.inc

  include \masm32\include\kernel32.inc

  include \masm32\include\comdlg32.inc

  include \masm32\include\user32.inc

  include \masm32\include\comctl32.inc

  includelib \masm32\lib\comctl32.lib

  includelib \masm32\lib\user32.lib

  includelib \masm32\lib\kernel32.lib

  includelib \masm32\lib\comdlg32.lib

  IDD_SECTIONTABLE equ 104

  IDC_SECTIONLIST equ 1001

  SEH struct

  PrevLink dd ? ;the address of the previous seh structure

  CurrentHandler dd ? ;the address of the new exception handler

  SafeOffset dd ? ;The offset where it's safe to continue execution

  PrevEsp dd ? ;the old value in esp

  PrevEbp dd ? ;The old value in ebp

  SEH ends

  .data

  AppName db "PE tutorial no.5",0

  ofn OPENFILENAME <>

  FilterString db "Executable Files (*.exe, *.dll)",0,"*.exe;*.dll",0

  db "All Files",0,"*.*",0,0

  FileOpenError db "Cannot open the file for reading",0

  FileOpenMappingError db "Cannot open the file for memory mapping",0

  FileMappingError db "Cannot map the file into memory",0

  FileInValidPE db "This file is not a valid PE",0

  template db "%08lx",0

  SectionName db "Section",0

  VirtualSize db "V.Size",0

  VirtualAddress db "V.Address",0

  SizeOfRawData db "Raw Size",0

  RawOffset db "Raw Offset",0

  Characteristics db "Characteristics",0

  .data?

  hInstance dd ?

  buffer db 512 dup(?)

  hFile dd ?

  hMapping dd ?

  pMapping dd ?

  ValidPE dd ?

  NumberOfSections dd ?

  .code

  start proc

  LOCAL seh:SEH

  invoke GetModuleHandle,NULL

  mov hInstance,eax

  mov ofn.lStructSize,SIZEOF ofn

  mov ofn.lpstrFilter, OFFSET FilterString

  mov ofn.lpstrFile, OFFSET buffer

  mov ofn.nMaxFile,512

  mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY

  invoke GetOpenFileName, ADDR ofn

  .if eax==TRUE

  invoke CreateFile, addr buffer, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL

  .if eax!=INVALID_HANDLE_VALUE

  mov hFile, eax

  invoke CreateFileMapping, hFile, NULL, PAGE_READONLY,0,0,0

  .if eax!=NULL

  mov hMapping, eax

  invoke MapViewOfFile,hMapping,FILE_MAP_READ,0,0,0

  .if eax!=NULL

  mov pMapping,eax

  assume fs:nothing

  push fs:[0]

  pop seh.PrevLink

  mov seh.CurrentHandler,offset SEHHandler

  mov seh.SafeOffset,offset FinalExit

  lea eax,seh

  mov fs:[0], eax

  mov seh.PrevEsp,esp

  mov seh.PrevEbp,ebp

  mov edi, pMapping

  assume edi:ptr IMAGE_DOS_HEADER

  .if [edi].e_magic==IMAGE_DOS_SIGNATURE

  add edi, [edi].e_lfanew

  assume edi:ptr IMAGE_NT_HEADERS

  .if [edi].Signature==IMAGE_NT_SIGNATURE

  mov ValidPE, TRUE

  .else

  mov ValidPE, FALSE

  .endif

  .else

  mov ValidPE,FALSE

  .endif

  FinalExit:

  push seh.PrevLink

  pop fs:[0]

  .if ValidPE==TRUE

  call ShowSectionInfo

  .else

  invoke MessageBox, 0, addr FileInValidPE, addr AppName, MB_OK+MB_ICONINFORMATION

  .endif

  invoke UnmapViewOfFile, pMapping

  .else

  invoke MessageBox, 0, addr FileMappingError, addr AppName, MB_OK+MB_ICONERROR

  .endif

  invoke CloseHandle,hMapping

  .else

  invoke MessageBox, 0, addr FileOpenMappingError, addr AppName, MB_OK+MB_ICONERROR

  .endif

  invoke CloseHandle, hFile

  .else

  invoke MessageBox, 0, addr FileOpenError, addr AppName, MB_OK+MB_ICONERROR

  .endif

  .endif

  invoke ExitProcess, 0

  invoke InitCommonControls

  start endp

  SEHHandler proc uses edx pExcept:DWORD,pFrame:DWORD,pContext:DWORD,pDispatch:DWORD

  mov edx,pFrame

  assume edx:ptr SEH

  mov eax,pContext

  assume eax:ptr CONTEXT

  push [edx].SafeOffset

  pop [eax].regEip

  push [edx].PrevEsp

  pop [eax].regEsp

  push [edx].PrevEbp

  pop [eax].regEbp

  mov ValidPE, FALSE

  mov eax,ExceptionContinueExecution

  ret

  SEHHandler endp

  DlgProc proc uses edi esi hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD

  LOCAL lvc:LV_COLUMN

  LOCAL lvi:LV_ITEM

  .if uMsg==WM_INITDIALOG

  mov esi, lParam

  mov lvc.imask,LVCF_FMT or LVCF_TEXT or LVCF_WIDTH or LVCF_SUBITEM

  mov lvc.fmt,LVCFMT_LEFT

  mov lvc.lx,80

  mov lvc.iSubItem,0

  mov lvc.pszText,offset SectionName

  invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,0,addr lvc inc lvc.iSubItem

  mov lvc.fmt,LVCFMT_RIGHT

  mov lvc.pszText,offset VirtualSize

  invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,1,addr lvc

  inc lvc.iSubItem

  mov lvc.pszText,offset VirtualAddress

  invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,2,addr lvc

  inc lvc.iSubItem

  mov lvc.pszText,offset SizeOfRawData

  invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,3,addr lvc

  inc lvc.iSubItem

  mov lvc.pszText,offset RawOffset

  invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,4,addr lvc

  inc lvc.iSubItem

  mov lvc.pszText,offset Characteristics

  invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTCOLUMN,5,addr lvc

  mov ax, NumberOfSections

  movzx eax,ax

  mov edi,eax

  mov lvi.imask,LVIF_TEXT

  mov lvi.iItem,0

  assume esi:ptr IMAGE_SECTION_HEADER

  .while edi>0

  mov lvi.iSubItem,0

  invoke RtlZeroMemory,addr buffer,9

  invoke lstrcpyn,addr buffer,addr [esi].Name1,8

  lea eax,buffer

  mov lvi.pszText,eax

  invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTITEM,0,addr lvi

  invoke wsprintf,addr buffer,addr template,[esi].Misc.VirtualSize

  lea eax,buffer

  mov lvi.pszText,eax

  inc lvi.iSubItem

  invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_SETITEM,0,addr lvi

  invoke wsprintf,addr buffer,addr template,[esi].VirtualAddress

  lea eax,buffer

  mov lvi.pszText,eax

  inc lvi.iSubItem

  invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_SETITEM,0,addr lvi

  invoke wsprintf,addr buffer,addr template,[esi].SizeOfRawData

  lea eax,buffer

  mov lvi.pszText,eax

  inc lvi.iSubItem

  invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_SETITEM,0,addr lvi

  invoke wsprintf,addr buffer,addr template,[esi].PointerToRawData

  lea eax,buffer

  mov lvi.pszText,eax

  inc lvi.iSubItem

  invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_SETITEM,0,addr lvi

  invoke wsprintf,addr buffer,addr template,[esi].Characteristics

  lea eax,buffer

  mov lvi.pszText,eax

  inc lvi.iSubItem

  invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_SETITEM,0,addr lvi

  inc lvi.iItem

  dec edi

  add esi, sizeof IMAGE_SECTION_HEADER

  .endw

  .elseif

  uMsg==WM_CLOSE

  invoke EndDialog,hDlg,NULL

  .else

  mov eax,FALSE

  ret

  .endif

  mov eax,TRUE

  ret

  DlgProc endp

  ShowSectionInfo proc uses edi

  mov edi, pMapping

  assume edi:ptr IMAGE_DOS_HEADER

  add edi, [edi].e_lfanew

  assume edi:ptr IMAGE_NT_HEADERS

  mov ax,[edi].FileHeader.NumberOfSections

  movzx eax,ax

  mov NumberOfSections,eax

  add edi,sizeof IMAGE_NT_HEADERS

  invoke DialogBoxParam, hInstance, IDD_SECTIONTABLE,NULL, addr DlgProc, edi

  ret

  ShowSectionInfo endp

  end start

  分析:

  本例重用了PE教程2的代码,校验PE文件的有效性后,继续调用函数ShowSectionInfo显示各节信息。

  ShowSectionInfo proc uses edi

  mov edi, pMapping

  assume edi:ptr IMAGE_DOS_HEADER

  add edi, [edi].e_lfanew

  assume edi:ptr IMAGE_NT_HEADERS

  我们将edi用作指向PE文件数据的指针。首先,将指向DOS header地址的pMapping赋给edi,再加上e_lfanew域值等于PE header的地址。

  mov ax,[edi].FileHeader.NumberOfSections

  mov NumberOfSections,ax

  因为我们要遍历节表,所以必须先获取文件的节数目。这就得靠file header里的NumberOfSections域了,切记这是个word域。

  add edi,sizeof IMAGE_NT_HEADERS

  现在edi正指向PE header的起始地址,加上PE header结构大小后恰好指向节表了。

  invoke DialogBoxParam, hInstance, IDD_SECTIONTABLE,NULL, addr DlgProc, edi

  调用 DialogBoxParam显示列表对话框,注意我们已将节表地址作为最后一个参数传递过去了,该值可从WM_INITDIALOG消息的lParam参数中提取。

  在对话框过程里我们响应WM_INITDIALOG消息,将lParam值 (节表地址)存入esi,节数目赋给edi并设置列表控件。万事俱备后,进入循环将各节信息插入到列表控件中,这部分相当简单。

  .while edi>0

  mov lvi.iSubItem,0

  字符串置入第一列。

  invoke RtlZeroMemory,addr buffer,9

  invoke lstrcpyn,addr buffer,addr [esi].Name1,8

  lea eax,buffer

  mov lvi.pszText,eax

  要显示节名,当然要将其转换为ASCIIZ字符串先。

  invoke SendDlgItemMessage,hDlg,IDC_SECTIONLIST,LVM_INSERTITEM,0,addr lvi

  然后显示第一列。

  继续我们伟大的工程,显示完本节中最后一个欲呈现的值后,立马下一个结构。

  dec edi

  add esi, sizeof IMAGE_SECTION_HEADER

  .endw

  每处理完一节就递减edi,然后将esi加上IMAGE_SECTION_HEADER结构大小,使其指向下一个IMAGE_SECTION_HEADER结构。

  遍历节表的步骤:

  PE文件有效性校验。

  定位到 PE header 的起始地址。

  从 file header 的NumberOfSections域获取节数。

  通过两种方法定位节表: ImageBase+SizeOfHeaders或者 PE header的起始地址+ PE header结构大小。 (节表紧随 PE header)。如果不是使用文件映射的方法,可以用SetFilePointer直接将文件指针定位到节表。节表的文件偏移量存放在 SizeOfHeaders域里。(SizeOfHeadersIMAGE_OPTIONAL_HEADER 的结构成员)

  处理每个 IMAGE_SECTION_HEADER结构。

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章