本文融合了某CSDN博主的两篇文章《顶象加固分析和一点还原》和《顶象企业版加固逆向分析》,可以理解为分别对顶象加固免费版和企业版进行的分析。
《顶象加固分析和一点还原》
一:首先dump出核心函数的so
参考:https://bbs.pediy.com/thread-221270.htm(看雪的一篇精华帖《某Android DEX vmp加固逆向分析》)
可以从上面看到的是内存地址是不连续的,并且走到.init_array处以后就会消失code区域。可能是中间存在一些解壳的数据或者是IDA没有读出来。先不管这些,往应用级函数走。
在应用级函数oncreate处按照在.init处的libx3g.so在内存中的地址进行dump。在010中按照
进行修复,然后用IDA打开,可以清晰的看到oncreate和test方法。
二、接一下一步步的分析并尝试着手动进行还原。
1.函数NewLocalRef有时被用来确保一个工具函数返回一个局部引用,先这么理解,先往下看。
Sub_206C函数是一个JNI抛出异常的解决办法,如下图所示为函数体:
接着往下看sub_2170函数:
可以看到通过Findclass获取类的对象,然后通过GetMethodID来获取oncreate方法的属性。接下来通过sub_13C8函数调用callNonvirtualVoidMethod对刚才获取的方法属性进行调用。
这就是一个JNI反射调用的过程,所对应的Smali代码如下:
最后通过sub_1980本地方法执行完毕后进行释放。
可以发现顶象在对于oncreate函数这块的安排上主要是采用对于Android SDK中JNI反射调用的原理。
通过上面的分析可以推测出对应的smali的调用过程为:
Invoke-super{参数怎么确定}Landroid/support/v7/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V
2.接着往下分析,可以发现setContentView跟上面那个oncreate的调用是一样,但是对于这个参数是怎么确定的,在静态分析的时候是不清楚的。
需要动态调试进行分析一下。
可以确定的是这块所执行的smali代码为:
const v2, 0x7f040019
invoke-virtual {p0, v2},Lcom/example/zbb/dingxiangdemo01/MainActivity;->setContentView(I)V
3.接着往下看
可以发现跟上边是一样的原理,只不过在这里可以看到的是得到的值给了V6,因此肯定有一个调用函数返回给某个寄存器的过程。动态调试看一下运行的过程为:
因此可以得到大概的对应的smali的代码为:
const vX, 0x7f0c0051
invoke-virtual {p0, vX},Lcom/example/zbb/dingxiangdemo01/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object vX
4.接着往下分析看到的是
这两个函数的作用就是,在上面一步可以得到的是通过findviewbyid得到的,自然要把对象引用转换为ID类型的实例。因此这可以得到对应的smali为:
check-cast v1, Landroid/widget/EditText;
5.接着往下看,下面这个跟上面是相似的就不描述了。
可以大致推断出来smali的代码为:
const v2, 0x7f0c0052
invoke-virtual {p0, v2},Lcom/example/zbb/dingxiangdemo01/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v0
check-cast v0, Landroid/widget/Button;
6.在这里可以看到一个新的类的出现
new-instance v2,Lcom/example/zbb/dingxiangdemo01/MainActivity$1;
invoke-direct {v2, p0, v1},Lcom/example/zbb/dingxiangdemo01/MainActivity$1;-><init>(Lcom/example/zbb/dingxiangdemo01/MainActivity;Landroid/widget/EditText;)V
invoke-virtual {v0, v2},Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V
三、对于单纯的java2C
我在测试的过程中专门写了一个纯的java方法,可以看到的是顶象变成了一个C方法,如下图所示:以此为原java、汇编、伪C
对于以上这种的还原目前还没有想到好的办法,只能是理解汇编、理解伪C,写出对应的java,我觉得这种想法实现起来会比较困难。
四、手动还原or自动还原
上面的一个分析是手动进行大致还原的一个过程,真的是异常的麻烦,还是一个事倍功半的效果,包括对于MainActivity$1都还没有进行还原分析,这还是一个简单的demo,如果一个成熟APK这么还原肯定是行不通的,如果把so进行混淆,直接就增加了工作量。对于在自动还原这一块,也还没有想到好的自定处理脚本应该来怎么写。
五、总结
顶象这种保护目前来说我认为是比较理想的并且达到的效果应该是在之前以360为代表的dex虚拟机之上的,首先把所有的smali理解变为C,并且进行编译,在这种情况下的很多语义完全的失真,对于攻击者来说要想还原只能是理解ARM的基础上转成smali。对于ARM汇编的理解还是难度比较大。所以这个保护策略的价值比较大。
对于保护者来说,有了进一步的smali转为C,我们可以在编译或者在编译之前对dex都做一些混淆,或者说对保护完的so做加壳加密等保护措施,以及对C做一些白箱转换的保护我觉得最后的效果是惊人的。还有现在最为流行的对so做VMP保护。因此可做的事情会很多很多。
六、YY一下
思考一下如果作为一个加固者,对于一个待保护的APK是怎么进行保护的?
首先反编译得到对应的Smali代码,然后如果从单纯的Smali转为C,应该是不是很现实的。
这里的重点是理解Dalvik虚拟机或者ART虚拟机是怎么执行这些汇编的,用C语言是怎么模拟执行Smali的。有待于进一步的考虑和分析。
————————————————
《顶象企业版加固逆向分析》
首先看一下顶象对于这种自身的虚拟源码保护是怎么说明的?
结合免费版分析可以猜测是这样的加固流程:
首先进行免费版转换把dex中对应的方法通过JNI反射转换为cpp文件(当然这里企业版比免费版转换的方法要多,免费版中的内部类MainActivity$1就没有进行转换);
然后就是独有工具链的编译,猜测很有可能是基于LLVM的自定义编译;
进行虚拟化保护,生成相对的VMdata和对应的Handler解释器,最后每次通过dispatcher来读取VMdata完成解释。
一、分析篇:
1.So脱壳:
根据上一次的免费版分析知道顶象中so的保护采用的变形的UPX加壳。
在linker执行完第一个init函数,也就是upx的loader函数后开始dump.
在偏移值为274E处下断点:
根据base和Size dump出来
dump下来接着修复。
通过对Load和dynamic进行简单修复后基本上所有的函数,静态都可以看到了,
如图所示:
2.直接分析:
静态分析:
这里面要抓住几个核心的问题,一是核心代码的虚拟指令,二是虚拟安全运行环境.
OK,开始吧!在IDA中打开分析看到JNI_Onload、oncreate、wanwan01、wanwan02、wanwan0、Testloop等这些函数基本上是遵从一个规则那就是:
所以可以猜想的是这几个函数是构成那个虚拟运行环境的重点函数,也是我们分析的重点。
进入sub_43B0这个函数以后发现如下:
经过简单分析以后发现
在F5看伪代码以后发现:是一个控制流平展图,里面的case总共有250个左右,但是只有50多个,200个左右没有显示,大致猜测加固原理如下:
在加固处理的时候对dex native化形成的cpp文件进行语法分析,或者就是上面所说的形成了中间二进制文件,然后根据这些二进制文件的特征来形成了50个左右“Handler”。(总共是253个“Handler”,这里只用到50个),每次解释的时候读取这个虚拟指令集合。然后调用相应的Handler来进一步的进行解释完成。
动态调试分析:
如上图所示为去虚拟指令的一个过程。下图是每次调用执行完返回的一个过程。
下面感觉是维护一个地址表:
并且在调试过程中发现case94、case 249、case107、case222、case197这些Handler的内容是一样的,或许是一些无用的垃圾指令。
在调试中如下图所示会发现有大量的重复的虚拟指令,并且对应的Handler也没有具体的语义。如下图所示:
因此转换思路,从JNI一些系统函数入手:
3.间接分析:
转换思路,HOOK 这些JNI 接口函数,比如Findclass,GetMethodID函数,或者是找到这些函数地址然后进行向上引用来发现其中的Handler和对应的虚拟指令。
通过对之前免费版的加固分析,我们这里只重点关注两个重点函数Findclass,GetMethodID;
发现FindClass的地址为:41520778 ;GetMethodID的地址为:4151FC58并且在手机不重启的情况下这个地址是不变的。
因此接着分析:
同样的继续F9执行发现每次都是停留在116这个case中,这个时候才明白真正的扮演dispatcher的角色是这条分支。
-..$..$-.$--.$-..-$..$.-$-.$--. 这个偏移里面保存就是各个类和方法名的一些字符串
同样的继续F9执行可以看出:又是执行ExceptionCheck这个系统函数。
可以看出这个过程执行的不就是在上一次免费版中sub_2170函数,如下图所示:
对于反射执行中只要理清了这些系统函数的一个调用过程基本就可以搞清楚基本逻辑了,接下来就不用分析了,基本上case 116这条分支执行的就是类似把如下的这些sub_XX函数走完一遍:
上面拿onCreate含有Android SDK的函数作为重点分析例子,接下来一个JAVA SDK的也就是Testloop这个方法:
通过上面的分析发现也是case 116这条Handler,基本可以逆推出反射执行的所有流程和框架,这里不再进行演示。
因为在上一个so没保护的APP中核心分支是210,并且所对应的“虚拟指令”也是不同的,这可能就是顶象所说的每台设备的虚拟指令均不相同。
从以上可以说明在同一个APP中核心的分支是一样的,不同的APP的“虚拟指令”是不同的并且核心分支也是不一样的。
对于含有Android SDK 或者java SDK的反射分析基本就到这了,其实可以借助于HOOK JNI接口函数,然后进行log输出,根据log的输出以及结合以上的分析更好的理解整个反射执行的基本流程,从而尝试着手动进行还原。
二、总结篇
优点:
1.首先这种编译虚拟方法可以完美的解决平台兼容性问题,不像基于指令虚拟,需要很多的适配。
2.真正的做到了逻辑的隐藏并且确实每个加固的APP的“虚拟指令”和对应重要case分支不一样。
3.通过对生成的cpp进行控制流混淆,并且在里面加入垃圾分支,增加了正面分析的难度。
Dex保护方面:
java层先做了一层java2cpp的翻译,之后才走虚机机保护。即cpp2vmp的过程。由于java2cpp的过程中需要借助于JNI的一些接口,因此间接去找核心的关键点。
Cpp保护方面:
Cpp层直接就是DX单独提供的私有的NDK编译工具,直接在编译过程中进行虚拟加固保护,很强。就不能借助上面的逻辑了,需要正面分析。
三、最后
本身年前分析的,还没有完全分析清楚,年后有许多别的事情,没时间,分享给大家,希望大家一起交流学习。
DX加固做的真的很强大,在每一个步骤上的处理要想做到完善完美难度都很大!
————————————————
版权声明:本文为CSDN博主「不知世事」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:
https://blog.csdn.net/feibabeibei_beibei/article/details/78667481
https://blog.csdn.net/feibabeibei_beibei/article/details/79431130
有加固需求的同学点看这里:
Android应用加固:https://www.dingxiang-inc.com/business/stee
iOS应用加固:https://www.dingxiang-inc.com/business/ios
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2020-5-21 20:41
被顶象编辑
,原因: