2015移动安全挑战赛(阿里&看雪主办)全程回

  • A+
所属分类:WooYun-Drops

http://drops.wooyun.org/tips/5409

Author: 上海交通大学密码与计算机安全实验室软件安全小组GoSSIP

第一题


0x1 分析

2015移动安全挑战赛(阿里&看雪主办)全程回

题目下载

本次比赛的第一个题目是一个APK文件,安装后,需要用户输入特定的密码,输入正确会显示破解成功。该题目的APK文件没有太多的保护,可以直接使用各种分析工具(如jeb等)反编译得到Java代码。

获得正确注册码的代码逻辑为: 1. 从logo.png这张图片的偏移89473处,读取一个映射表,768字节编码成UTF-8,即256个中文表 2. 从偏移91265处读取18个字节编码的UTF-8(即6个中文字符)为最终比较的密码。然后通过输入的字符的转换,转换规则就是ASCII字符编码,去比较是否和最终密码相等。

0x2 巧妙的解法

我们在这里提供一种非常愉快的解法,不需要复杂的工具和分析,大家可以参见视频


打开app后,我们使用adb logcat并加上这个app独有的 lil 标签过滤日志输出,发现app输出日志中有table,pw以及enPassword。随意输入字符串如123456789,发现enPassword中有对应的中文输出,根据输出反馈,可以知道有如下对应关系

  • 1 - 么
  • 2 - 广
  • 3 - 亡
  • 4 - 门
  • 5 - 义
  • 6 - 之
  • 7 - 尸
  • 8 - 弓
  • 9 - 己

通过观察Logcat输出可知,最终目标pw应为义弓么丸广之,根据上述table中的对应关系,我们可以得到最终密码为:

581026

第二题


0x1 分析

2015移动安全挑战赛(阿里&看雪主办)全程回

题目下载

本次比赛的第二个题目仍然是一个独立的APK文件,安装后,需要用户输入特定的密码,输入正确会显示成功。第二题APK在Java层代码中并没有关键逻辑,将用户输入直接传给native so层中securityCheck这个native method(securityCheck方法在libcrackme.so中),由native code来决定返回正确与否。

用IDA工具打开libcrackme.so,首先看下程序的大致流程,可以看到在securityCheck这个方法调用前,在init_array段和JNI_Onload函数里程序都做了些处理,而在securityCheck方法的最后有一个判断,将用户输入和wojiushidaan做比较。尝试直接输入wojiushidaan,发现密码错误,因此可以猜测前面一大段逻辑的作用就是会把这个最终的字符串改掉。此时的思路是只需知道最终判断时候这个wojiushidaan地址上的变换后的值就行了。尝试使用IDA调试发现一旦attach上去,整个程序就退出,想必一定是在之前的代码中有反调试的代码。

0x2 巧妙的解法

同上一题一样,我们提供一种非常巧妙的解法:

注意到在最终比较之前,程序使用了android_log_print函数,当我们直接运行程序时,发现这里固定输出了

I/yaotong ( XXX): SecurityCheck Started...

这时候我们想,是否可以直接patch这个libcrackme.so,修改打印的内容,直接利用这个函数帮我们输出此时真正需要比较的值。

我们选择patch的方法是直接把这个log函数往下移,因为在0x12A4地址处正好有我们需要的打印的数据地址赋值给了R2寄存器(本来是为了给后面做比较用的),因此将代码段从0x1284到0x129C的地方都用NOP改写,在0x12AC的地方调用log函数,同时为了不影响R1的值,把0x12A0处的R1改成R3。

下面是对比patch前和patch后的图:

2015移动安全挑战赛(阿里&看雪主办)全程回

2015移动安全挑战赛(阿里&看雪主办)全程回

参考视频给出了完整的解决过程:


通过观察Logcat输出可知,最终密码为:

aiyou,bucuoo

第三题


题目下载

