前言
这几天我想学习一点逆向的知识,但苦于入门较难,教学视频废话太多,也不想看,就去刷ctf题,不会的就看wp,不得不说ctf真的是居家旅行,杀人灭口必备良药啊。这不,在做逆向题的时候就发现有个软件用ida打开没啥函数,查看wp才知道被加壳了,照着wp用PEID查壳,发现是upx的,人家用od演示了怎么手动去壳,但我看的云里雾里的,各种名词她也没有解释,例如oep,popad啥的,我就只能去查,而且我的od还跟他的不一样,最后我通过x64dbg去掉壳,去壳的过程使我对x64dbg的使用更加熟练,也了解了壳的原理等。于是想记录下来。
脱壳的常见概念
壳:是一种对应用程序加密压缩处理的技术
一般对可执行文件加壳的目的有三:
① 软件加壳,保护数据、防止破解
② 外挂加壳,保护数据、防止破解
③ 病毒加壳,防止被查杀。
常见的壳分为压缩壳和加密壳两种,upx属于压缩壳
脱壳:表示通过反汇编将壳去掉的过程。
OEP:程序最开始执行的地方。
原始OEP:程序加了壳后,壳是先运行的,所以OEP是壳程序的入口点,而原始OEP就是程序原来的入口点。
dump内存:将内存中的数据或代码转储(dump)到本地。
IAT: 导入地址表,windows下可执行文件中文件格式中的一个字段,描述的是导入信息函数地址,在文件中是一个RVA数组,在内存中是一个函数地址数组。
修复IAT:在脱壳后,不管是加密壳还是压缩壳,都有一个从内存dump到本地存储并保存为文件,而IAT在文件中是一个RVA数组,在内存中是一个函数地址数组,我们就需要将从内存dump出来的IAT从函数地址数组转换成RVA数组,这样程序才能修复。
脱壳的环境:这个单独出来说,主要原因就是不同的系统脱壳时遇到的问题可能是不一样的,因为脱壳时要修改IAT,而不同系统中同一个模块的API导出的顺序是不一样的,所以修复时一般都会出现点问题。因此,我建议脱壳的环境应该是在32位系统的虚拟机中,以下的所有操作应该在32位系统的虚拟机中操作,64位系统下可能会出现意想不到的问题。
pushad: 将所有的32位通用寄存器压入堆栈 ESP-32 。
popad: 将所有的32位通用寄存器出栈 ESP+32。
脱壳的方法
一步一步分析每一条汇编指令,吃透每一行汇编背后所代表的意思,将壳代码读懂,从而找到原始OEP然后脱壳。这种方法是最锻炼人的,也是最难的。 说白了就是一点点的调试,需要深厚的汇编功底和耐心。
平衡堆栈(又称ESP定律,技巧法)
一般加壳的程序,都是先运行壳,然后再内存中还原程序,然后跳转到原始OEP,开始执行原程序代码。既然先运行壳,那就必然进入壳程序然后再退出壳程序,期间要遵守堆栈平衡(进入前和退出后的栈指针是相同的),也就是说壳退出后,必然会操作堆栈指针为进入之前的堆栈指针。在进入壳程序时,就可以在堆栈设置访问断点,让程序跑起来,当程序暂停的时候,就是壳程序即将执行完的时候,然后再其附近单步跟踪就可以找到原始OEP了。 这种方法比较适用于upx这种只对代码和数据压缩了的壳,如果还对代码加密了,那么就不是太好找了。加密的话就需要结合单步跟踪法。
脱壳三步法
不管是哪种脱壳方法,都需要遵循脱壳三步法,脱壳三步法分为以下三步:
① 寻找原始OEP
这一步骤的主要作用就是要确定原始程序代码到底在哪里,能找到原始程序的代码,说明壳代码执行完了,我们只有找到原始OEP才能进行下一步的动作。
② dump内存到文件
当我们找到原始OEP,调试运行到原始OEP时,只要代码被还原,我们就可以在这个地方进行dump内存,将内存中被还原的代码和数据抓取下来,重新保存成一个文件,这样脱完壳时,我们就可以用静态分析工具分析程序了。
③ 修复文件
这一步主要就是修复IAT,对从内存中转储到本地的文件进行修复。
UPX壳
①经过UPX压缩的win32/pe文件,包含三个区段:UPX0, UPX1, .rsrc或UPX0, UPX1, UPX2(原文件本身无资源时)。
UPX0:在文件中没有内容,它的”Virtual size”加上UPX1的构成了原文件全部区段需要的内存空间,相当于区段合并。
②UPX1:起始位置为需解压缩的源数据,目标地址为UPX0基址。紧接着源数据块是”UPX stub”,即壳代码。一个典型的pushad/popad结构,所以人们常用”ESP定律”来脱UPX。
③.rsrc/UPX2:在原文件有资源时,含有原资源段的完整头部和极少部分资源数据(类型为ICON、GROUP_ICON、VERSION和MANIFEST),以保证explorer.exe能正常显示图标、版本信息。还有就是UPX自己的Imports内容,导出表的库名和函数名(如果有的话)。
就是因为UPX是一个典型的pushad/popad结构,又要遵守堆栈平衡,即pushad之前的ESP需要跟popad之后的ESP相同,所以当我们看到UPX壳进入pushad后,对堆栈添加访问断点,当壳即将及执行结束即执行到popad要去还原堆栈时,必然会触发断点暂停下来,然后单步调试往下走,原始OEP就在附近了。
打开x64dbg,拖入程序。发现EIP指在这个位置,往下看看并没有发现pushad,不知道为啥人家用od打开就能看到pushad。这种情况我们就按F8单步调试,不知按了多少次后终于遇到了pushad,我们可以看一下在没有执行pushad时的ESP为 0060FF74,如下图:
然后在按一次F8,使其执行pushad,将此时的ESP记录一下,0060FF54,正好相差32。
此时我们在堆栈打上断点,右键ESP,点击在内存窗口中转到。
然后在内存窗口中,右键断点—>硬件访问—>4字节。
打上断点后,直接按F9运行到断点处,观察ESP值为0060FF74,跟pushad前一样,遵循堆栈平衡。且上面就是popad,此时原始OEP就不远了,我们要理解壳程序执行完后,必然有个大的跳转 跳转到原始OEP去执行源程序,我们能看到下面有一个jmp 且跳转到401280,跳转范围很大,这应该就是我们要找的原始OEP.
双击jmp跳转到401280,使用插件sclla,修改OEP为401280,点击dump,dump到本地文件。保存即可。
但此时的文件无法正常运行,因为没有修复IAT,但是通过IDA可查看原始代码了,如果有运行程序的需求可以修复IAT。
修复IAT
依然使用Scylla,使用IAT自动扫描,
成功后点击 GET Imports 获取导入表
然后再点击,fix Dump 修复上一节的dump文件
即可。
总结
对于像我这种想入门逆向的,这种方式真的可以培养兴趣,也从中学到了很多知识,我也不会仅仅止步于脱upx的。