专栏名称: 看雪学苑
致力于移动与安全研究的开发者社区,看雪学院(kanxue.com)官方微信公众帐号。
目录
相关文章推荐
分享迷  ·  号称唯一电视广告拦截程序,AdGuard ... ·  18 小时前  
分享迷  ·  号称唯一电视广告拦截程序,AdGuard ... ·  18 小时前  
今夜职场  ·  裁员了,很严重,大家做好准备吧! ·  4 天前  
今夜职场  ·  裁员了,很严重,大家做好准备吧! ·  4 天前  
看雪学苑  ·  浅谈Cocos2djs逆向 ·  5 天前  
云技术  ·  3000万元,GPU算力:采购H800、A8 ... ·  6 天前  
云技术  ·  3000万元,GPU算力:采购H800、A8 ... ·  6 天前  
看雪学苑  ·  House of water & ... ·  1 周前  
51好读  ›  专栏  ›  看雪学苑

用Frida入侵Android App III (二)

看雪学苑  · 公众号  · 互联网安全  · 2017-06-07 18:01

正文

继续解决难题

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

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

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

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

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

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

用到了 CodeCheck 类:

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

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

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

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

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

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

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

  1. 找到strncmp在libfoo.so中的内存地址。

  2. 用Interceptor.attach来hook libfoo.so中的strncmp函数,并dump它的参数。

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

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

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

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

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

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

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

这是代码的完整版本,用这个版本输出那些参数会更直观一些:

现在,打开Frida然后加载这个脚本:

输入字符串并按下verify:

在控制台,你将会看到:

很直观,我们可以看到secret string是Thanks for all the fish。把它填入输入框就能看到成功的消息啦。

解决修改方案

最后,一些关于修改值得注意的事以及为什么我们不能用修改过的apk得到secret string。libfoo.so中的init函数包含一些初始化逻辑,这些逻辑会阻止我们去查看secret string。

如果我们再认真看看反编译后的init函数,我们会看到一行很有意思的代码:

这个变量在后面libfoo.so的bar函数里也有用到,如果它未曾设置,代码就会跳过strncmp。

所以在它之后应该是一些布尔变量记录init函数有没有运行。如果我们希望修改过的版本能够调用strncmp,我们应该设置这个变量,或者至少阻止它跳过strncmp调用。

现在我们要重新修改,反编译apk,重写jmp指令,然后重新编译。好麻烦。因为这是Frida教程,我们将会用Frida动态改变内存。

因此,我们需要:

  1. 获取已经加载好的foo库的基址。

  2. 找到那个变量相对于库基址的偏移量(我们从反编译后的代码中可以看到这个偏移量是0x400C位)

  3. 设置这个变了为1.

所以,在Frida这边:

下面是针对这个apk修改后版本的完整代码:

现在运行这个app,用Frida加载上面的脚本,然后再次输入01234567890123456789012 。按下Verify。app会调用strncmp,然后我们就能够看到那个secret string。

愿你能从中获得乐趣。

评论、批评、建议等请移步Twitter 。感谢阅读。

注:感谢 @oleavr 帮我指出一个bug已经告诉我在Frida中正确处理指针的方法。


本文由 看雪翻译小组 lumou 编译,来源 Michael [email protected]


完~

如果你喜欢的话,不要忘记点个赞哦!


热门阅读文章:



更多优秀文章,长按下方二维码,“关注看雪学院公众号”查看!

看雪论坛:http://bbs.pediy.com/

微信公众号 ID:ikanxue

微博:看雪安全

投稿、合作:www.kanxue.com