专栏名称: 看雪学苑
致力于移动与安全研究的开发者社区,看雪学院(kanxue.com)官方微信公众帐号。
目录
相关文章推荐
第一财经资讯  ·  AIPC难推销,全球电脑巨头股价暴跌! ·  2 天前  
第一财经资讯  ·  AIPC难推销,全球电脑巨头股价暴跌! ·  2 天前  
超前一步午后一股  ·  特写 || 量子科技产业链全梳理! ... ·  4 天前  
超前一步午后一股  ·  特写 || 量子科技产业链全梳理! ... ·  4 天前  
太星小升初  ·  心仪哪所?西城初中校热度Top10来了 ·  1 周前  
仓总嘉措  ·  历时1年,南非报业终于要减完1个百分点 ·  1 周前  
仓总嘉措  ·  历时1年,南非报业终于要减完1个百分点 ·  1 周前  
51好读  ›  专栏  ›  看雪学苑

使用 Frida 来 hack 安卓 APP(一)

看雪学苑  · 公众号  · 互联网安全  · 2017-04-17 18:24

正文

我去年看 RadareCon 的时候,我学习了一个动态二进制插桩框架 Frida 。而且,最初看起来仅仅是有意思的东西,其实是很有趣的。还记得在游戏里的上帝模式么?在原生应用中使用 Frida 就是这样的一种感觉。这是一篇介绍如何使用 Frida 来操作 Android apps 的文章。


1


什么是动态二进制插桩?

动态二进制插桩(Dynamic Binary Instrumentation)意味着我们要在已经存在(或者运行)的二进制文件中注入外部代码来使得它们去做一些它们之前没有做的事情。它并不需要我们写 exp,因为代码注入的发生并不依赖于你是否已经找到了漏洞。它也并不是在debugging,因为我们并不需要使用 debugger 调试 binary,但是我们仍然可以做一些类似的事情。那么我们可以使用 DBI 做什么呢?有以下一些炫酷的事情:


•             访问进程内存

•             在应用运行的时候覆盖函数

•             调用导入的类中的函数

•             寻找堆上的对象实例,并且利用它们

•             Hook,记录以及拦截函数等等


当然你也可以利用 debugger 来做上面的这些事情,但是却会遇到一系列的问题。例如,在 Android 中,你需要反汇编并且重新编译一个应用之后,它才可以被 debug。一些 app 还会检测,并且会试图阻止 debugger,因此,你还要去想办法跳出对应的逻辑。当然,这是很有可能的,但是确实比较麻烦的。使用 Frida 的 DBI 使得我们不需要知道其中的细节就可以快速开始。


2


Frida


 

Frida 允许你将 JavaScript 的部分代码或者你自己的库注入到 windows、macos、linux、iOS、Android,以及 QNX 的原生应用中。它最初是基于 Google 的 V8  Javascript runtime,到了版本 9 之后,Frida就开始使用内部的 Duktape 了。但在你需要的情况下,你仍然可以切换到 V8。Frida 有很多模式的操作来和二进制文件进行交互(也有可能在没有 root 的设备上的 app 中插桩),但是在这里我们就是举一些常用的例子,并且不关心内部的情况。

首先,你需要如下软件:


  1. Frida。在这个教程中我是用的是9.1.16版本

  2. 一个 release page 上的 Frida-server 可执行文件(在写这篇文章的时候,我使用的是 frida-server-9.1.16-android-arm.xz,这个可执行文件的版本应该和你的 Frida 版本匹配)

  3.  一个安卓模拟器或者被 root 的设备。Frida 是在 Android4.4 上开发的,但它应该也会支持之后的版本。我在这篇教程中成功地在 Android7.1.1 中使用了它。对于第二部分的 crackme,我们无论如何也需要 Android4.4 之后的版本。


同样,我假设你已经有了一个 linux 为主机的操作系统。当然,如果你正在使用 windows 或者 mac,你需要调整一下命令。

如果你想要跟着我在第二部分的讲解中解决 OWASP中的Unbreakable Crackme Level 1,你应该也需要下载:

  1. OWASP Uncrackable Crackme Level 1(APK)

  2. BytecodeViewer

  3. dex2jar

