BruceFan's Blog

Stay hungry, stay foolish

0%

Frida使用说明

Frida是一个动态代码插桩框架,这里的介绍主要以应用在Android平台应用程序上。动态二进制插桩(DBI)是将外部代码注入到现有的正在运行的二进制文件中,从而让它执行额外操作。DBI可以:

  • 访问进程内存
  • 在应用程序运行时覆盖函数
  • 从导入的类调用函数
  • 在堆上查找对象实例并使用
  • Hook、跟踪和拦截函数等

调试器也能完成相应工作,不过非常麻烦,比如各种反调试。

实验环境

1.操作系统 MacOS(Linux和Windows也可以)
2.主机安装Frida:

1
2
3
$ sudo pip install frida
...
Successfully installed frida-10.0.9

3.下载服务器二进制文件frida-server
我下载的是frida-server-10.0.9-android-arm.xz,要注意的是frida-server文件要与安装的frida是同一个版本。
这里说明一下我用的是nexus5 Android4.4,但是发现后面的实验所需Android的版本较高,改为使用SDK自带的Emulator,新建了一个Android7.0的虚拟机。

Frida基础

Frida可以在Android应用程序中注入JavaScript或自己的库代码。从版本9开始Frida不再使用Google的V8 Javascript运行时,而是使用其内部的Duktape运行时。启动模拟器或连接设备:

1
2
3
$ adb devices
List of devices attached
emulator-5554 device

安装并启动frida-server:

1
2
3
4
5
$ adb push frida-server-10.0.1-android-arm /data/local/tmp/frida-server
$ adb shell
generic:/ # cd /data/local/tmp
generic:/data/local/tmp # chmod 755 frida-server
generic:/data/local/tmp # ./frida-server

查看frida-server是否启动成功:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ frida-ps -U
PID Name
---- --------------------------------------------------
698 adbd
2113 android.process.acore
2247 android.process.media
700 audioserver
701 cameraserver
3426 com.android.gallery3d
1261 com.android.inputmethod.latin
1989 com.android.launcher3
3543 com.android.managedprovisioning
2720 com.android.phone
1771 com.android.printspooler
3122 com.android.providers.calendar
1971 com.android.systemui
...

-U表示USB,允许Frida检查USB设备。这时将看到一个进程列表。现在可以通过Frida hook到任何一个进程,并对其进行插桩了。

Hook应用程序函数调用

这里我先在模拟器上安装了一个Drozer的测试应用程序sieve。使用frida-trace可以跟踪由sieve使用的特定调用:

1
2
3
4
5
6
7
8
9
10
$ frida-trace -i open -U -f com.mwr.example.sieve
Instrumenting functions...
open: Loaded handler at "/Users/fan/__handlers__/libc.so/open.js"
Started tracing 1 function. Press Ctrl+C to stop.
/* TID 0x1284 */
206 ms open()
/* TID 0x129a */
221 ms open()
/* TID 0x1284 */
...

frida-trace会生成一个Javascript文件(第二行输出),然后Frida将其注入到进程中,并hook特定调用(libc.so中的open函数)。修改生成的Javascript文件open.js可以输出open函数的调用参数。

1
2
3
4
5
...
onEnter: function (log, args, state) {
log("open(" + "pathname=" + args[0] + ", flags=" + args[1] + ")");
},
...

再次运行应用程序会输出如下信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
frida-trace -i open -U -f com.mwr.example.sieve
Instrumenting functions...
open: Loaded handler at "/Users/fan/__handlers__/libc.so/open.js"
Started tracing 1 function. Press Ctrl+C to stop.
/* TID 0x1383 */
222 ms open(pathname=0xa7137e65, flags=0x80002)
/* TID 0x1399 */
255 ms open(pathname=0xa662cc2c, flags=0x2)
/* TID 0x139a */
267 ms open(pathname=0xa662cc2c, flags=0x2)
/* TID 0x1383 */
286 ms open(pathname=0xa662c6df, flags=0x2)
...

再对open.js做修改,让其输出打开文件路径的文本形式,而不是存储单元的地址。我们可以直接访问内存,这里可以参考Frida的Javascript API

1
2
3
4
5
...
onEnter: function (log, args, state) {
log("open(" + "pathname=" + Memory.readUtf8String(args[0]) + ", flags=" + args[1] + ")");
},
...

