为了熟悉 Java API 的访问,简单利用 Frida 的命令行来看一下 Android 的版本吧。
[USB::Android Emulator 5556::['com.android.chrome']]-> Java.androidVersion "7.1.1" |
或者列出所有被加载的类(警告:这将会输出一大堆东西,我下面就会对这些代码进行介绍):
[USB::Android Emulator 5556::['com.android.chrome']]-> Java.perform(function() {Java.enumerateLoadedClasses({"onMatch":function(className){ console.log(className) },"onComplete":function(){}})}) org.apache.http.HttpEntityEnclosingRequest org.apache.http.ProtocolVersion org.apache.http.HttpResponse org.apache.http.impl.cookie.DateParseException org.apache.http.HeaderIterator |
这里我们输入了一个很长的命令,我们需要明确一下其中嵌入的代码。首先,我们输入的代码的最外层包装是 Java.perform(function(){ ... }),这是 Fridas Java API 的需求。
下面是我们在 Java.perform 中插入的函数:
Java . enumerateLoadedClasses ( { "onMatch": function(className){ console.log(className) }, "onComplete":function(){} } ) |
这一步相当简单,我们利用 Java.enumerateLoadedClasses 来枚举所有被装载的类,然后利用 console.log 来输出所有的匹配。你将会在 Frida 中遇到很多这样的回调函数。你会提供下面样子的回调对象:
{ "onMatch":function(arg1, ...){ ... }, "onComplete":function(){ ... }, } |
一旦 Frida 在你的请求中发现了一个匹配,onMatch 会被一个有 一个或者多个参数 的函数所调用。然后当Frida枚举完所有的可能的匹配后,就会调用这个函数。
现在,让我们更加深入地了解一下 Frida 的魔法吧,然后利用 Frida 来覆盖一个函数,除此之外,我们也同样加载了一个外部分脚本,而不是把它出入到 cli 中,这样会更加方便一点。把下面的代码保存到一个叫脚本文件中,例如 chrome.js:
Java.perform(function () { var Activity = Java.use("android.app.Activity"); Activity.onResume.implementation = function () { console.log("[*] onResume() got called!"); this.onResume(); }; }); |
这个函数会重写 android.app.Activity 类的 onResume 函数。他利用 Java.use 来接受这个类的一个封装对象,然后访问他的 onResume 函数的 implementation 的属性来提供一个新的接口。在函数的内部,他会通过 this.onResume() 来直接调用原始的 onResume 接口,因此这个 app 可以正常地运行。
打开你的模拟器,然后打开 chrome,并利用 -l 来注入对应的脚本
frida -U -l chrome.js com.android.chrome |
一旦你触发了 onResume -e.g. 通过切换到其它的应用,然后返回到模拟器中的chrome,你将会得到:
[*] onResume() got called! |
漂亮,难道不是么?我们确实重写了一个 app 中的函数。因此,我们就很有可能控制目标 app 的行为了。但是我们可以做的更多:我们可以利用 Java.choose 来寻找堆上实例化的对象。
在我们继续之前,需要注意一点:当你的模拟器有点慢的时候,Frida 可能会超时。 为了防止这个发生,要么在函数 setImmediate 中给你的脚本添加一层包装,要么 export them as rpc 。Frida 中的 RPC 默认不会超时(这里要感谢@oleavr 给出这些建议)。一旦你修改了脚本,setImmediate 就会自动返回它,因此这相当方便。它同时在后台运行你的程序。这也就意味着,你会立即得到一个 cli,即使Frida还在执行你的脚本。我们需要做的就是等待,然后不要离开 cli,直到 Frida 已经把你脚本里所有的输出都打印出来了。再次修改一下你的 chrome.js 脚本:
setImmediate(function() { console.log("[*] Starting script"); Java.perform(function () { Java.choose("android.view.View", { "onMatch":function(instance){ console.log("[*] Instance found"); }, "onComplete":function() { console.log("[*] Finished heap search") } }); }); }); |
然后通过下面的指令运行
frida =U -l chrome.js com.android.chrome |
应该会输出如下的结果
[*] Starting script [*] Instance found [*] Instance found [*] Instance found [*] Instance found [*] Finished heap search |
可以看出,我们在堆上找到了 android.view.View 的四个对象。让我们继续来看看我们可以利用这些东西再做些什么。或许我们可以调用这些实例的方法。我们只需要简单地添加 instance.toString() 到我们的 console.log 中(因为我们使用了 setImmediate,我们可以直接修改我们的代码,Frida 会自动重新加载它):
setImmediate(function() { console.log("[*] Starting script"); Java.perform(function () { Java.choose("android.view.View", { "onMatch":function(instance){ console.log("[*] Instance found: " + instance.toString()); }, "onComplete":function() { console.log("[*] Finished heap search") } }); }); }); |
这会返回
[*] Starting script [*] Instance found: android.view.View{7ccea78 G.ED..... ......ID 0,0-0,0 #7f0c01fc app:id/action_bar_black_background} [*] Instance found: android.view.View{2809551 V.ED..... ........ 0,1731-0,1731 #7f0c01ff app:id/menu_anchor_stub} [*] Instance found: android.view.View{be471b6 G.ED..... ......I. 0,0-0,0 #7f0c01f5 app:id/location_bar_verbose_status_separator} [*] Instance found: android.view.View{3ae0eb7 V.ED..... ........ 0,0-1080,63 #102002f android:id/statusBarBackground} [*] Finished heap search |
Frida 确实调用了 android.view.View 的 toString 方法。相当酷。因此,在 Frida 的帮助下,我们可以读取进程的内存,修改函数,然后找到存在的实例化对象,然后使用简单的几行代码来使用它。
现在你应该对 Frida 有了一个基本的了解,而且应该能够自己去更进一步地挖掘它的文档与 API 了。在结束这篇文章之前,我想要添加两个主题,Frida' binding 以及 r2frida,但是首先,我们需要注意如下的事情。
注意事项
当你在用 Frida 的时候,你可能会发现不稳定性。首先,将外部代码注入到其它的进程中后,由于 app 将会以不被期望的方式运行,所以可能会导致崩溃。其次,Frida 本身也仍处于试验期阶段。虽然它也是有效的,但通常你需要尝试很多种方法才能够得到结果。例如,当我尝试在命令行中使用一条命令加载一个脚本并且生成一个进程的时候,Frida 经常会崩溃。取而代之的,我需要首先启动进程,然后再利用 Frida 注入代码。这就是我为什么给你展示了使用 Frida 的不同的方法以及防止延迟。 你需要自己去判断哪一个更加适合自己。
Python bindings
如果你想要利用 Frida 自动化工作,你可能需要 python、C 或者 NodeJS 了。例如,使用 python 注入 chrome.js 脚本,你可能需要使用 Frida 的 Python bindings,然后创建一个脚本。
#!/usr/bin/python import frida # put your javascript-code here jscode= """ console.log("[*] Starting script"); Java.perform(function() { var Activity = Java.use("android.app.Activity"); Activity.onResume.implementation = function () { console.log("[*] onResume() got called!"); this.onResume(); }; }); """ # startup frida and attach to com.android.chrome process on a usb device session = frida.get_usb_device().attach("com.android.chrome") # create a script for frida of jsccode script = session.create_script(jscode) # and load the script script.load() |
如果你想要结束你的Frida会话并且销毁在这个会话中插入的脚本,你可以调用session.detach()。
关于更多的例如,请参考Frida的documentation。
Frida 和 Radare2:r2frida
如果可以使用一个类似于 Radare2 的反汇编框架来检查我们的 app 内存,那岂不是更好。因此有了 r2frida。你可以利用 r2frida 来将 Radare2 连接到 Firda。然后使用静态分析以及反汇编进程内存。由于它需要 Radare2(如果你没有看的话,非常值得看一看)作为铺垫,我在这里并不打算详细介绍 r2frida。但是,我还是打算告诉你如何简单地使用它。
你可以利用 Radare2 的包管理器(假设你已经装了 Radare2)来装 r2frida。
我们再次回到 frida-trace 的例子,删除或者重命名我们已经修改的脚本,然后让 frida-trace 自动生成默认的脚本,然后我们再来简单看一下日志:
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 */ [...] |
通过 r2frida,你可以非常容易地检查内存地址中的内容,并且读取对应的文件名(在这个例子中是 /dev/binder)。
root@sixtyseven:~# r2 frida://emulator-5556/com.android.chrome -- Enhance your graphs by increasing the size of the block and graph.depth eval variable. [0x00000000]> s 0xa843ffc9 [0xa843ffc9]> px - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0xa843ffc9 2f64 6576 2f62 696e 6465 7200 4269 6e64 /dev/binder.Bind 0xa843ffd9 6572 2069 6f63 746c 2074 6f20 6f62 7461 er ioctl to obta 0xa843ffe9 696e 2076 6572 7369 6f6e 2066 6169 6c65 in version faile 0xa843fff9 643a 2025 7300 4269 6e64 6572 2064 7269 d: %s.Binder dri [...] |
使用 r2frida 来访问对应的内存并且使用它来注入代码的语法如下:
r2 frida://DEVICE-ID/PROCESS |
你同样可以使用 =! 来检查你可以使用那些命令。你还可以快速查找内存中的特定内容或者将内容写到任意地址。
[0x00000000]> =!? r2frida commands available via =! ? Show this help ?V Show target Frida version /[x][j] Search hex/string pattern in memory ranges (see search.in=?) /w[j] string Search wide string [...] |
后续
如果这个使你感兴趣的话,你可以看看下面的内容
Frida 的 project page
youtube 上的 @oleavr 在 r2con 上的讲话,以及 David Weinstein 的 Frida Intro talk
Frida 的 Twitter @fridadotre
Frida 的 Telegram channel
AppMon, 基于 Frida 的 app 监视以及注入的可视化工具(by @dpnishant)
本文由 看雪翻译小组成员 iromise 编译,来源 Michael Helwig@Notes on IT and Security
戳👇 图片加入看雪翻译小组哦!
❤ 往期热门内容推荐
更多优秀文章,长按下方二维码,“关注看雪学院公众号”查看!
看雪论坛:http://bbs.pediy.com/
微信公众号 ID:ikanxue
微博:看雪安全
投稿、合作:www.kanxue.com