捷径并不是把弯路改直了,而是帮你把岔道堵上!
走得弯路跟成长的速度是成正比的!不要害怕走上弯路,弯路会让你懂得更多,最终还是会在终点交汇!
岔路会将你引入万劫不复的深渊,并越走越深……
在开始讲解输入表(导入表)概念之前,请允许小甲鱼童鞋用简短的几句话来总结之前我们学过的内容,并做进一步的思想综合提升,注意咯!
首先,我们知道PE 文件中的数据被载入内存后根据不同页面属性被划分成很多区块(节),并有区块表(节表)的数据来描述这些区块。这里我们需要注意的问题是:一个区块中的数据仅仅只是由于属性相同而放在一起,并不一定是同一种用途的内容。例如接着要讲的输入表、输出表等就有可能和只读常量一起被放在同一个区块中,因为他们的属性都是可读不可写的。
其次,由于不同用途的数据有可能被放入同一个区块中,因此仅仅依靠区块表是无法确定和定位的。那要怎么办?对了,PE 文件头中 IMAGE_OPTIONAL_DEADER32 结构的数据目录表来指出他们的位置,我们可以由数据目录表来定位的数据包括输入表、输出表、资源、重定位表和TLS等15 种数据。(数据目录表,不要以为他在前边出现就不重要哦~)
这节课我们谈的是输入表,为什么需要输入表呢?因为我们从数据目录表得到的仅仅是一些指定数据的RVA 和数据块的尺寸,很明显,不同的数据块中的数据组织方式(结构)是显然不同的,例如输入表和资源数据块中的数据就完全是牛马不相及的两个东西。因此,我们想要深入了解PE 文件就必须了解这些数据的组织方式,以及了解系统是如何处理调用它们的。
输入函数
在代码分析或编程中经常遇到“输入函数(Import Functions,也称导入函数)”的概念。这里我们就来解释下,输入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于相关的DLL 文件中,在调用者程序中只保留相关的函数信息(如函数名、DLL 文件名等)就可以。对于磁盘上的PE 文件来说,它无法得知这些输入函数在内存中的地址,只有当PE 文件被装入内存后,Windows 加载器才将相关DLL 装入,并将调用输入函数的指令和函数实际所处的地址联系起来。这就是“动态链接”的概念。动态链接是通过PE 文件中定义的“输入表”来完成的,输入表中保存的正是函数名和其驻留的DLL 名等。
实例预演(视频中将演示,这里只能截图)
之所以是预演,主要是因为咱还没对输入表进行解剖前就来给大家演示一下尝尝鲜找找滋味~不然学习总是无趣的和郁闷的~(参与演示的例子程序和工具都可以到 找到对应的帖子下载,也可以看咱视频演示: )
以上是咱这次实验的小青蛙哈~灰常简单的一个小程序,如图双击程序只显示一个对话窗口,然后就结束~试验用小程序,我们尽量的将内部的结构删减,调试起来才方便些。我们这次体验的目的就是想靠所学的知识,试图来找到MessageBox 在内存中的地址。
注:MessageBox 是来自于USER32.DLL 动态链接库里的一个函数,我们通过对PE 文件的静态反编译分析来观察hello.exe 这个试验品是如何定位和调用MessageBox 这个在“异乡”的函数哈。
(MessageBox 有两个版本,一个是MessageBoxA 还有一个是MessageBoxW 分别带便ASCII码形式和UNICODE~历史故事了~)
体验开始:
1. 我们用曾经号称为屠龙刀的W32DAM 对hello.exe进行反编译,如图:
我们可以看到这个程序只有两个导入模块(Import Module),分别导入来自两个动态链接库(USER32.DLL和KERNEL32.DLL)的若干函数,我们还清晰可见,咱要跟踪的MessageBoxA 就在USER32.DLL 中,这里程序还自动给我们定位了它的虚拟地址:2A2DC,但我们不要用这个,因为我们说过这回我们是来探险的,凡事讲究人工……
我们通过W32DASM 的查找功能找出MessageBox 这个函数代码的位置,并试图查看他的汇编跳转~
为啥,这要从汇编调用子程序说起啦~简单的说就是汇编语言要调用子程序会运用到CALL 和 RET这样的搭配来进行,学过咱《零基础入门学习汇编语言》的童鞋还记得小甲鱼当时的动画片演示吧?!上图……
各位看官看到了吧?push xxxx push xxxx push xxxx push xxxx 之后再来一个Call xxxx ……
没错,这就是调用函数的一个标准形式。每个push 其实就是将所要调用的函数需要的参数入栈,为啥要入栈呢?这又要从地球的起源说起了……我这里就简单的说下吧:栈的发明,使得函数和子程序的出现成为可能!对于局部变量和参数,栈的特性最适合不过~不明白不急哈,这个是汇编语言和编译原理的范畴了,以后咱学习深入自然会碰到,到时候再来详细解决就可以。
int MessageBox( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType // style of message box );
由定义可见MessageBox 函数共有四个参数,因此我们四次push xxxx 分别将参数按照STDCALL 的方式入栈之后,就可以CALL MessageBox 这个函数了。好,既然是CALL 我们的目标函数了,通过反汇编我们就可以观察到它的地址是:[0042A2AC],难道就这么简单?42A2AC 就是目标函数的地址?
那我们就直接把程序往下拉,试图找找这个 42A2AC 的地址吧~
可是…… ……
我们悲剧的发觉,程序压根还没到 42A2AC 这地方,到了421FF8 就结束了!!什么情况呢?
或许……或许……或许……在没详细讲输入表时我们还不能直接给大家答案……
好吧,小甲鱼这里提问一下,这是一个什么地址呢?偏移地址 or 虚拟地址?
恩,没错,这是一个VA 哈,那这个VA 上节课我们说过可以换算成存放在实际物理内存上的方法。具体就是将这个VA 与该程序的各个区块的VA 地址逐一对比,由于PE 头文件有记载每个区块的VA 地址也同时记载着它的实际物理地址,因此我们可以通过判断该VA 位于哪个区块内并求出与区块VA 的差值进而求出该VA 的实际物理地址。那既然前边直直的路子走不通,我们就试下把他转换为物理地址试试吧?!
好吧,我知道我说话又绕口令了,咱就实例演示下,不然大家要喷了……上图……
我们看到,咱的42A2AC 地址加载咱的2A000 和 2B000 之间(改程序VC编写,映像基地址为400000打头哦~),因此我们可以将该地址定位到改程序位于.idata 区块内。该区块的VA 起始地址为42A000,因此42A2AC - 42A000 = 2AC,raw data offs 跟我们说该区块的物理地址是28000,因此42A2AC 这个VA 所对应的物理偏移地址就是 28000 + 2AC = 282AC。
咱用UE 打开看下282AC 这偏移地址上有啥东西……
282AC 这个地址上存放着 DCA20200 这个数据,翻译成ASCII 码也是莫名其妙的说~
但我们把DCA20200 当成一个DWORD 类型的数据来读的话我们得到数据 0002A2DC(还记得大端与小端吧)
慢着,是不是很熟悉,又是2A****开头,跟咱之前的地址是不是差不多?那好,我们又按照刚才的方法转化为偏移地址试试,转化后得到的偏移地址是:282DC,咱再看看282DC 里边有啥神秘的东西吧?
哈哈,看到奇迹了吗?从282DC 地址读起,ASCII 码对应的值是MessageBoxA.USER32.dll
怎么样,有点神秘的成就感吧~
但根本问题我们还是没能解决:MessageBox 的地址是?其实在这里我们还不能帮大家解决这个问题,因为我们缺乏对输入表的深入理解,所以我们期待吧,下节课将揭开输入表的神秘面纱……