再次运行程序输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ frida-trace -i open -U -f com.mwr.example.sieve
Instrumenting functions...
open: Loaded handler at "/Users/fan/__handlers__/libc.so/open.js"
Started tracing 1 function. Press Ctrl+C to stop.
/* TID 0x1434 */
272 ms open(pathname=/dev/binder, flags=0x80002)
/* TID 0x1449 */
286 ms open(pathname=/dev/ashmem, flags=0x2)
/* TID 0x144a */
296 ms open(pathname=/dev/ashmem, flags=0x2)
/* TID 0x1434 */
300 ms open(pathname=/sys/qemu_trace/process_name, flags=0x2)
428 ms open(pathname=/dev/alarm, flags=0x0)
431 ms open(pathname=/sys/qemu_trace/process_name, flags=0x2)
...

Frida命令行接口frida-cli

启动sieve应用,并将生成的进程的任务留给Frida:

1
2
3
4
5
6
7
8
9
10
11
12
$ frida -U --no-pause -f com.mwr.example.sieve
____
/ _ | Frida 10.0.1 - A world-class dynamic instrumentation framework
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at http://www.frida.re/docs/home/
Spawned `com.mwr.example.sieve`. Resuming main thread!
[USB::Android Emulator 5554::['com.mwr.example.sieve']]->

这样就能得到一个shell,可以使用Frida的Javascrip API写命令了。通过Tab可以查看和补全命令,Frida也提供了详细的API文档。对于Android,查看Javascript APIJava部分。下面详细讨论一个访问Java对象的Javascript包装器,在和Android应用程序交互的时候,这是一种更便捷的方法。不同于hook libc函数,我们可以直接使用Java函数和对象。
列出加载的类:

1
2
3
4
5
6
7
8
9
10
11
12
[USB::Android Emulator 5554::['com.mwr.example.sieve']]-> Java.perform(function(){Java.enumerateLoadedClasses({"onMatch":function(className){ console.log(className) }, "onComplete":function(){}})})
...
com.mwr.example.sieve.AuthServiceConnector$MessageHandler
com.mwr.example.sieve.WelcomeActivity
com.mwr.example.sieve.PWDBHelper
com.mwr.example.sieve.AuthService
com.mwr.example.sieve.DBContentProvider
com.mwr.example.sieve.AuthServiceConnector$ResponseListener
com.mwr.example.sieve.MainLoginActivity
com.mwr.example.sieve.FileBackupProvider
com.mwr.example.sieve.AuthServiceConnector
...

这里输入了一个比较长的命令,是一些嵌套的函数代码。首先,输入的代码必须包装在Java.perform(function(){...})中,这是Frida API硬性要求。
下面是在Java.perform包装器中插入的函数体:

1
2
3
4
5
6
7
8
Java.enumerateLoadedClasses(
{
"onMatch": function(className) {
console.log(className)
},
"onComplete":function() {}
}
)

使用Frida API的Java.enumerateLoadedClasses枚举所有加载的类,并使用console.log将匹配的类输出到控制台。这种回调对象在Frida中是一种非常常见 的模式。你可以提供一个回调对象,形式如下:

1
2
3
4
{
"onMatch":function(arg1, ...){...},
"onComplete":function(){},
}

当Frida找到符合要求的匹配项时,就会使用一个或多个参数调用onMatch;当Frida完成匹配工作时,就会调用onComplete

Frida加载外部脚本

下面通过加载一个外部脚本来覆盖一个函数,首先,将下面的代码保存到一个脚本文件中,例如sieve.js:

1
2
3
4
5
6
7
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来接收android.app.Activity类的包装对象,并访问其onResume函数的implementation属性,来重新实现。在新的函数体中,通过this.onResume调用原来的onResume函数,所以应用程序可以正常执行。
打开sieve应用,通过-l选项来注入这个脚本:

1
$ frida -U -l sieve.js com.mwr.example.sieve

一旦触发onResume,例如切换出sieve再切换回来,就会在shell中输出:

1
[*] onResume() got called!

通过覆盖应用程序中的函数,就可以控制应用程序的行为。