在介绍本次比赛第三道题目之前,首先要介绍一个我们GoSSIP小组开发的基于Dalvik VM的插桩分析框架InDroid,其设计思想是直接修改AOSP上的Dalvik VM解释器,在解释器解释执行Dalvik字节码时,插入监控的代码,这样就可以获取到所有程序运行于Dalvik上的动态信息,如执行的指令、调用的方法信息、参数返回值、各种Java对象的数据等等。InDroid只需要修改AOSP的dalvik vm部分代码,编译之后,可直接将编译生成的新libdvm.so刷入任何AOSP支持的真机设备上(目前我们主要使用Nexus系列机型特别是Nexus4和Galaxy Nexus)。在本次比赛的第三题和第四题分析过程中,我们使用该工具进行分析,大大提高了分析效率。具体细节可以参考我们发表的CIT 2014论文DIAS: Automated Online Analysis for Android Applications

回到题目上,将第三题的APK进行反编译后发现代码使用了加壳保护,对付这类加壳的APK,最方便的方法就是使用InDroid来进行动态监控,因为静态加密的DEX一定会在执行时在Dalvik上时解密执行,这样我们可以直接在InDroid框架里对解释执行过程中释放出来的指令进行监控。在我们自己使用的工具里,我们开发了一个动态读取整个dex信息的接口,执行时去读DexFile这个结构,然后对其进行解析(解析时直接复用了Android自带的dexdump的代码)。这样,我们的插桩工具在运行程序后,能够直接得到程序的dex信息,同未经保护时使用dexdump后得到的结果基本一致。虽然我们得到的信息是dalvik字节码,没有直接反编译成Java代码那么友好,但由于程序不大,关键逻辑不多,因此对我们的分析效率影响并不大。

使用InDroid进行脱壳的演示视频:


在得到脱壳之后的dexdump结果后,我们可以对代码进行静态分析。我们发现用户的输入会传递给继承自Class timertask的Class b,被Class b的run方法处理。在run方法中,如果sendEmptyMessage方法被调用时的参数为0,就会导致Class c的handleMessage这个方法中得到的messagewhat值为0,进而导致103除0跳入异常处理中,触发成功的提示。

继续分析这个run方法的逻辑,可以知道用户的输入会被传递到Class e的a方法中,做个类似摩尔斯译码的过程(其译码与标准的摩尔斯电码不太一样),然后经过下面一系列大量的混淆用的无用处理和不可能相等的比较后,将译码后得到的字符串送入到关键的判断中去。这个判断成功的条件比较复杂:对于译码后得到的字符串的前两个字节,要求使用hashcode方法的结果等于3618,并且这两个字节相加等于168,才会进入后面的比较。我们穷搜索一下符合这类输入的字符串:

#!java
for ( size_t i = 33; i < 127; ++i )
{
    for ( size_t j = 33; j < 127; ++j )
    {
        String x =String.valueOf((char)j)+String.valueOf((char)i);
        if (x.hashCode()==3618 && (i+j) == 168)
        {
            System.out.println(x);
            System.out.println(j+i);
        }
    }
}

输出为:

s5
168

也就是说只有s5满足hashcode为3618,而相加等于168这个条件。

确定前两个字符后,后面还有四个字符需要同Class e和Class a的Annotation值比较。因为我们做脱壳的时候直接使用了dexdump的代码,而dexdump即使到最新版里也无法很好地处理Annotations:

// TODO: Annotations.

不过没关系,我们还有动态分析工具这一利器,因为最终目的是得到getAnnotation方法的返回值,依然可以用InDroid在Dalvik执行到getAnnotation方法时监控返回值,就能得到Annotation的具体值。使用InDroid获取具体信息的视频如下:


最后可知,符合程序需求的字符串是

s57e1p

使用程序内部的对应表,对其进行逆变换,能够让程序输入成功提示的输入应该是:

… _____ ____. . ..___ .__.

第四题


2015移动安全挑战赛(阿里&看雪主办)全程回

题目下载

本次移动安全挑战赛的第四题和第三题一样,是一个包含了加壳dex的APK文件,我们使用同解决上一题一样的方法,用InDroid得到原始dex文件的dexdump结果:

使用InDroid进行脱壳的演示视频:


Dex整体处理过程和上一题也类似,使用handleMessage处理最后的判断输入成功与否,只有sendEmptyMessage(0)后,触发除以0