之前,我学习过了一小部分有关程序的逆向(反编译)技术,也只是逆向技术入门的开胃小菜。这次,我继续使用动态分析技术尝试加壳程序的逆向。

程序的加壳:

程序加壳是为了尽可能防止破解和爆破程序的主要手段,一般都是隐藏OPE(程序入口点)来实现防破解功能的。

加壳技术一般是为了维护程序版权的,但对于一些计算机病毒来讲,这反而是它们逃脱杀毒软件追杀的有效手段,能让它们更好地发挥其破坏性。

程序的加壳一般分为这几种类型:

  • 压缩壳:

    • UPX

    • ASPack

    • PECompact

    • RLPack

    • NSPack

  • 保护壳:

    • ASProtect

    • Armadillo

    • EXECryptor

    • Themida

    • VMProtect

  • 捆绑壳:

    • MOleBox

加壳测试:

接下来,我要对上次那个吾爱论坛里的无壳程序进行现场加壳,为后面的脱壳操作提供样本:

程序逆向(反编译)技术学习日记二——程序的脱壳1.jpg

这里,我选择了UPX壳,使用的是GitHub上面的项目:

程序逆向(反编译)技术学习日记二——程序的脱壳2.jpg

程序逆向(反编译)技术学习日记二——程序的脱壳3.jpg

下载好后,我将无壳程序拖到upx.exe上进行加壳操作,然后得到了加壳后的程序:

程序逆向(反编译)技术学习日记二——程序的脱壳4.jpg

我们发现,加壳后的程序比加壳前的程序文件大小上小上不少,这也证明了加壳的同时也对原程序进行了压缩操作。

我们用Exeinfo PE工具查看加壳情况:

程序逆向(反编译)技术学习日记二——程序的脱壳5.jpg

工具读取出为upx加壳,版本号为4.22,证明加壳成功。

我们再用x64dbg调试器调试,发现使用搜索字符串的方法无法找到可读取的字符串:

程序逆向(反编译)技术学习日记二——程序的脱壳6.jpg

不脱壳破解:

如果没有脱壳需求的话,我们可以使用x64dbg调试器上面的附加功能来读取出正常的字符串。

首先,我们先让程序运行起来,然后在x64dbg里使用附加功能:

程序逆向(反编译)技术学习日记二——程序的脱壳7.jpg

然后,我们搜索所有模块里的字符串:

程序逆向(反编译)技术学习日记二——程序的脱壳8.jpg

最后,我们成功看到了那几个熟悉的登录提示类字符串:

程序逆向(反编译)技术学习日记二——程序的脱壳9.jpg

到这里,不脱壳的破解过程就基本上结束了。

脱壳破解:

接下来,我们要开始正片了——程序的脱壳。

这里依然使用x64dbg调试器调试加壳程序,然后来到了主程序模块开头:

程序逆向(反编译)技术学习日记二——程序的脱壳10.jpg

然后我们发现,这里程序开头的汇编指令不是传统的push ebp,而是加壳程序开头常用的汇编指令pushad,对应的操作是把程序数据压入栈,达到隐藏真正程序入口OPE的效果。所以我们接下来要做的,就是找到真正的程序入口OPE。

我们使用步进(F7)到下一行汇编指令,发现右侧的寄存器面板中,只有ESP,也就是栈顶指针的数据发生了变化。

我们右键EPS,转到内存窗口中:

程序逆向(反编译)技术学习日记二——程序的脱壳11.jpg

然后,我们对内存地址0019FF54h添加4字节的硬件访问断点:

程序逆向(反编译)技术学习日记二——程序的脱壳12.jpg

添加完断点后,我们F9继续运行程序,直到下一个断点停止:

程序逆向(反编译)技术学习日记二——程序的脱壳13.jpg

我们发现,断点处004BF61Fh的前一个汇编指令是popad,这通常是加壳代码准备执行最后清理操作的标志性指令,说明这里离真正的程序入口OPE不远了。

我们往后步进,发现程序一直在下面所示的这个蓝色区域来回跳跃,实际上这是加壳程序清理多余数据的操作,我们可以慢慢步过直到跳出去,或者用NOP修改这里的jne(条件跳转指令),使其无效化跳出循环:

程序逆向(反编译)技术学习日记二——程序的脱壳14.jpg

跳出循环后,我们通过循环后面的jmp跳转到真正的程序入口,实际上这个指令后面也有标注真正的程序入口地址是00460711h:

程序逆向(反编译)技术学习日记二——程序的脱壳15.jpg

一般的,真正的程序入口OPE后面不远处都会有调用GetVersion函数的call指令,可以作为判断OPE的标准。

接下来,我们使用x64dbg的一个插件Scylla将定位到真正程序入口的数据导出为exe程序,从而实现脱壳.

我们先用IAT Autosearch查找函数表,然后Get Imports导出函数表来修复程序的原函数表:

程序逆向(反编译)技术学习日记二——程序的脱壳16.jpg

如果在函数表中有错误的函数,可以直接删除,并使用右下角的Dump导出文件:

程序逆向(反编译)技术学习日记二——程序的脱壳17.jpg

这样,我们就得到了脱壳后的程序:

程序逆向(反编译)技术学习日记二——程序的脱壳18.jpg

虽然得到的脱壳程序好像无法直接打开(目前还在研究),但使用x64dbg调试器调试可以直接读取出有效的字符串,和无壳的读取结果基本一致:

程序逆向(反编译)技术学习日记二——程序的脱壳19.jpg

然后,我们用Eexinfo PE查看,没有了upx壳的信息,不过读取的信息好像不太准确(无Not packed提示):

程序逆向(反编译)技术学习日记二——程序的脱壳20.jpg

但我们把脱壳后的程序用PEiD查看后,是可以读取到程序初始的编译器的,为Microsoft Visual C++ 6.0:

程序逆向(反编译)技术学习日记二——程序的脱壳21.jpg

根据x64dbg读取出了正常的字符串以及可以检测到原编译器,基本可以证明脱壳成功。

不过,脱壳后的程序因为函数表一部分损坏或者程序运行区段改变等各种各样的因素,无法直接正常运行。需要等到后期真正掌握了逆向(反编译)技术后,使用各种方法去手动修复。不过目前的脱壳程度基本可以做到分析并破解程序。