FindTheActivity出题记录

某次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个类,其中名称很明显的是CheckActivityMainActivity

对于一个android的apk,首先思路是要查看一下AndroidManifest.xml文件,这记录了该apk的一些权限、Activity等组件注册信息。

<intent-filter>包含这两句的是第一个启动的Activity

1
2
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

最后可以看到第一个启动的其实是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
2
3
4
5
6
root@pisces:/data/data/ctf.green.findtheactivity # ls
app_dex
app_outdex
cache
files
lib

有个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>

用smali
java ‐jar smali.jar ass out

把smali恢复成dex,得到out.dex,就能正常反编译了,然后就是那个智障的验证过程

或者,可以在动态调试还没删除app_dex下的dex的时候pull出来,再或者……看手速?

×

赞助gif换电脑、吃双皮奶(逃

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. FindTheActivity
  2. 2. writeup
    1. 2.1. 正常解法
    2. 2.2. 偷鸡解法
,