加固厂商SO的加壳,一般都是采用UPX壳实现的。如果用原生的进行加壳,可以使用原生UPX进行脱壳。但是对于定制的UPX壳使用原生UPX是无法脱壳的。定制版的UPX一般有以下2种形式:
1 将里面的魔术字符串改掉。下图是UPX源码中定义的魔术字以及修改后的例子:
源码中的魔术字 修改后的例子
二 UPX壳loader
加壳一般需要加密和解密2个过程,UPX壳也不例外。UPX壳的解密是由 upx
loader实现的。在UPX加壳过程中,将loader放在init段,确保loader先获得程序执行的控制权。
Loader程序是由汇编代码实现的,下面是upx源码中针对arm平台的汇编代码:
下面是loader在dynamic中的位置:
当loader开始运行时的步骤如下:
从loader执行流程可以看到如下:
1、loader是由汇编代码内嵌实现的,因此要解决重定位的问题,所以其中的函数调用都是使用系统调用实现的,比如mmap 以及mprotect等函数。
2、loader 程序要将加密数据解密,解密后的数据要覆盖加密数据。
3、由于upx使用的是压缩算法,因此解密后的数据会比加密的数据大,此时loader程序也会被解密后的数据覆盖,所以loader在解密前要拷贝到内存中执行。
三 linker加载so
Android系统的linker加载是通过dlopen函数实现的。以4.4源码为例如下:
四 手脱UPX壳的原理
前面说加壳一般需要加密和解密2个过程,UPX壳也一样。我们手脱指的是不用研究加密算法,动态脱壳。当upx的loader执行完后,代码也解密完成了,并且加密的数据被解密后的数据覆盖,是否可以直接在这个时间点,将SO从内存中dump出来就可以了呢?
确实可以在这个时间点将so从内存中dump出来,但是只有这样是不行的,需要解决以下4个问题:
● 由于此时代码已经重定位完成,dump后plt段等信息已经包含了重定位后的信息,也就是绝对地址,此时静态用IDA打开,很多函数都无法识别。
● Segment段中的Load段需要恢复,UPX加密后将原始的load段的大小由原来的加密前的大小改成了加密后的大小,因此需要修复。
● Segment段中的
dynamic段在文件中的偏移需要修复。UPX将dynamic在文件中的偏移改变了。
● section 段修复。对于section段的修复论坛里面有很多,这里就不讲了。
因此解决了前面三个问题就可以实现手脱UPX壳了。
1 解决重定位的问题
我们的目标是手脱UPX壳,因此我们可以将linker中执行重定位代码nop掉,就是不让linker执行重定位的操作,自然PLT等段中就不会带有重定位的信息。
通过前面分析linker加载so的过程,soinfo_link_image调用soinfo_relocate实现重定位,下图是源码中相关调用:
下面是在linker中的对应的汇编代码(
对于如何找到对应汇编代码,只要在IDA中搜索字符串“[ relocating %s plt ]”就可以找到):
直接将上面的对sub_1464函数改成对应的”c0 46 c0 46”(thumb nop指令) 就可以。我们知道如果SO本身又调用了其他的SO,此时会优先加载其他的SO,因此注意nop的时机,避免将不是UPX壳的其它SO重定位也执行不了。
2 如何恢复segment段的load的大小
我们知道upx壳的loader会对加密的数据进行解密,解密后的数据大小,就是对应load段的大小。
下面是UPX壳部分数据排列顺序:
l_info p_info b_info结构大小都为12个字节,如下:
根据上面的loader地址我们知道第一个l_info结构的地址为0x6508。见下图:
我们先不关心l_info和p_info结构,我们只关心b_info结构。对于b_info结构,第一个成员是压缩前的大小,第二个成员是压缩后的大小。因此我们可以通过b_info获取到被压缩数据的压缩前的大小:
从上图可知,第一个b_info结构对应的压缩块:
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)