当模拟器速度较慢时,Frida会出现超时。为了防止这种情况,可以将脚本封装到setImmediate中。修改脚本文件后,setImmediate将自动重新运行脚本。setImmediate运行脚本是在后台,所以会立刻得到一个cli,稍等脚本处理完,就会输出结果。

下面利用Java.choose查找堆中已经实例化的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
setImmediate(function() {
console.log("[*] Starting script");
Java.perform(function() {
Java.choose("android.widget.EditText", {
"onMatch":function(instance) {
console.log("[*] Instance found");
},
"onComplete":function() {
console.log("[*] Finished heap search")
}
});
});
});

运行脚本:

1
2
3
4
5
6
7
8
$ frida -U -l sieve2.js com.mwr.example.sieve
...
[*] Starting script
[USB::Android Emulator 5554::com.mwr.example.sieve]-> [*] Instance found
[*] Instance found
[*] Instance found
[*] Instance found
[*] Finished heap search

在堆上找到了四个android.widget.EditText,可以调用这些实例对象方法。这里只是为console.log输出添加instance.toString()。由于使用了setImmediate,所以只要修改脚本,Frida就会重新运行它:

1
2
3
4
5
6
7
8
9
10
11
12
13
setImmediate(function() {
console.log("[*] Starting script");
Java.perform(function() {
Java.choose("android.widget.EditText", {
"onMatch":function(instance) {
console.log("[*] Instance found: " + instance.toString());
},
"onComplete":function() {
console.log("[*] Finished heap search")
}
});
});
});

输出结果:

1
2
3
4
5
6
[*] Starting script
[*] Instance found: android.widget.EditText{78e1fa3 VFED..CL. .F....ID 710,245-1440,383 #7f08002b app:id/welcome_editt}
[*] Instance found: android.widget.EditText{3952a0 VFED..CL. ......ID 710,488-1440,626 #7f08002d app:id/welcome_editte}
[*] Instance found: android.widget.EditText{7551e59 VFED..CL. .F...... 710,245-1440,383 #7f08002b app:id/welcome_editt}
[*] Instance found: android.widget.EditText{533a1e VFED..CL. ........ 710,488-1440,626 #7f08002d app:id/welcome_editte}
[*] Finished heap search

Frida实际上调用了android.widget.EditText对象实例的toString方法。借助Frida,我们可以读取进程内存、修改函数、查找对象实例。Frida的基本使用就是这样,可以通过其官网的Docs深入学习。

使用Frida和Redare2:r2frida

还可以使用Radare2反汇编框架来检查应用程序的内存,方法是通过r2fridaRadare2连接到Frida,然后对进程的内存进行静态分析和反汇编处理。之前简单介绍过Radare2,还是要抽时间再多学习补充一下。
安装Radare2后,可以用Radare2的数据包管理程序来安装r2frida

1
$ r2pm install r2frida

回到前面frida-trace的例子,还是输出内存地址:

1
2
3
4
5
6
7
8
9
10
11
frida-trace -i open -U -f com.mwr.example.sieve
Instrumenting functions...
open: Loaded handler at "/Users/fan/__handlers__/libc.so/open.js"
Started tracing 1 function. Press Ctrl+C to stop.
/* TID 0x18cd */
208 ms open(pathname=0xa7137e65, flags=0x80002)
/* TID 0x18e1 */
232 ms open(pathname=0xa662cc2c, flags=0x2)
/* TID 0x18e2 */
250 ms open(pathname=0xa662cc2c, flags=0x2)
...

使用r2frida显示内存地址的内容并读取路径名:

1
2
3
4
5
6
7
8
9
10
$ r2 frida://emulator-5554/com.mwr.example.sieve
-- Run .dmm* to load the flags of the symbols of all modules loaded in the debugger
[0x00000000]> s 0xa7137e65
[0xa7137e65]> px
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0xa7137e65 2f64 6576 2f62 696e 6465 7200 4269 6e64 /dev/binder.Bind
0xa7137e75 6572 2069 6f63 746c 2074 6f20 6f62 7461 er ioctl to obta
0xa7137e85 696e 2076 6572 7369 6f6e 2066 6169 6c65 in version faile
0xa7137e95 643a 2025 7300 4269 6e64 6572 2064 7269 d: %s.Binder dri
...

reference
http://bobao.360.cn/learning/detail/3641.html