2017年4月25日星期二

ANDROID 逆向实例(七)- Ali 加固(2017.01)

也算是第一次独立完成壳子的分析大概花了两周的时间,通过这个壳子学到了很多,感觉学习价值还不错 ~

先 apktool 看一下加固前后的目录结构,对比图如下,看出来主要修改的地方有两个,lib 下的 libdemolish.so 和 libdemolishdata.so,另一处是 com.ali.fixHelper 类。
这里还有一个需要留意的地方,原来的 Java 类都没有消失,目录结构也没有变化,先看一下加固后的 AndroidMainfest.xml。这里不贴图了,比较之后发现源 Mainfest 和加固后的 Mainfest 没有区别,入口还是 com.example.helloworld.MainActivity 类,看一下加固后的这个类长什么样。
乍一看和之前的类很像,只不过多了一个 [static constructor <clinit>()V] 方法, 而其他原函数都变成了 native 函数,可以看到这里比较关键的是调用了 [Lcom/ali/fixHelper;->fixfunc([I)V],不贴具体的 fixHelper 类代码了没什么实质性的内容,主要操作 load 了 libdemo.so 然后执行了 [public static native soInit()V] 方法。

不在 Java 层做过多的停留了,直接跳到 Native 层,IDA 打开 .so 看看。果然,无法正确打开,尝试了一下动态 dump 出来,用工具修复再静态加载到 IDA 还是报错,而且载入之后 IDA 的符号表为空,不过好在可以载入,虽然数据比较混乱。算了,不要函数表了,从零开始。

因为这里 dump 的没有函数表,所以动态调试的时候就算载入 libdemo.so 之后也没有办法通过 IDA 常规办法的 [Module -> Name] 来找到 JNI_Onload 函数下断点。[greadelf -a dump.so],可以看到这里是有 .init_array 的,不过这里的 .init_array 并没有什么用,不赘述了。
既然常规简单的办法不行,还可以通过逆向 libdvm.so 找到调用 JNI_Onload 的位置,我这 libdvm.so 调用 JNI_Onload 的位置在 +53A08,在这下个断点,然后动态调试进去看看。
在 +53A08 下好断点之后可以看到调用 JNI_Onload 时确实停下来了,F7,又回到了 libdemolish.so。这里因为没有函数表,先简单过一遍把 BL、BLX、PC 操作都列出来,可以 C 的地方都 make code,这里比较繁琐,需要一点耐心具体的过程不一个个说了,下面是我当时简单列的一个操作表。

+ 4214  # SDK_INT

+ 6920 # android.app.ActivityThread
+ 6B1C # currentActivityThread

+ 6B36 -> +8AB0 -> +8AD8 # new_obj_ActivityThread -> +8728
+ 6B48 -> +8AB0 -> +8AD8 # new_obj_CurrentActivityThread -> +8728

+ 6AFC -> +663E (IMP) -> JNI 操作
+ 6AAC -> +673A (IMP)-> JNI 操作

+ 6DD8 ->  +8BA4 # getString # "/data/app/com.example.helloworld-1.apk"
+ 6BC2 ->  检查是否存在 mobiledata.so
+ 70A6 # open("/data/app-lib/com.example.helloworld-1/libdemolishdata.so")
+ 6EEA # stat("mobiledata") -> +7028 # close()
+ 46E0 # malloc -> memset
+ 477C # malloc -> memset -> android.os.Binder
+ 4812 # malloc -> memset
+ 883e # 字符变换
+ 6FE8 # log("Ali", "parse data success")

+ 4298 # 检查 yunos
+ 3F86 # BLX R3 -> JNI 调用
+ 4a14 # check dvm OR art
+ 3418 # log("sdk_version: %d, dalvik, yunos: %d")

+ 3570 clock()
+ 3468 log("init success, total (%.2f ms)")

+ 8678 memset()
+ 8682 memcpy(0xBEA2B06C, ptr_dex, 0x70) # 上面的 dex 片段

while(times < 3){
+ 73EA 失败触发 -> log("find codeoffset by dexfileid error") # find_dex_codeoff

+ 74DC # strlen("Lcom/example/helloworld/MainActivity;") -> + 752C # calloc(len + 2)
+ 753E # strcpy("740A40E8", "Lcom/example/helloworld/MainActivity;")

+ 7668 # strlen("") -> + 760A # calloc(len + 2) -> memcpy(740A4E40, "", 6)
+ 7B2E # strlen("V") -> + 7ACC # strlen("V") -> memcpy(740A4E52, "V",) -> "()V"
+ 71FE 失败触发 -> log("") # chk_find_class_method
}

虽然比较乱,不过从上面的表可以看到整个流程已经很清楚了,先做了一系列初始化操作,检查 libdemolishdata.so 是否存在、检查是否 yunos、检查 dvm Or art,等等。初始化不赘述了,然后可以看到执行了三遍 [+ 73EA # find_dex_codeoff](+ 73EA 对应的是 libdemolish.so 的偏移量,find_dex_codeoff 是我方便理解取的名字下同),F5 + 还原后的伪代码如图所示。
如果执行失败则 [log("find codeoffset by dexfileid error")],看到 codeoff 其实 ali 的加固流程已经很明显了,通过 static 方法最先执行的特性 load 一个 libdemolish.so 然后再动态修复 odex 执行时对应 methodcodeoff。

也正是这个原因,所以之前看到的加固后的类结构并没有发生变化只是原有的方法变成了 native 方法,其实这里这个 native 也只不过是吓唬人的纸老虎罢了。

然后跟进 [+ 71FE # chk_find_class_method],F5 + 修复之后如下所示,检查是否有对应的类和方法,如果没有的话 log 失败日志。
而之所以 find_dex_codeoff chk_find_class_method 在 while 里执行了三遍,是因为加固前的 MainActivity 类里一共有三个方法[init, onCreate, onCreateOptions] 在加固后的 codeoff 都被修改了。

那么现在思路已经很清楚了,动态调试时在 [chk_find_class_method] 执行结束的位置下断点,然后看后续的操作。
我这里在对 [MainActivity->onCreate] 的 [chk_find_class_method] 结束后下断电,然后一路单步执行到这里,看到这个 R3 很可疑,因为它指向的是内存 libdemolishdata.so 片段,这里用的是 [R3 + #0x10] 位置的内容,即 0x73861068

0x73861068 指向的是 [6f 20 1c 00 21 00 15 00 03 7f 6e 20 b6 13 01 00 0e 00] 这么一串内容,而这一串恰巧是原 onCreate dalvik opcode!下图是通过 010Editor 看到加固前 onCreate insns
通过 dalvik opcode 对查表可以还原出 onCreatesmali 代码。

使用相同的方法可以对应恢复其他 ClassMethod,这里不一一说明了,Crack 加固成功。