Frida 提供了一系列的 API 以及方法来开始你的旅程。你可以使用命令行窗口或者像 frida-trace 的记录 low-level 函数(例如 libc.so 中的'open'调用)的工具来快速运行。你可以使用C,NodeJs或者Python绑定来完成更加复杂的工作。Frida 在内部使用了很多 JavaScript 语言,因此很多时候你可能都需要和这个语言打交道。因此,如果你也像我一样总是有点不喜欢 JavaScript (除了它的 XSS capabilities),Frida 就是一个你需要熟悉它的理由。

如果你还没有安装 Frida,那就使用下面的方法来安装(当然你也可以根据 README 采用其他的方法来安装):


pip install frida

npm install frida

然后启动你的模拟器或者连接到你的设备,并且确保 adb 可以运行,之后列出你的设备:

michael@sixtyseven:~$ adb devices

List of devices attached 

emulator-5556 device

然后安装 frida-server。从压缩包中提取对应的文件,并把它 push 到设备上:

adb push /home/michael/Downloads/frida-server-9.1.16-android-arm /data/local/tmp/frida-server

使用设备的 adb 来打开一个 shell,并且切换到 root 状态,然后启动 frida

adb shell

su cd /data/local/tmp

chmod 755 frida-server 

./frida-server



Note1

  • 如果 frida-server 没有启动的话,请确保你处于 root 状态,并且之前传输的那个二进制文件被正确传输了。我曾经由于传输文件不成功而导致了一些奇怪的错误。

  • 如果你想要将 frida-server 作为一个后台程序启动,请使用 ./frifa-server &。


在另外一个终端中,一个正常的 OS 的 shell,检查 frida 是否正在运行,并且列出在 Android 上的进程

frida-ps -U

-U 代表着 USB,并且让 Frida 检查 USB-Device,但是使用模拟器也会有这样的效果,你会得到类似于下面的结果

michael@sixtyseven:~$ frida-ps -U

  PID  Name ----  --------------------------------------------------  

  696  adbd 

  5828 android.ext.services 

  6188 android.process.acore 

  5210 audioserver 

  5211 cameraserver 

  8334 com.android.calendar 

  6685 com.android.chrome 

  6245 com.android.deskclock 

  5528 com.android.inputmethod.latin 

  6120 com.android.phone 

  6485 com.android.printspooler 

  8355 com.android.providers.calendar 

  5844 com.android.systemui 

  7944 com.google.android.apps.nexuslauncher 

  6416 com.google.android.gms 

  [...]

你可以看到进程号 id(PID),以及正在运行的程序的名字。利用 Frida,你现在就可以 hook 其中任意一个进程,并且开始进行修改。

例如:你可以追踪 Chrome 的某些调用(如果它还没有运行的话,请记得先启动chrome)。

frida-trace -i "open" -U com.android.chrome

然后你会得到如下的结果

michael@sixtyseven:~$ frida-trace -i open -U -f com.android.chrome

 Instrumenting functions...                                               

  

 open: Loaded handler at "/home/michael/__handlers__/libc.so/open.js" 

 Started tracing 1 function. Press Ctrl+C to stop.                                   

  

 /* TID 0x2740 */    

 282 ms  open(pathname=0xa843ffc9, flags=0x80002)            

 /* TID 0x2755 */    

 299 ms  open(pathname=0xa80d0c44, flags=0x2)            

 /* TID 0x2756 */    

 309 ms  open(pathname=0xa80d0c44, flags=0x2)            

 /* TID 0x2740 */    

 341 ms  open(pathname=0xa80d06f7, flags=0x2)    

 592 ms  open(pathname=0xa77dd3bc, flags=0x0)    

 596 ms  open(pathname=0xa80d06f7, flags=0x2)    

 699 ms  open(pathname=0xa80d105e, flags=0x80000)    

 717 ms  open(pathname=0x9aff0d70, flags=0x42)    

 742 ms  open(pathname=0x9ceffda0, flags=0x0)    

 758 ms  open(pathname=0xa63b04c0, flags=0x0)

frida-trace 命令会产生一些 javascript 文件,这些文件就是 Frida 注入到进程中用来记录某些调用的。我们可以简单看看生成的 open.js,它的目录为 __handlers__/libc.so/open.js。它 hook 了 libc.so 中的 open 函数,然后输出了对应的参数。在 Frida 这样做是非常简单的。

