2015移动安全挑战赛全程回顾 并附上解法

来源:互联网 发布:如何下载麦田识字软件 编辑:程序博客网 时间:2024/05/01 09:15


汇集全国一流安全精英,挑战业内最强移动安全防御。2015移动安全挑战赛由阿里&看雪在1月22日联合主办,本次比赛为单人挑战赛,非团队赛,参赛者需在比赛页面下载并破解给出移动应用。

所有比赛过程均在线上完成。3天赛程中共开启5道题,参赛者破解了当前题目后即可开启下一题。

比赛时,参赛者需破解给出的移动应用程序,提交正确答案,破解成功后将赢得该题对应的分值。

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

index

第一题

0x1 分析

 

0x01

AliCrackme_1

本次比赛的第一个题目是一个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
----------------------------------

 【我的解法】:

1、首先用JEB打开AliCrackme_1.apk 阅读“点击登录按钮后APK的处理逻辑”



2、从反编译的代码可知,密码和assets的logo.png相关,copy到我们的测试项目中,并copy相关方法getTableFromPic 和 getPwdFromPic()。 我们输入的字符串都会转成byte[] &255 变成 下标,对应成getTableFromPic的字符串,只要我们byte[]转成的下标对应的字符串和getPwdFromPic()相同 密码就正确了。

所以我们先用一个byte[] 保存 遍历getPwdFromPic() 对应的下标,new String(byte[])就行了。


---------------------------------------------------

第二题

0x1 分析

 

0x02

AliCrackme_2

本次比赛的第二个题目仍然是一个独立的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后的图:

0x03

 

0x04......................

 

 

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

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

aiyou,bucuoo

**************************************************

更多细节可以参考:

http://blog.csdn.net/tabactivity/article/details/78492377

接着看这个:

作者:Fly2015

AliCrackme_2.apk运行起来的注册界面,如图。


首先使用Android反编译利器JebAliCrackme_2.apkJava层代码进行分析。


很幸运,就找到了该apk程序的用户注册码的函数securityCheck并且这个函数是在Native层实现的。下面就到该程序的so库中去查找该函数的Native实现。


NativesecurityCheck函数的注册并不是使用在JNI_OnLoad函数中进行注册的方式注册的,因此非常走运的找到securityCheck函数的实现,分析如下图:


通过对Native代码的静态的分析发现, _lpSaveBuffer = off_628C中保存的就是正确的注册码字符串,因此,要获取该apk的注册码,必须对其进行动态的调试,获取到_lpSaveBuffer 中保存的字符串的内容就能实现该apk的破解。

 

对该Android应用程序进行动态的调试。说一句,Android应用程序一般会在JNI_OnLoad函数中进行程序的反调试操作,以防止别人对其App进行动态调试。经过几轮动态调试的实验,发现该Android应用程序会调用函数pthread_create创建线程进行反调试。因此,直接在函数Java_com_yaotong_crackme_MainActivity_securityCheck处下断点实现破解的方法不能直接达到。


因此,为了阻止该App程序的反调试,将程序的代码进行了修改。如图,定位地址A8CE 9C58处的ARM汇编指令BLX R7在内存中的位置,然后将汇编指令BLX R7改为汇编指令MOV R0, RONOPBLX R7指令。


至于ARM汇编指令与机器码的转换,使用下面这个工具,但是请注意,在使用这个工具的时候,必须将这个工具放在桌面上才能正常的使用。


顺利跨过反调试的障碍,下面就在函数Java_com_yaotong_crackme_MainActivity_securityCheck上下断点,直奔主题。


在程序输入密码的界面上,随便输入一个字符串密码,函数Java_com_yaotong_crackme_MainActivity_securityCheck就会被调用。


程序就会断在函数Java_com_yaotong_crackme_MainActivity_securityCheck上,然后在该函数里找到_lpSaveBuffer = off_628C对应的汇编代码LDR R2, [R1, R7]。在汇编代码LDR R2, [R1, R7]的下一条指令上下断点即可得到R2保存的密码字符串指针的地址为A8CEC450,然后在程序数据内存中同步R2的值定位到地址A8CEC450处的字符串aiyou,bucuoo,很显然aiyou,bucuoo就是要获取的密码。


AliCrackme_2题的分析文档和apk下载地址:http://download.csdn.net/detail/qq1084283172/8897059


**************************************************

第三题

AliCrackme_3

在介绍本次比赛第三道题目之前,首先要介绍一个我们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,才会进入后面的比较。我们穷搜索一下符合这类输入的字符串:

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);        }    }}

 

输出为:
s5168

 

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

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

// TODO: Annotations.

 

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

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

s57e1p

 

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

… _____ ____. . ..___ .__.


原创粉丝点击