# 逆向基础——软件手动脱壳技术入门
这里整合了一下之前自己学习软件手工脱壳的一些笔记和脱文,希望能给新学软件逆向和脱壳的童鞋们一点帮助。
1 一些概念
======
* * *
### 1.1 加壳
加壳的全称应该是可执行程序资源压缩,是保护文件的常用手段。加壳过的程序可以直接运行,但是不能查看源代码。要经过脱壳才可以查看源代码。
加壳是利用特殊的算法,对`EXE`、`DLL`文件里的资源进行压缩、加密。类似`WINZIP`的效果,只不过这个压缩之后的文件,可以独立运行,解压过程完全隐蔽,都在内存中完成。它们附加在原程序上通过`Windows`加载器载入内存后,先于原始程序执行,得到控制权,执行过程中对原始程序进行解密、还原,还原完成后再把控制权交还给原始程序,执行原来的代码部分。加上外壳后,原始程序代码在磁盘文件中一般是以加密后的形式存在的,只在执行时在内存中还原,这样就可以比较有效地防止破解者对程序文件的非法修改,同时也可以防止程序被静态反编译。
壳的类型通常分为压缩壳和加密壳两类。压缩壳的特点是减小软件体积大小,加密保护不是重点。加密壳种类比较多,不同的壳侧重点不同,一些壳单纯保护程序,另一些壳提供额外的功能,如提供注册机制、使用次数、时间限制等。
### 1.2 OEP
OEP:(`Original Entry Point`),程序的入口点。软件加壳一般隐藏了程序真实的`OEP`(或者用了假的`OEP`), 我们需要寻找程序真正的`OEP`,才可以完成脱壳。
一般加壳程序在使用`Ollydbg`等动态调试工具时,会停在壳的预处理块。即处在对于程序原始代码块的解压或解密操作之前,在运行完程序自脱壳模块后,会停留在程序加壳之前的`OEP`位置,此时是`dump`程序的最佳时期。脱壳时在真实`OEP`处下`int3`断点,就可以捕捉到程序代码段完全恢复的状态。因此,寻找加壳程序的正确OEP,也成了手动脱壳时的第一要务。
### 1.3 IAT
IAT:(`Import Address Table`),导入地址表。由于导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或者多个`DLL`中。当`PE`文件被装入内存的时候,`Windows`装载器才将`DLL`装入,并将调用导入函数的指令和函数实际所处的地址联系起来(动态连接),这操作就需要导入表完成。其中导入地址表就指示函数实际地址。 多数加壳软件在运行时会重建导入地址表,因此获取加壳程序正确的导入地址表也是手动脱壳操作中的一个关键问题。
2 一些脱壳方法
========
* * *
### 2.1单步跟踪法
单步跟踪法的原理就是通过`Ollydbg`的单步(`F8`)、单步进入(`F7`)和运行到(`F4`)功能,完整走过程序的自脱壳过程,跳过一些循环恢复代码的片段,并用单步进入确保程序不会略过`OEP`。这样可以在软件自动脱壳模块运行完毕后,到达`OEP`,并`dump`程序。
### 2.2 ESP定律法
`ESP`定律法是脱壳的利器,是应用频率最高的脱壳方法之一。
`ESP`定律的原理在于程序中堆栈平衡的合理利用。由于在程序自解密或者自解压过程中,不少壳会先将当前寄存器内容压栈,如使用`pushad`,在解压结束后,会将之前的寄存器值出栈,如使用`popad`。因此在寄存器出栈时,往往程序代码被自动恢复,此时硬件断点触发。然后在程序当前位置,只需要少许单步跟踪,就很容易到达正确的`OEP`位置。
### 2.3内存镜像法(二次断点法)
内存镜像法是在加壳程序被加载时,通过`OD`的`ALT+M`快捷键,进入到程序虚拟内存区段。然后通过加两次内存一次性断点,到达程序正确`OEP`的位置。
内存镜像法的原理在于对于程序资源段和代码段下断点,一般程序自解压或者自解密时,会首先访问资源段获取所需资源,然后在自动脱壳完成后,转回程序代码段。这时候下内存一次性断点,程序就会停在`OEP`处。
### 2.4一步到达OEP
所谓的一步到达`OEP`的脱壳方法,是根据所脱壳的特征,寻找其距离`OEP`最近的一处汇编指令,然后下`int3`断点,在程序走到`OEP`的时候`dump`程序。 如一些压缩壳往往`popad`指令距离`OEP`或者`Magic Jump`特别近,因此使用`Ollydbg`的搜索功能,可以搜索壳的特征汇编代码,达到一步断点到达`OEP`的效果。
### 2.5最后一次异常法
最后一次异常法的原理是,程序在自解压或自解密过程中,可能会触发无数次的异常。如果能定位到最后一次程序异常的位置,可能就会很接近自动脱壳完成位置。现在最后一次异常法脱壳可以利用`Ollydbg`的异常计数器插件,先记录异常数目,然后重新载入,自动停在最后一次异常处。
### 2.6 模拟跟踪法
模拟跟踪法的原理就是使用`Ollydbg`下条件断点,`SFX`相当于是一个自解压段,在自解压段结束时(`eip`的值转到代码段时),已经距离`OEP`很近,但是这种跟踪方法会比较耗时。
### 2.7 “SFX”法
“`SFX`”法利用了`Ollydbg`自带的`OEP`寻找功能,可以选择直接让程序停在`OD`找到的`OEP`处,此时自解压已经完成,可以直接`dump`程序。
3一些脱壳实践
=======
* * *
下面给出整理的使用以上方法,自己尝试手动脱这几种常用壳的脱壳笔记。
### 3.1UPX脱壳笔记
首先进行侦壳:
![enter image description here](http://drops.javaweb.org/uploads/images/7cbe68a40972baadc6903868032a9766cdc628bb.jpg)
首先把程序扔到`OllyIce`里面可以看到:
![enter image description here](http://drops.javaweb.org/uploads/images/3150832969c3d85add153a1f1c27bc0316b05b9d.jpg)
然后这里尝试使用`ESP`定理:即在`ESP`第一次改变时,对`ESP`的地址设置硬件字访问断点,这样可以在代码被`UPX`算法还原之后,跳转到程序的正常入口处。
![enter image description here](http://drops.javaweb.org/uploads/images/03ae9348fcf592cd192c1e1cac0b7fc64431babb.jpg)
然后`F5`运行,并没有直接到跳转到程序入口处的大跳位置,但是可以看到`UPX`的大跳就在眼前:
![enter image description here](http://drops.javaweb.org/uploads/images/2980719b541553888ba30de53a2a1441230f0255.jpg)
所以被还原后的程序入口点就是`0x00445151`(通过单步往下走,`F4`略过往回走的循环语句,也可以看到这个大跳的位置。)接下来走到大跳位置,跳到正常程序入口处:
![enter image description here](http://drops.javaweb.org/uploads/images/51e253e80a9eb213038ad1ef59043fb17735e425.jpg)
然后去掉硬件断点,并使用`LoadPE`的`dump`功能`dump`目标程序:
![enter image description here](http://drops.javaweb.org/uploads/images/820b4bbcd1a759781000d1f8c76a1692bb6dea42.jpg)
先修正映像大小,然后再选择完整脱壳,这样可以得到第一步dump的程序,然后再使用`ImportREC`修复`dump`程序的`OEP`,`OEP`的信息通过`OD`自带的`dump`功能查询或者直接填`45151`:
![enter image description here](http://drops.javaweb.org/uploads/images/d48db0c962e1b30ffa8522a0f13014617bb1bc58.jpg)
将正确的入口地址填入`ImportREC`中,然后自动搜索`IAT`信息:
![enter image description here](http://drops.javaweb.org/uploads/images/c7009534f8f848eb3b0f69ee3384c65d0490c032.jpg)
然后点击获取输入表得到修正`IAT`之后的程序函数输入表,然后再点击显示无效函数,愉快地发现没有无效函数,那么就可以直接修复转存文件了。
![enter image description here](http://drops.javaweb.org/uploads/images/b6f43d9d06cf98ace94d7a6bd9201c49668c1a12.jpg)
选择刚刚第一步`dump`下来的转储文件进行修复,修复完成之后脱壳完成:
![enter image description here](http://drops.javaweb.org/uploads/images/87014d90f55188a332374e4ebc20c2d1f3747cd2.jpg)
这里对于压缩壳`UPX`,直接使用了`ESP`定律,可以很方便找到`OEP`并`dump`程序。
### 4.2 tElock脱壳笔记
这里脱的是一个`tElock`的壳:
![enter image description here](http://drops.javaweb.org/uploads/images/da81180d2628e9ee8b69b712528eb9a3086fddbb.jpg)
1、先使用最简单的最后一次异常法: 首先把程序扔到`OllyIce`里面设置`OD`调试选项中的异常选项,
![enter image description here](http://drops.javaweb.org/uploads/images/9dec6398cffb64ff3acafd81677611952b3809bc.jpg)
仅保留内存非法访问异常,然后使用异常计数器插件,在使用前要清空断点设置:
![enter image description here](http://drops.javaweb.org/uploads/images/2629446a9257d1bdbb62753c99593d702ab818dc.jpg)
等到程序正常运行后,重新加载程序,再选择第二步,停在最后一次异常之前:
![enter image description here](http://drops.javaweb.org/uploads/images/24e650f743173e762d8a2405ecd10002e4eab78b.jpg)
然后用`Alt+M`转到内存窗口,对主程序`code`段下内存断点,`SHIFT+F9`执行:
![enter image description here](http://drops.javaweb.org/uploads/images/4e7b206f8bf59bdc4a779e49454734448a7643c1.jpg)
这样程序就中断在了正确的`OEP`处,可以选择从模块中删除分析以显示正常分析的汇编代码。然后使用`LoadPE dump`程序,并修正程序映像大小。但是在使用`ImportREC v1.6F Fix`版,输入正确的`OEP`,获取函数输入表信息时,会发现无效的指针。使用方法一修复后,再使用方法三可以完全修复:
![enter image description here](http://drops.javaweb.org/uploads/images/28ce8ed8598ae6e2fd324ae9bc116e2d20aab603.jpg)
再点击`Fix dump`,可以修复之前`dump`下来的程序,脱壳完成:
![enter image description here](http://drops.javaweb.org/uploads/images/6c3d2b38686d8d8081dfc7bbf9f79a652a61bb04.jpg)
2、使用二次内存断点法: 首先载入程序,将所有的异常类型忽略,然后在`idata`段设置内存断点, 然后`SHIFT+F9`:
![enter image description here](http://drops.javaweb.org/uploads/images/21f0bee5f94eee0e72ddf39a62b4427063494c0b.jpg)
![enter image description here](http://drops.javaweb.org/uploads/images/190d1e43eb114bcb79efd44e22d18b2e10aa79e5.jpg)
停下来后再次在code段设置内存断点,再次SHIFT+F9执行,可以直接达到正确的OEP中:
![enter image description here](http://drops.javaweb.org/uploads/images/3d52cd93ee62c0925cb0cc59da8233a359bcdebb.jpg)
然后`LoadPE dump`,然后修复`IAT`。修复方法同方法1。
3、寻找`magic jump`以及修复函数表完成后`dump`程序: 前两步还是加内存断点(`idata`、`code`),然后定位到程序的正确`OEP`处
![enter image description here](http://drops.javaweb.org/uploads/images/9281d7a55b2e966c6929c8c5a1f44d28c2751d33.jpg)
然后如果这时使用`LoadPE dump`后修复,就和前两种一样了。这里先是使用`ImportREC`获取函数输入表第一个位置的指针地址。
![enter image description here](http://drops.javaweb.org/uploads/images/687cd0b1cb733865459f704f7400f068e6ab1872.jpg)
然后得到函数指针偏移地址在`0x005512C`,加上基地址后为`0x045512C`,这时在该位置下硬件访问双字断点。再重新`SHIFT+F9`忽略异常执行后,由于下了断点,会触发`tElock`的`CRC`校验错误:
![enter image description here](http://drops.javaweb.org/uploads/images/a4a946c6c3be5feb3346210b517a37d35e8ccabc.jpg)
所以这里要先绕过`CRC`校验,才能成功执行到硬件断点位置,所以首先暂停程序,然后使用`Alt+F9`返回用户代码。点击确定按钮后,程序暂停在调用`ExitProcess`的位置:
![enter image description here](http://drops.javaweb.org/uploads/images/723cb5a65b0552a712cf1ca6cb36b492edf5494a.jpg)
现在要向上找一找能跳过这个退出的跳转(`CRC`判断跳转),然后进行修改并跳过:
![enter image description here](http://drops.javaweb.org/uploads/images/2231e92bb580b5299d45e0cf645bedd108640b99.jpg)
找到了应该修改的位置,但是如果修改之后重新运行是会被恢复的,所以先记下来这个跳转的地址,`0x00469622`。重新运行之后,在`idata`断设置内存断点,`SHIFT+F9`停下后,再`Ctrl+G`找到修改点再修改。修改完之后再设置之前的硬件断点,这样不会触发`CRC`校验错误了。
无数次的`SHIFT+F9`之后,在寄存器窗口可以看到指针以及能够正常显示:
![enter image description here](http://drops.javaweb.org/uploads/images/d8ef0e53f6e92540dacf3b561001a365b7556129.jpg)
然后此时`F8`单步,找`magic jump`……看小生大大的视屏是通过分析疑似`CRC`跳转得到`magic jump`的位置:
![enter image description here](http://drops.javaweb.org/uploads/images/c2be6e1d46065203d4877ef87dc2b2ab1bd6cea4.jpg)
这里记下来`magic jump`的地址是`0x0046973B`,然后清空`udd`文件,删除硬件断点,再次重新运行程序,然后在`idata`下内存断点停住,然后`Ctrl+G`找到`magic jump`位置处,修改跳转:
![enter image description here](http://drops.javaweb.org/uploads/images/f111e6493732db95cadb67d7541c913dfe57e17d.jpg)
然后在`code`段下内存断点:
![enter image description here](http://drops.javaweb.org/uploads/images/5391c35a5eec79386166958b6886aba6f2ee3107.jpg)
然后`SHIFT+F9`执行,停下来就到了`OEP`的位置:
![enter image description here](http://drops.javaweb.org/uploads/images/79d9813e8b150e71502e532713a6d015e62367e7.jpg)
这时候再`dump`程序,`IAT`表已经被修复,可以直接获得脱壳版程序:
![enter image description here](http://drops.javaweb.org/uploads/images/399416c924b78015e4ea5c57b6d26fa7cca7cea9.jpg)
这里尝试使用了另外两种脱壳方法,并且通过预先找`OEP`的方式,修复了`CRC`校验后,直接`dump`到了`IAT`被修复了的程序。
### 3.3 PEncrypt脱壳笔记
首先进行侦壳:
![enter image description here](http://drops.javaweb.org/uploads/images/e30c9465f172c89b266cf4e4ddb795bae870d6a5.jpg)
先把程序扔到`OllyIce`里面,然后程序停在这里,看起来蛮怪的:
![enter image description here](http://drops.javaweb.org/uploads/images/c09fd88074ddfac53df857489ce2a20e972717f3.jpg)
![enter image description here](http://drops.javaweb.org/uploads/images/476e1e2c0ff4a3da7082c92271aa579e9b1ff77d.jpg)好吧,重新加载程序,尝试使用最后一次异常法,不忽略所有异常,然后使用异常计数器插件,程序停在最后一次异常处:
![enter image description here](http://drops.javaweb.org/uploads/images/ab2ab1567a416f07d417c01dec5e0beeaeb6608f.jpg)
如果此时`F8`单步下去,程序会触发异常处理,然后又到不了`OEP`了。这时需要看一下堆栈数据情况:
![enter image description here](http://drops.javaweb.org/uploads/images/aa2219a5f0e90c2f5db92b133ac1d83d1a18a9b9.jpg)
这时需要在`0040CCD7`处`F2`下断点,然后`SHIFT+F9`执行,可以跳过这个坑:
![enter image description here](http://drops.javaweb.org/uploads/images/dd5663442ab6a7b3a04f03d435f32a33a8aead37.jpg)
然后接下来就是`F8+F4`的操作,一路直到`OEP`:
![enter image description here](http://drops.javaweb.org/uploads/images/0ce16d7795eb0002ae33aef5a328f5881adc855c.jpg)
![enter image description here](http://drops.javaweb.org/uploads/images/a4bf6b6d635917b73e035371cab5fb182242d3da.jpg)
用`LoadPE`脱壳,然后用`ImportREC`修复后,虽然没有无效指针,但是还是不能运行:
![enter image description here](http://drops.javaweb.org/uploads/images/037b90e168aacaaf7cc1ed0228e83e2b60631160.jpg)
这时候用`LoadPE`的重建`PE`功能:
![enter image description here](http://drops.javaweb.org/uploads/images/a971cb08757e9536e892803229995597819a1f07.jpg)
![enter image description here](http://drops.javaweb.org/uploads/images/e86a5b6ba6e5890ed17bf46d7487eb98dafc7ca5.jpg)
然后就可以正常运行了:
![enter image description here](http://drops.javaweb.org/uploads/images/24eb84d8a647714cdbbd692a12e9f7e5e502a82b.jpg)
这个壳使用了单步跟踪的脱壳方法,一路跳过程序“陷阱”,最后达到`OEP`。并且使用了`LoadPE`的重建`PE`功能,对程序进行了重建,最终完成了这个加密壳的脱壳全过程。
### 3.4 FSG变形壳脱壳笔记
首先进行侦壳:
![enter image description here](http://drops.javaweb.org/uploads/images/0ab5472cdb5c7966786d777e9bca3ac7fd56691b.jpg)
使用`ESP`定律,首先把程序扔到`OllyIce`里面,`F8`单步走,观察`ESP`变化,在`ESP`第一次发生变化时,对`ESP`对应的地址处设置内存硬件访问`WORD`断点,然后`SHIFT+F9`运行,在程序停下来之后,取消硬件断点,进行`F8`单步:
![enter image description here](http://drops.javaweb.org/uploads/images/e758813476d83ced9bcb5d887032696e6b06ea2d.jpg)
用`F4`略过向后的跳转(循环),然后继续往下找,一直到这里:
![enter image description here](http://drops.javaweb.org/uploads/images/83d6cd4da12a48003316483390e0f91133f72478.jpg)
在这个`jmp`下面`F4`,程序会跑飞。说明程序代码在这个循环中就已经释放完毕,所以向上找找这个循环中有没有带条件的大跳。这样很容易找到`magic jump`的位置,然后我们`Enter`或者`Ctrl+G`到`00402666`的位置,发现果然是`OEP`,重新分析,然后`F2`下断点,让程序走到`OEP`:
![enter image description here](http://drops.javaweb.org/uploads/images/e3239d978e1cd8104596a93f31a57ac94f0d2e4b.jpg)
如果是`FSG1.33`,直接使用`LoadPE dump`文件,然后使用`ImportREC`修复,就可以正常脱壳了。但是这里在使用`ImportREC`修复时,会出现一个无效指针:
![enter image description here](http://drops.javaweb.org/uploads/images/bd1418741625db12fd26b296848aa6b2ab9bc9df.jpg)
这里直接剪掉(或者删掉)这个指针,然后修复转存文件,发现无法正常打开:
![enter image description here](http://drops.javaweb.org/uploads/images/69197b99b65aa7301e183b6ce0fd038c7c28a773.jpg)
然后再把修复后的程序,丢到`OllyIce`中`F9`直接运行:
![enter image description here](http://drops.javaweb.org/uploads/images/faa6e130a55c59916b1fddd90dccd62407fec688.jpg)
这里是变形壳添加的一个暗桩,会导致程序出现异常退出,这里直接`nop`掉或者把之前的`jle`(校验)改成`jmp`,然后保存修改另存文件。然后就可以运行了
![enter image description here](http://drops.javaweb.org/uploads/images/6468710f1c4f38dbacd785c5e094dba875e8803a.jpg)
4 参考
====
【百度百科(各种概念)、自己之前的脱文……】 另附一篇脱壳步骤汇总:[http://www.52pojie.cn/thread-259984-1-1.html](http://www.52pojie.cn/thread-259984-1-1.html)
请登录后查看评论内容