[...]

 onEnter: function (log, args, state) {     

    log("open(" + "pathname=" + args[0] + ", flags=" + args[1] + ")"); 

 }, 

[...]

请注意 Frida 如何提供访问到被 chrome 在内部调用的 open 函数的参数的能力。我们来简单改一下这个脚本。如果我们将对应输出文件的路径名修改为可以阅读的文本格式而不是这些路径被存储的内存地址,那岂不是更好?幸运的是,我们可以直接利用Frida来访问内存。简单看一下 Frida 的 API 以及 Memory 对象。我们可以修改我们的脚本,并将内存地址对应的内容以 UTF8 的格式输出,这样我们就可以看到一个更加直观的效果了。修改之后,脚本大概是这个样子的

onEnter: function (log, args, state) {

     log("open(" + "pathname=" + Memory.readUtf8String(args[0])+ ", flags=" + args[1] + ")"); 

},

我们仅仅添加了 Memory.readUtf8String 函数,然后我们得到了

michael@sixtyseven:~$ frida-trace -i open -U -f com.android.chrome

 Instrumenting functions...                                               

 open: Loaded handler at "/home/michael/__handlers__/libc.so/open.js" 

 Started tracing 1 function. Press Ctrl+C to stop.                                   

 /* TID 0x29bf */    

 240 ms  open(pathname=/dev/binder, flags=0x80002)            

 /* TID 0x29d3 */    

 259 ms  open(pathname=/dev/ashmem, flags=0x2)            

 /* TID 0x29d4 */    

 269 ms open(pathname=/dev/ashmem, flags=0x2)            

 /* TID 0x29bf */    

 291 ms  open(pathname=/sys/qemu_trace/process_name, flags=0x2)    

 453 ms  open(pathname=/dev/alarm, flags=0x0)    

 456 ms  open(pathname=/sys/qemu_trace/process_name, flags=0x2)    

 562 ms  open(pathname=/proc/self/cmdline, flags=0x80000)    

 576 ms  open(pathname=/data/dalvik-cache/arm/system@app@[email protected]@classes.dex.flock, flags=0x42)


Frida 输出了对应的路径名,对吧?

另一个需要注意的事情是,你不仅可以在利用 Frida 注入到某个进程之前启动它,你还可以利用-f参数来使得 Frida 自己产生对应的进程。

现在我们来看一下 Frida 的命令行接口 frida-cli:

frida -U com.android.chrome

这将会启动 Frida 和 Chrome app。但是它并不会启动 chrome 的主进程。这也就意味着,这样就给了你机会使得你可以在主线程启动之前注入 Frida 代码。不幸的是,在我这里,这样做总会使得 app 在两秒之后被自动杀死。这并不是我们想要的。正如 cli 输出所建议的,你可以利用这两秒去输入 %resume,使得 app 启动其主线程。或者你也可以直接使用 --no-pause 参数来使得无论如何也不要中断 app 的启动,同时,将生成进程的工作交给 Frida。

无论你是用哪一种方法,你都会得到一个不会被kill的shell,利用这个你就可以使用Frida的JavaScript的API向其中写命令。你还可以利用TAB键来看一下一些可以使用的命令。这个 shell 是支持补全命令。

大部分你想要做的事情都会有对应的文档。对于 Android,你可以看一下 Javascript API 部分的 Java section( 尽管从技术角度出发,应该说是访问 java 对象的Javascript外壳,但我这里就直接说是“Java API”)。我们将在下面关注 Java API,因为这是一个相对来说比较容易与 app 进行交互的方法了。我们并不需要直接 hook libc 函数,我们可以直接与 java 函数以及对象进行交互。(注意,如果你对使用 Frida 做其它的事情感兴趣的话,你可以 hook 更低一级的 C 代码,也就是我们使用的 frida-trace。)

本文由 看雪翻译小组成员 iromise 编译,来源 Michael Helwig@Notes on IT and Security


戳👇 图片加入看雪翻译小组哦!



❤ 往期热门内容推荐




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

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

微信公众号 ID:ikanxue

微博:看雪安全

投稿、合作:www.kanxue.com