首页
社区
课程
招聘
[翻译]用Frida来hack 安卓应用III—— OWASP UNCRACKABLE 2
发表于: 2017-5-4 21:41 11845

[翻译]用Frida来hack 安卓应用III—— OWASP UNCRACKABLE 2

2017-5-4 21:41
11845



在我发完第二篇关于Frida的博文之后,@muellerberndt 立即就决定公布另一个OWASP  Android crackme。我想试试看我是否依然可以用Frida来解决这个问题。如果你也想跟着我一起,那么你需要:

OWASP Uncrackable Level2 APK

Android SDK 和 模拟器 (我用的是Android 7.1 x64镜像

 Frida  的安装包(加上  frid服务器

字节码查看器

radare2 (或者你自己选择用其他的反编译器)

apktool

如果你需要Frida的安装教程,请查看Frida官方文档. 至于Frida的使用,请查看这个教程的 第一部分 。现在我就当你已经准备好所有东西,在继续往前走之前,你还需要稍微熟悉Frida的使用,还有,确保Frida可以连接你的设备/模拟器。(比如,通过使用frida-ps -U 命令)。

说在前面的话:这不仅仅是一篇解决那个crackme的攻略。相反的,我打算向你展示几种不同的方法来解决这个具体的问题。如果你只是想看解决方法,可以直接翻到本教程的最后面,那里有Frida脚本。


注意:如果你在使用Frida时收到下面的错误

或者其他类似的错误,它可能会清除模拟器上所有的用户数据,所以你要重启然后重新安装apk

做好得多试好几次的思想准备,程序会崩溃,模拟器会需要重启,一切都可能变得很乱七八糟的,但最终,我们会成功。



和在UnCrackable 1做的一样,我们第一步是运行那个app。也和它UnCrackable 1 一样,当我们在模拟器上运行这个app时,它会被检测到说是在已经root过的设备上运行的。

Uncrackable root

我们会像在UnCrackable 1那里一样,钩住 OnClickListener函数。但在这之前,我们得看看我们是否已经连上Frida以便我们修改。


这是啥?有两个同名进程。我们可以 frida-ps -U 来验证:

好奇怪,让我们把Frida注入到父进程:

没用。因为我们是在以root运行Frida时得到这样的结果的,所以这个方案没什么效果。这是怎么了?我们得好好研究这个app。解压apk,用字节码查看器(例如CFR-Decompiler)反编译classes.dex

我们注意到static块里调用了System.load来加载foo库(参看【1】)。这个app还在OnCreate函数里的第一行就调用了this.init(),而这个函数被声明为native函数(参看【2】),所以它应该是foo的一部分。

让我们来看看这个foo库。在radare2中打开这个库(你会在lib文件夹里看到几个不同的架构,我这里用的是lib/x86_64 ),分析它并列出它的输出。

我们看到这个库导出了两个很有意思的函数:Java_sg_vantagepoint_uncrackable2_MainActivity_init  Java_sg_vantagepoint_uncrackable2_CodeCheck_bar (关于这些函数的命名,请查看 Java nativ interface JNI ),我们要看的是:

Java_sg_vantagepoint_uncrackable2_MainActivity_init

这是一个蛮短的函数:

它调用了另一个函数sub.fork_820,这个要做的事就比较多了:

我们看到调用了forkpthread_create,getppid,ptracewaitpid. 无需花太多时间来反编译我们就可以猜到,当调试器用ptrace的时候,主进程会fork一个子进程来关联它。这是很简单的反调试技术,你可以从这里了解到更多细节。

因为Fridaptrace来初始化注入,所以这就解释了为什么我们不能连接到父进程:因为已经连接了一个进程来作为调试器,再来一个进程关联调试将被阻塞。


Frida救援。相比于注入Frida到一个正在运行的进程,我们可以让它自己spawn出一个进程来给我们。用-f选项,我们告诉Frida注入Zygote然后启动该应用程序。在我们启动Frida后关掉这个应用程序,看看发生什么:

我们得到:

呼啦啦!Frida注入到Zygote了,spawn 我们的进程并等待输入。(我承认,有很多教程都告诉你们要在Frida-f选项,但你也被警告过……)

我们现在已经做好准备了。但是在继续往下走之前,我们再来看另一种针对这个crackme的反反调试方案。

除去让Fridaspawn,我们也可以通过修改这个app来解决这个问题。这意味着,我们要反编译这个app,重新打包和签名修改过的apk。然而,在这个crackme中,这样做会在后面给我们带来麻烦。就算这样,我还是决定告诉你怎样做,后面的问题后面解决。

我们可以用apktool来修改:

(我用-r来跳过了提取资源,因为这会在重新编译apk时出错。而且,在这里我们也不需要这些资源。)

来看看在smali/sg/vantagepoint/uncrackable2/MainActivity.smalismali代码。你可以看到调用inti的操作是在82行附近,你可以把它注释掉。

重新打包(忽略叼那个fatal error……):

align(优化):

签名(注意:在这一步你要有一个keykeystore,你可以在 OWASP 手机安全测试指南 看到更多介绍。):

卸载原来的apk,安装这个修改过的apk

启动这个app,运行frida-ps我们会看到只有一个进程了:

然后连接Frida也没有问题:

相比于只是在Frida里加-r选项是比较麻烦啦,但这也更普遍。


就像前面提过的,如果我们使用这个修改过的版本,那后面要提取那个Secret String就不会那么容易(尽管如此,我还是会告诉你怎样解决的,所以不要放弃哦)。但是现在我们要用的是原来的版本来进行后面的操作。确保你下面安装的是原来的版本。


在我们找到摆脱反调试的可能性后,我们来看看要怎么处理。这个app会做一个root检测,当我们在模拟器上运行的时候,只要我们按下OK按钮,就会退出。我们已经从UnCrackable1 看到过这样的情况。同样,我们可以修改这个行为,删掉对System.exit的调用。但这次我们打算用Frida来解决。查看反编译后的代码,我们可以看到并没有OnClickListener类,只有一个匿名的内部类。因为OnClickListener实现System.exit的调用,我们可以简单的hook这个函数,然后让它失效。

这是做这些操作的Frida脚本:


再次关掉UnCrackable 2,然后用Frida来打开它:

等,直到App启动并且Frida在控制台显示Hooking calls……信息。然后按下“OK”。你会得到类似下面的信息:

这样,这个app就不会被退出来了。我们可以输入一个secret string

Uncrackable root

但我们在这输入了什么?来看MainActivity里的Android代码是如何检查正确的输入的:

用到了CodeCheck类:


我们可以看到我们在文本框输入的信息—我们的“secret string”会被传送到一个名为barnative函数中。我们在libfoo.so库中再次找到这个函数。查找这个函数的地址(像我们之前找init函数那样),然后用radare2来反编译它:

Uncrackable root

仔细观察这些汇编代码,我们可以看到有一些字符串的比较操作,还看到一个很有意思的明文字符串 Thanks for all t. 我们在文本框里输入这个字符串发现并不是这个crackme的答案,所以我们还得继续。

查看在0x000010d8的汇编代码,我们可以看到:

所以,这里比较了eax0x17,也就是十进制的23。如果比较不成功,就不会调用strncmp。我们也注意到在0x00010e1处,0x17作为strncmp的一个参数:

要知道,按照64linux的调用惯例,函数参数是放在——至少参数16——寄存器中的。尤其是前三个参数是按序放在RDI,RSIRDX中(具体的可以看这里 [PDF], p. 20 )。strncmp的头部是这样的:

所以我们的strncmp函数会比较0x17=23个字符。我们可以推断出我们的secret string长度应该是23.

最后让我们尝试这去hook这个strncmp函数,输出它的参数。我们期望这样能给出解密后的字符串。我们要做的是:

找到strncmplibfoo.so中的内存地址。

Interceptor.attachhook libfoo.so中的strncmp函数,并dump它的参数。

如果你这么做了,你会发现很多地方都有调用strncmp,所以我们要进一步限制输出。这是一段Frida代码:


在这段代码中有几点需要注意的:

这段代码调用了Module.enumerateImportsSync来检索对象数组,这些对象中包含了从libfoo.so导入的信息(具体请看文档 )。我们迭代这个数组直至我们找到strncmp和它的地址。然后我们给它关联一个拦截器(Interceptor)。

Java中的字符串不是以null来终止的。当我们用FridaMemory.readUtf8String方法且不提供长度来读取strncmp内存中的字符串时,Frida会以为有\0来终止,不然就一直返回一些内存垃圾,因为它不知道字符串的终点在哪里。如果我们在第二个参数中明确给出要读取的字符串长度,我们就不会遇到这个问题。

如果我们不在判断条件那里作限制,限制我们要dumpstrncmp参数,我们会看到很多输出。所以我们只在strncmp的第三个参数size_t 23,且第一个参数指向我们的输入框的时候输出。在输入框中我们会输入01234567890123456789012 (这个字符串有23个字符)。

我是怎么知道args[0]指向我们的输入,args[1]指向那个secret string的?事实上,我并不知道。我只是测试,然后在满屏的输出中找到我的输入。如果你不想跳过这部分,你可以把上面代码中的if语句删掉,然后使用Fridahexdump输出。


这样每次调用strncmp都会输出很多hexdump,要小心哦。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 1
支持
分享
打赏 + 1.00雪花
打赏次数 1 雪花 + 1.00
 
赞赏  CCkicker   +1.00 2017/05/08
最新回复 (4)
雪    币: 32
活跃值: (48)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
前排
2017-5-4 23:07
0
雪    币: 36
活跃值: (16)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
太高级了
2017-5-5 16:38
0
雪    币: 222
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
在真机7.1.1上面发现不能劫持native函数,提示unable  to  intercept  function  at  0xa81f50c7;  please  file  a  bug,不知道有没有大佬也遇到了同样的情况
2017-5-10 17:39
0
雪    币: 3136
活跃值: (97)
能力值: ( LV9,RANK:165 )
在线值:
发帖
回帖
粉丝
5
清水丶七 在真机7.1.1上面发现不能劫持native函数,提示unable to intercept function at 0xa81f50c7; please file a bug,不知道有没有大佬也遇到 ...
不知道大佬最后怎么解决的....orz, 我情况是只要在arm架构上都不能hook native,提示unable  to  intercept  function  at 。
2018-11-21 17:30
0
游客
登录 | 注册 方可回帖
返回
//