【技术分享】如何利用Frida实现原生Android函数的插桩

阅读量377349

|

发布时间 : 2017-08-18 14:07:58

x
译文声明

本文是翻译文章,文章来源:安全客

原文地址:https://www.notsosecure.com/instrumenting-native-android-functions-using-frida/

译文仅供参考,具体内容表达以及含义原文为准。

http://p9.qhimg.com/t01864eb15b5011e2e2.png

译者:興趣使然的小胃

预估稿费:150RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


一、前言

上一篇文章中,Rohit向我们介绍了如何使用Frida完成基本的运行时测试任务。简而言之,Frida可以动态改变Android应用的行为,比如可以绕过检测Android设备是否处于root状态的函数。对于在ART(Android Runtime,Android运行时)环境中运行的应用来说,我们可以使用Java.perform来hook函数。

然而,在某些情况下,开发者会使用Android NDK来执行各种操作,比如检测root状态等,这种情况下,开发者就可以使用C++或C语言来开发代码,也可以访问APK中的函数。

在本文中,我们介绍了如何实现使用Android NDK开发的代码的动态插桩,具体而言,我们会介绍如何利用Frida来hook使用C++或C开发的函数。


二、动机

Xposed之类的框架默认情况下没有提供hook原生函数(native function)的功能,而其他工具,如android eagle eye对初学者来说并不友好,学习曲线非常陡峭。然而,我们可以使用Frida来hook基于Android NDK框架构建的那些函数。接下来我们可以看看具体的操作流程。


三、目标:Rootinspector

在本文中,我们的测试对象为Rootinspector应用,这个应用可以检查设备的root状态,应用由纯C++语言编写的原生代码构建而成。我们的目标是hook这些函数,绕过root检测逻辑。

在Rootinspector中,与root状态检测逻辑有关的代码分为两个部分。APK中的一个封装函数会调用由C++编写的checkifstream()底层函数,这一过程所对应的java函数为checkRootMethodNative12(),如下图所示。

 http://p6.qhimg.com/t01dac1402bdc130987.png

checkRootMethodNative12()是Android APK中使用Java编写的函数,会调用底层的checkifstream()函数,后者使用C++编写。

这个Android APK中声明的所有原生函数如下所示。

 http://p7.qhimg.com/t01d7cc0fac0beecb75.png

检查原生函数的源代码后,我们发现这个函数的具体实现为JavacomdevadvancerootinspectorRootcheckifstream,这个字符串由包名及函数名构成,由“”符隔开。

我们首先尝试hook checkRootMethodNative12()这个Java函数,所使用的代码如下所示:

 http://p2.qhimg.com/t01e3ea3d99188b7147.png

然而,上述代码没法实现hook任务,出现的错误如下所示。Frida无法获得Root类对应的“localRoot”对象的引用。

 http://p1.qhimg.com/t01bcb7686bbe9dc757.png

在这种情况下,我们无法hook使用C++编写的那些函数,因为这些函数没有运行在Java VM上下文环境中。因此,我们必须做些改变,才能hook到原生的C++代码。


四、Hook原生代码

我们可以使用Frida中的Interceptor函数,深入到设备的底层内存中,hook特定的库或者内存地址。

当APK被封装打包时,编译器会编译C++代码,将其存放在APK文件lib目录中的“libnative.so”,如下所示。

 http://p2.qhimg.com/t016b9360804738b262.png

使用Interceptor时,我们需要hook libnative2.so这个.so以及Javacomdevadvance_rootinspectorRootcheckfopen函数。我们需要使用十六进制编辑器或者调试器来读取.so文件,通过逆向工程获取函数名。这里我们耍了点小聪明,因为我们对应用的源代码已经非常熟悉。

现在,我们可以运行如下代码,看看我们是否可以成功拦截到checkfopen这个原生函数。

 https://p5.ssl.qhimg.com/t01886fe0420b4eae55.png

执行上述代码后,我们又遇到一个错误,错误提示某个指针不存在,这意味着libnative.so文件没有被正确加载,或者应用没有找到这个文件。

 https://p2.ssl.qhimg.com/t013f73edc4dfd40c4d.png

然而,再次运行代码,保持应用处于启动状态,我们的代码就能正常执行。具体操作为,先结束第一次运行的脚本,保持应用处于打开状态,再次运行脚本,点击“inspect using native code”按钮后,程序的运行状态如下图所示。

 https://p2.ssl.qhimg.com/t0159d5f1181fb9db5f.png

我们有必要了解发生这种情况的具体原因。在Android 1.5中,Android NDK提供了动态链接库(与Windows环境中的DLL类似),以支持NDK中的动态加载特性。当我们第一次启动应用时,dll文件(libnative2.so)没有被加载,因此我们会得到一个“expected a pointer”的错误信息。现在,当我们终止脚本、保持应用处于打开状态时,再次运行脚本,程序发现dll文件已经被加载,因此此时我们就可以hook目标函数。

现在换个思路,不必等待程序加载dll文件,我们可以在“dlopen”函数上设置一个陷阱,这个函数是一个原生系统调用,可以用来加载与应用有关的所有动态链接库。一旦dlopen函数hook成功,我们就可以检查我们的目标dll有没有被加载。如果dll是第一次被加载,我们可以继续运行,hook原生函数。我们使用didHookApi布尔值检查hook过程,避免dlopen被多次hook。

我们使用如下代码来直接hook原生函数。代码可以分为两部分。

在代码第17-30行中,我们首先尝试使用Frida的Module.findExportByName API来hook dlopen函数,然后搜索内存中的dlopen函数(这里只能祈祷该函数没有被覆盖)。

在onLeave事件中,我们首先检查我们的目标DLL有没有被加载,只有DLL已经被加载的情况下,我们才会hook原生函数。

 https://p0.ssl.qhimg.com/t01fa5b23cab0b41a05.png

执行最终的脚本后,我们就可以通过原生函数,绕过Rootinspector的root检测机制,过程如下所示。

脚本运行之前如下所示:

 https://p1.ssl.qhimg.com/t015c346e8df308043e.png

现在,关掉应用,在不启动应用的情况下运行脚本。脚本会自己打开这个应用。我们只需要点击“Inspect Using Native”这个按钮即可,如下所示。

https://p4.ssl.qhimg.com/t019f6e8ff0f0683ceb.png

本文翻译自安全客 原文链接。如若转载请注明出处。
分享到:微信
+10赞
收藏
興趣使然的小胃
分享到:微信

发表评论

内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66