PE文件与脱壳初探

一、PE文件的载入机制:

PE文件并不是作为单一映射文件被载入,它先被Windows加载器(PE装载器)遍历,决定哪个部分要被映射,(映射是高偏移地址对应高内存地址)。
然后PE文件被载入内存,数据结构布局与原始的一致之外,数据间的相对位置不一定一致(如下图)。所以某一部分的载入偏移地址不一定等于原始偏移地址。被载入到内存的部分统称模块,那么模块句柄其实就是映射文件(等同于 PE文件 的虚拟空间 )的起始地址(但 Windows CE 除外),它还有另一个名字叫基地址,我们在Windows编程时用到的API函数GetModuleHandle就是用来获取模块句柄,即基地址 用的。

PE文件的基地址由文件自身决定,按照默认设置,VC++编译链接生成的EXE文件的基地址是400000h,DLL文件则是10000000h

在这里插入图片描述

(一)相对虚拟地址RVA:

相对虚拟地址是PE文件在 内存中的相对于PE文件载入地址(基地址)的偏移地址,它被采用的意义就在于,载入的指针可以在内存中的任意位置,可以很好地确定某一部分的具体位置。

相 对 虚 拟 地 址 = 目 标 地 址 ( 虚 拟 地 址 ) − 载 入 地 址 ( 基 地 址 ) 相对虚拟地址 = 目标地址(虚拟地址) - 载入地址(基地址) =

(二)文件偏移地址(物理地址):

文件偏移地址是PE文件在磁盘中相对于文件头的偏移地址,
其实我们用winhex等16进制编辑器打开一个PE文件后,在左边栏呈现的就是文件偏移地址,即物理地址
在这里插入图片描述

二、PE文件结构简述

(一)MS-DOS头部

DOS MZ(MZ头)和紧随其后的DOS stub(DOS块)组成

  • MZ头告诉DOS这是一个有效执行体【就把他当作文件头就好】
  • DOS块由编译器自动生成

这个结构中有用的字段主要是e_magice_lfanew

  • e_magicMZ的ASCII编码,可执行文件都以其开头。
  • e_lfanew:记录真正PE头的RVA。

在这里插入图片描述

(二)PE文件头

Windows加载器会读取e_lfanew字段,进而算出PE文件头的指针,PE文件头又可以被划分成3个部分,分别是SignatureIMAGE_FILE_HEADER(映像文件头)IMAGE_OPTIONAL_HEADER(可选文件头)

  • Signature:"PE\0\0"的ASCII编码
  • 映像文件头:记录一些PE文件的基本信息,里面的一个字段指出可选映像头的大小。具体参考 《加密与解密(第四版)》P.409
  • 可选映像头:更详尽地描述了PE文件的基本信息,一些有用的信息可以通过LordPE等PE编辑器直观地看出来。在这里详细说说位于可选映像头的一个重要字段DataDirectory[16],其他的具体参考 《加密与解密(第四版)》P.409

——DataDirectory[16](数据目录表):

这16个元素的结构都是IMAGE_DATA_DIRECTORY(记录偏移地址和占用空间大小),指向输入表、输出表、资源块等数据,用于定位输入输出表等资源。
打开LordPE,进入目录界面可以直观的看到数据目录表的信息。
在这里插入图片描述

(三)区块表与区块

  • 区块表:紧接着可选映像头就是区块表,是一个IMAGE_SECTION_HEADER结构数组,一个IMAGE_SECTION_HEADER结构对应一个区块,每个这样的结构记录的是对应区块的基本信息。而这个数组有多少个取决于PE文件头映像文件头NumberOfSections字段的数值。

我们用LordPE编辑一个PE文件的时候,进入区段窗口可以清晰的看到各个区块即它们的一些重要信息
在这里插入图片描述
关于块属性字段Characteristic,它的值是由下表中的值相或得到:

