某次ctf内部赛上出了一题android的re,算是第一次认真出题了23333
因为太菜了,这破题从开始想到写花我4天= =,最后还改了一个思路
题目源码https://github.com/giglf/CTF_RE_FindTheActivity
FindTheActivity
我刚开始想到的出题思路是启动一个trick的Activity,误导验证过程,再通过AMS hook修改实际启动的Activity,真正启动的是一个要通过解密出来的dex里的Activity,这个解密出来的dex要通过DexClassLoader进行加载
但是我害怕这个太简单,就想着再加了层NativeActivity
从google的样例代码直接扒了下来简单修改了一下(不然完全不会写
初始想法是把内层的整个dex给加密到so文件的data段,然后jeb反编译的时候会什么都看不到,再动态加载内存里的dex,但是……因为太菜了不会写(捂脸),这个参考了之前pwnhub上的一道题,后来仔细看了下,发现我内层的dex写得有点大……正常编译下来700多k,本身想法是直接加密后拷到代码中作为一个全局常量放在.data段,想参考apk加固的做法,但是一直没找到……总结下来还是太菜了,感觉给整个文件patch到data段不应该是这么个做法
所以我就改成了从NativeActivity传一个key给MainActivity,作为获取解密的秘钥的关键,强制让解题者去看NativeActivity(滑稽),但是还遗留了一个偷鸡解法
其中AMS hook替换启动的Activity参考了这篇博客里的做法
http://weishu.me/2016/03/21/understand-plugin-framework-activity-management/
writeup
正常解法
首先安装上手机,发现是一个屏幕颜色不断变换的界面,点击操作无别的反应。
然后jeb直接反编译,可以发现底下有6个类,其中名称很明显的是CheckActivity
和MainActivity
对于一个android的apk,首先思路是要查看一下AndroidManifest.xml文件,这记录了该apk的一些权限、Activity等组件注册信息。
<intent-filter>
包含这两句的是第一个启动的Activity
1 | <action android:name="android.intent.action.MAIN" /> |
最后可以看到第一个启动的其实是NativeActivity,搜一下能知道这是一个纯粹靠cpp写的Activity,可以从apk解压出/lib/libnative-main.so,这是包含NativeActivity的地方,并且主函数为android_main
(但是我做了轻轻的混淆,把这符号名给删了)
但是搜一下字符串能找到
.text:00006288 0000003A C Ahahahah, go to shake your phone 100 times in 10 seconds!
这样的字符串
跳转过去就是原来的android_main
了
10秒里摇一摇摇到100次,会跳转到MainActivity(这开始应该是试出来的),会通过log输出一些信息,可以从ddms中看到
再看到MainActivity,全程就是一个crackme,获取输入,然后放到intent里,再启动CheckActivtity,然后开始验证
这个验证过程很简单,很容易就能恢复出green{Do_you_really_think_this_is_flag?}
这是是flag吗??并不是,这只是一个trick(要这么简单的话我写那么多类干嘛哦)
但是intent的的确确是启动了CheckActivity这个Activity,但实际上这个flag在手机里输入试下也是出现wrong的。
之后可以注意到,在MainActivity中还有个attachBaseContext,这个方法是在Activity生成时最开始调用的,注意到里面调用了另外的一个a的类
后面这些类都做混淆了
几个类翻看一下c里面有许多base64编码的字符串,简单解一下能看到一些类名,还有一个ctf.green.findtheactivity.check.CheckActivity
的类名,但是目录显然不包含ctf.green.findtheactivity.check
的包,后面还能看到DexClassLoader的类的调用,dex其实就是android虚拟机的可执行文件,可以猜想他加载了另外一个Dex!
再往代码上面看,能看到getAssets()的调用,base64解码后是一串类似MD5的值(的确也是MD5),然后在apk包里的asset目录能找到这个文件
接下来的操作,复制出来,对读到的字节还调用了一个函数,可以猜到是做一个解密的操作,这个解密的类是b,其中秘钥是文件名和一个int值拆解成的4字节byte数组轮流进行异或
而这个int值跟上去是MainActivity中从启动intent获取的一个叫key的字段的值
而启动MainActivity的是NativeActivity,再从NativeActivity中看,在摇到100次后会调用一个函数,这时还传进去一个int值,int值是根据摇晃次数生成的,但摇晃次数是固定的,实际这就是一个写死的值,很容易算出是70624300(数字人生——林子祥 233333)
获取key后就是解密asset底下那个文件了
因为这里算法类已经给出,可以直接复制一下自己写个java调用,或者对算法熟悉的可以看出这是一个ARC4的加解密,直接恢复后就出来那个动态加载的dex了
反编译一下这个dex,跟那个trick的CheckActivity非常相似,而且对flag的验证也只是几个异或,本来想着到最后一步了就不难为大家了(其实也是懒得写更复杂的验证算法2333333)
最后就能出来真正的flag了
偷鸡解法
后来还放出了一个hint,注意data目录,因为DexClassLoader加载的dex在程序中有一步复制出来的步骤
查看手机里/data/data/<package>/
这个存放app的信息的目录,就能发现
1 | root@pisces:/data/data/ctf.green.findtheactivity # ls |
有个app_dex和app_outdex的目录
从app_outdex中能找到一个dex文件,这其实就已经是解密过后的dex了!
正常逻辑是解密后放到app_dex
,然后app_outdex
生成的是一个缓存用的odex文件,程序中在dex成功加载后就会删除两个文件夹里的内容,所以app_dex是空的,但是运行需要这个odex文件,odex文件是没删掉的!
通过adb pull把odex文件拷出
再用baksmali把odex解出来smali
java ‐jar baksmali.jar de ‐‐classpath‐dir <framework‐dir> <classes.dex>
用smalijava ‐jar smali.jar ass out
把smali恢复成dex,得到out.dex,就能正常反编译了,然后就是那个智障的验证过程
或者,可以在动态调试还没删除app_dex下的dex的时候pull出来,再或者……看手速?