这里以攻防世界的一道简单的题目来记录一下手动脱壳相关内容,其实理解了原理之后也是非常简单的一件事。
首先看看攻防世界的这道crackme。
查壳
PEiD查一查,发现有壳,nSpack,并不是常见的upx,也懒得去找工具,这样一道简单的题目肯定不会用很复杂的壳,所以开始徒手脱壳。
找OEP
手动脱壳的第一步,就是要找到程序的OEP,就是真正的程序入口点。这个要从壳的原理说起,所谓的压缩壳、加密壳,作用都是要隐藏程序真正的入口点,在壳加载的过程中对数据进行解压和解密并放到相应的段,然后壳的作用就结束了,这个时候会从壳进入真正的程序,这个时候会有一个真正的程序入口,就是OEP,不管脱什么壳,首先都是要找到OEP。
首先OD打开调试(不管用什么调试都差不多,IDA也是可以的,并且也有相应的Dump脚本,过程上没什么太大的差别)
首先看到pushfd
和pushad
两条命令,这是记录所有寄存器,这里我根据esp定律,实际上也就是堆栈平衡的原理,在执行完pushad
之后对esp
下断点
下面简单介绍一下esp定律:
一般的加壳软件在执行时,首先要初始化,保存环境(保存各个寄存器的值),一般利用
pushad
(相当于把eax,ecx,edx,ebx,esp,ebp,esi,edi
都压栈),当加壳程序的外壳执行完毕以后,再来恢复各个寄存器的内容,通常会用POPAD(相当与把刚刚保存的寄存器的值都还原),在脱壳的时候,我们可以根据堆栈平衡来对ESP进行下断,进而快速到达OEP
使用硬件读断点(hw esp
),这里只经过了两次跳转,然后就来到了OEP附近,这里也需要注意,OEP处的esp
并不一定是之前记录的值,但是一定很近了,这个时候需要耐心的寻找疑似的OEP。这里的OEP我找到实在0x401000
处
这里显然就是程序真正的入口。还有一点需要注意,壳加载过程中会加载代码,所以如果遇到没有解析的数据,就Ctrl+A
重新分析代码。
Dump
接下来要把内存中的完整程序Dump出来,好像OD有相关的插件,但我还是喜欢LordPE,使用也很简单,就是注意右键进程,修正镜像大小,因为有人会改镜像的大小,Dump出来的程序会有问题,所以一般不去判断,直接先修正,再Dump
修正IAT
这个时候Dump出来的程序时没法运行的,因为缺少了很关键的一部分内容,就是IAT,也就是输入表,我们可以把现在Dump出来的程序用IDA打开,可以看到printf
和scanf
之类的函数都识别不出来,这就是因为我们Dump出来的程序没有输入表。至于输入表是什么,偷懒从百度百科抄了过来,这是PE结构中很重要的一部分。
Import Address Table 由于导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或者多个DLL 中。当PE 文件被装入内存的时候,Windows 装载器才将DLL 装入,并将调用导入函数的指令和函数实际所处的地址联系起来(动态连接),这操作就需要导入表完成.其中导入地址表就指示函数实际地址。
IAT当然可以自己动手改Dump出来的程序的16进制,但是我还是喜欢用ImportREC(明明有工具为什么还要自己动手……还容易写错)。
使用起来很简单,首先要把我们知道的OEP地址输入进去,注意这里是偏移地址,然后可以选择IAT Autosearch
选项,但有的时候可能会不准确,这个时候可以手动寻找,RVA就是相对虚拟地址,Size就是大小,手动找的过程也很简单,只要找到调用的函数的部分,比如说printf
,就可以在汇编代码里看到调用的地址,比如说dword ptr [402094]
,这是个间接取址,这里取的是402094
处指针指向的地址,这里才是真正的printf
函数的入口,我们转到402094
的位置,可以看到上下都有很多这样的指针,指向外部引用的函数,这里就是输入表
两个不同的dll中间用0隔开,所以找到开始地址和结束地址,就能知道RVA和Size。
这个时候点Get Imports
,中间窗口会显示出来,但是注意有时候会出现无效的值,这个时候可能需要手动修复或者是删除,如果全都显示有效,就可以Fix Dump
,选择刚刚Dump出来的文件,成功的话会生成一个新的文件,这个文件是可以执行的程序,到这里脱壳的过程就结束了。
解题
回到题目本身,IDA打开
1 | signed int start() |
发现程序的判断非常简单,可见这题的主要目的是为了考脱壳,把需要的数据导出之后,直接简单逆向输出flag
1 | #include <iostream> |
输出得到flag
1 | flag{59b8ed8f-af22-11e7-bb4a-3cf862d1ee75} |
Comments