地址用途
00000020h包含代码,常与10000000h一起设置
00000040h该块包含已经初始化的数据
00000080h该块包含未初始化的数据
02000000h该块可丢弃,一旦被载入,进程便不再需要它,例如重定位块.reloc
10000000h该块为共享块
20000000h该块可执行,通常00000020h被设置时它也被设置
40000000h该块可读,可执行文件中的块总是设置该标志
80000000h该块可写,如果PE文件中没有设置该标志,装载程序就将内存映像页标记为可读或者可执行
  • 区块:一个PE文件至少有两个区块(代码块数据块)组成,在映像中的排列顺序按照起始地址排列而非字母表,不额外自定义区块名的情况下,链接器给这些区块的命名是由链接器本身决定的(微软的链接器和Borland的链接器设置的名称不同)。常见区块命名详见《加密与解密(第四版)》P.417

——区块合并

从源代码到可执行文件的过程中,一些区块在OBJ文件时就已经被放置了,可能还有特殊的用于给链接器传递消息的区块,而链接器做的就是按照一定规则合并OBJ和区块,这样做可以节省磁盘与内存空间
(要是合并的过程中要合并的区块有一个是只读属性,那么系统临时将其设置为可读可写,再进行合并操作,初始化之后恢复)

——区块对齐与地址转换运算

PE文件头可选映像头中的FIleAlignment字段定义了磁盘区块的对齐值,SectionAligment字段定义了内存区块的对齐值,

  • 磁盘中,每一个区块以磁盘区块对齐值的整数倍作为偏移地址,不足的地方(区块间隙)用00h填充,
  • 内存中,区块至少从一个页边界处开始
    在这里插入图片描述

当区块在内存中的偏移跟文件中的偏移一致时可以提高载入速度,但会使可执行文件变大,这么做取决于文件是否足够小。
结合下面的磁盘到内存的映射图我们发现,MS-DOS到块表的部分无论是在磁盘中还是在内存中,它们的偏移都是一致的,不一致的是其后的块的偏移,由于区块对齐,对于不同块来说,各个块在内存中的偏移与磁盘中的偏移的差值是不一定相同,但在同一个区块中磁盘与内存的对应地址,这个差值又是相同的,不难得出下面的公式:
F i l e O f f s e t = RVA − Δ k \textcolor{red}{{\mathnormal{FileOffset}} = \text{RVA} -\varDelta{k}} FileOffset=RVAΔk
F i l e O f f s e t = RVA − I m a g e B a s e − Δ k \textcolor{red}{\mathnormal{FileOffset} = \text{RVA} - {ImageBase} - \varDelta{k}} FileOffset=RVAImageBaseΔk
在这里插入图片描述

三、输入表(.idata区块)

(一)输入函数的调用

PE文件载入内存前,要用到的输入函数的基本信息已经存在于PE文件中,但Windows加载器在PE文件载入内存之后才将相关DLL载入内存,并将调用输入函数的指令与输入函数的实际地址关联,同时输入地址表IAT)中也被写入了输入函数的地址。

由于使用来自其他DLL的代码和数据的过程叫输入,所以输入函数外部函数

有的时候程序本可以直接用下面汇编语句高效调用API

call DWORD PTR [某API的地址]

但因为编译器分辨不出输入函数的调用和普通函数的调用而一视同仁使用下面的低效调用方式。

	call 地址1                    ;子程序
	  …… ……
地址1:
	jmp dword ptr [某API的地址]

我们只需要在输入函数的申明前面加上_declspec(dllimport)即可解决这个问题

(二)输入表的结构 与 IAT/INT

  • IAT:输入地址表
  • INT:输入名称表

输入表以一个IMAGE_IMPORT_DIRECTORY(IID)结构数组开始,一个IID对应一个DLL等等,数组的最后一个元素是一个内容全0的IID作为该数组结束的标志。

IID中有两个很重要的字段OriginalFirstThunkFirstThunk,分别指向输入名称表INT输入地址表IAT的虚拟偏移地址,

IAT和INT本质上是一个IMAGE_THUNK_DATA数组,数组中的一个元素即一个IMAGE_THUNK_DATA(双字)对应一个输入函数,而IMAGE_THUNK_DATA本质上是指针,在不同的时刻有不同含义

  • IMAGE_THUNK_DATA的最高位为1:函数以序号方式输入,此时低31位代表被输入API的序数值。
  • IMAGE_THUNK_DATA的最高位为0:函数以字符串类型的函数名方式输入,此时双字指向一个IMAGE_IMPORT_BY_NAME结构(单字)

IMAGE_IMPORT_BY_NAME结构存储一个输入函数的相关信息如下:

  • hint:占一个字,输入函数在外部DLL输出表中的序号
  • name:所占空间可变,输入函数的函数名称

在PE文件加载到内存前,所有IMAGE_THUNK_DATA结构都指向IMAGE_IMPORT_BY_NAME结构
IAT和INT都是以一个内容全为0的IMAGE_THUNK_DATA结构作为结束标志。
在这里插入图片描述
也就是说,在载入内存前,PE文件的IAT和INT都是指向IMAGE_IMPORT_BY_NAME结构的,但在载入内存之后,Windows加载器通过INT找到所有输入函数的地址,然后用这些地址去替代IAT中指向IMAGE_IMPORT_BY_NAME结构的地址,此时IAT存入了输入函数的地址,输入表中别的部分已经不再重要。
在这里插入图片描述

(三)实例分析

下面我们以一个PE文件为例用010 EditorLordPE分别从载入内存前载入内存后分析它的输入表

载入前的输入表实例分析

我们知道,可选映像头中数据目录字段的第二个元素记录的就是输入表的RVA数据目录表相对于PE头的偏移是80h,在010 Editor中可以很容易定位得到输入表的RVA的值为13A1EC
在这里插入图片描述
LordPE中也可以一目了然得到。
在这里插入图片描述
目录表界面下输入表旁点击H按钮即可立即查看输入表的内容(黑色底纹处)
在这里插入图片描述

要想在010 Editor中定位输入表的位置需要知道的是物理地址,但物理地址并不等于相对虚拟地址(RVA),所以就要用到物理地址跟虚拟地址的转换。
可以直接用LordPE内置的文件位置计算器就可以得到文件偏移地址
在这里插入图片描述
在这里插入图片描述
提取出来如下,5个双字为一个IID

OriginalFirstThunkTimeDateStampForwardName(指向了DLL的名称)First Thunk
E4A3 13000000 00000000 000022A4 1300BCA1 1300
28A2 13000000 00000000 00004EAA 130000A0 1300
0000 00000000 00000000 00000000 00000000 0000

以第一个IID为例,Name是指向DLL名称的指针,因为高位对应高地址,所以22A4 1300倒置过来就是 RVA = 0013 A 422 \textcolor{red}{\text{RVA} = 0013A422} RVA=0013A422,位置计算器计算出物理地址E0A22
在这里插入图片描述
010 Editor中定位,可以发现第一个DLL对应USER32.dll
在这里插入图片描述
之前探讨过,加载到内存之前,IAT和INT结构相同,数值一致。OriginalFirstThunk指向INT,第一个IIDOriginalFirstThunk0013A3E4,物理地址对应0E09E4
在这里插入图片描述
定位到0E09E4
在这里插入图片描述
再看看第一个IID中指向IAT的First Thunk字段, RVA = 0013 A 1 B C \textcolor{red}{\text{RVA} = 0013A1BC} RVA=0013A1BC,对应物理地址0E07BC
在这里插入图片描述
定位到0E07BC,发现IAT与INT此时的值一致,都是0013A414
在这里插入图片描述
最高位为0,说明以函数名的方式输入,包含了函数名名称的IMAGE_IMPORT_BY_NAME结构的RVA为0013A414,物理地址是000E0A14
在这里插入图片描述
得到物理地址后010 Editor中定位,可以发现这个IMAGE_IMPORT_BY_NAME结构对应的输入函数是MessageBoxA函数
在这里插入图片描述

载入后的输入表实例分析

我们把程序运行过程中内存中的数据dump下来,然后再分析输入表,这里可以写个程序dump,也可以直接用OD插件,我选择的是用OD插件的方式

先用LordPE找到输入表的RVA为0013A1EC,由于是dump出来的程序,此时的RVA已经是等于物理地址了,不需要再转换。
在这里插入图片描述
010 Editor中定位,与载入内存前的输入表一致,
在这里插入图片描述
现在看看第一个IID的OriginalFirstThunk字段指向的INT,物理地址即RVA 为0013A3E4,数据相比载入内存前没有改变
在这里插入图片描述
再分析一下FirstThunk指向的IAT,物理地址即RVA 为0013A1BC,数据产生了变化,变成了75A1 ED60,这应该就是USER32.dll链接库MessageBox函数的地址
在这里插入图片描述

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Em0s_Er1t

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值