FRIDA脚本系列(一)入门篇:在安卓8.1上dump蓝牙接口和实例
0x01.FRIDA是啥?为啥这么火?
frida目前非常火爆,该框架从Java层hook到Native层hook无所不能,虽然持久化还是要依靠Xposed和hookzz等开发框架,但是frida的动态和灵活对逆向以及自动化逆向的帮助非常巨大。
frida是啥呢,github目录Awesome Frida这样介绍frida的:
Frida is Greasemonkey for native apps, or, put in more technical terms, it’s a dynamic code instrumentation toolkit. It lets you inject snippets of JavaScript into native apps that run on Windows, Mac, Linux, iOS and Android. Frida is an open source software.
frida是平台原生app的Greasemonkey,说的专业一点,就是一种动态插桩工具,可以插入一些代码到原生app的内存空间去,(动态地监视和修改其行为),这些原生平台可以是Win、Mac、Linux、Android或者iOS。而且frida还是开源的。
Greasemonkey可能大家不明白,它其实就是firefox的一套插件体系,使用它编写的脚本可以直接改变firefox对网页的编排方式,实现想要的任何功能。而且这套插件还是外挂的,非常灵活机动。
frida也是一样的道理。那它为什么这么火爆呢?
动静态修改内存实现作弊一直是刚需,比如金山游侠,本质上frida做的跟它是一件事情。原则上是可以用frida把金山游侠,包括CheatEngine等“外挂”做出来的。
当然,现在已经不是直接修改内存就可以高枕无忧的年代了。大家也不要这样做,做外挂可是违法行为。
在逆向的工作上也是一样的道理,使用frida可以“看到”平时看不到的东西。出于编译型语言的特性,机器码在CPU和内存上执行的过程中,其内部数据的交互和跳转,对用户来讲是看不见的。当然如果手上有源码,甚至哪怕有带调试符号的可执行文件包,也可以使用gbd、lldb等调试器连上去看。
那如果没有呢?如果是纯黑盒呢?又要对app进行逆向和动态调试、甚至自动化分析以及规模化收集信息的话,我们需要的是细粒度的流程控制和代码级的可定制体系,以及不断对调试进行动态纠正和可编程调试的框架,这就是frida。
frida使用的是python、JavaScript等“胶水语言”也是它火爆的一个原因,可以迅速将逆向过程自动化,以及整合到现有的架构和体系中去,为你们发布“威胁情报”、“数据平台”甚至“AI风控”等产品打好基础。
官宣屁屁踢甚至将其敏捷开发和迅速适配到现有架构的能力作为其核心卖点。
0x02.FRIDA脚本的概念
FRIDA脚本就是利用FRIDA动态插桩框架,使用FRIDA导出的API和方法,对内存空间里的对象方法进行监视、修改或者替换的一段代码。FRIDA的API是使用JavaScript实现的,所以我们可以充分利用JS的匿名函数的优势、以及大量的hook和回调函数的API。
我们来举个最直观的例子:hello-world.js
setTimeout(function(){
Java.perform(function(){
console.log("hello world!");
});
});
这基本上就是一个FRIDA版本的”Hello World!“,我们把一个匿名函数作为参数传给了setTimeout()函数,然而函数体中的Java.perform()这个函数本身又接受了一个匿名函数作为参数,该匿名函数中最终会调用console.log()函数来打印一个“Hello world!”字符串。我们需要调用setTimeout()方法因为该方法将我们的函数注册到JavaScript运行时中去,然后需要调用Java.perform()方法将函数注册到Frida的Java运行时中去,用来执行函数中的操作,当然这里只是打了一条log。
然后我们在手机上将frida-server运行起来,在电脑上进行操作:
$ frida -U -l hello-world.js android.process.media
然后可以看到console.log()执行成功,字符串打印了出来。
0x03.简单脚本一:枚举所有的类
我们现在来给这个HelloWorld.js稍微加一点功能,比如说枚举所有已经加载的类,这就用到了Java对象的enumerateLoadedClasses方法。代码如下:
setTimeout(function (){
Java.perform(function (){
console.log("\n[*] enumerating classes...");
Java.enumerateLoadedClasses({
onMatch: function(_className){
console.log("[*] found instance of '"+_className+"'");
},
onComplete: function(){
console.log("[*] class enuemration complete");
}
});
});
});
首先还是确保手机上的frida-server正在运行中,然后在电脑上操作:
$ frida -U -l enumerate_classes.js android.process.media
0x04.简单脚本二:定位目标类并打印类的实例
现在我们已经找到目标进程中所有已经加载的类,比如说现在我们的目标是要查看其蓝牙相关的类,我们可以把代码修改成这样:
Java.enumerateLoadedClasses({
onMatch: function(instance){
if (instance.split(".")[1] == "bluetooth"){
console.log("[->]\t"+instance);
}
},
onComplete: function() {
console.log("[*] class enuemration complete");
}
});
我们来看下效果:
可以找到上述这么多蓝牙相关的类。当然也可以使用字符串包含的方法,使用JavaScript字符串的indexOf()、search()或者match()方法,这个留给读者自己完成。
定位到我们想要研究的类之后,就可以打印类的实例了,查看FRIDA的API手册可以得知,此时应该使用Java.choose()函数,来选定某一个实例。
我们增加下列几行选定android.bluetooth.BluetoothDevice类的实例的代码。
Java.choose("android.bluetooth.BluetoothDevice",{
onMatch: function (instance){
console.log("[*] "+" android.bluetooth.BluetoothDevice instance found"+" :=> '"+instance+"'");
bluetoothDeviceInfo(instance);
},
onComplete: function() { console.log("[*] -----");}
});
在手机打开蓝牙,并且连接上我的漫步者蓝牙耳机,开始播放内容之后:
在电脑上运行脚本:
$ frida -U -l enumerate_classes_bluetooth_choose.js com.android.bluetooth
可以看到正确检测到了我的蓝牙设备:
0x05.简单脚本三:枚举所有方法并定位方法
上文已经将类以及实例枚举出来,接下来我们来枚举所有方法,主要使用了Java.use()函数。
Java.use()与Java.choose()最大的区别,就是在于前者会新建一个对象,后者会选择内存中已有的实例。
对代码的增加如下:
function enumMethods(targetClass)
{
var hook = Java.use(targetClass);
var ownMethods = hook.class.getDeclaredMethods();
hook.$dispose;
return ownMethods;
}
...
...
var a = enumMethods("android.bluetooth.BluetoothDevice")
a.forEach(function(s) {
console.log(s);
});
保持上一小节环境的情况下,在电脑上进行操作:
$ frida -U -l enumerate_classes_bluetooth_choose_allmethod.js com.android.bluetooth
最终效果如下,类的所有方法均被打印了出来。
接下来如何“滥用”这些方法,拦截、修改参数、修改结果、等等,皆可悉听尊便,具体流程请参考
0x06.综合案例:在安卓8.1上dump蓝牙接口和实例:
一个比较好的综合案例,就是作为上文案例的dump蓝牙信息的“加强版”——‘BlueCrawl’。
VERSION="1.0.0"
setTimeout(function(){
Java.perform(function(){
Java.enumerateLoadedClasses({
onMatch: function(instance){
if (instance.split(".")[1] == "bluetooth"){
console.log("[->]\t"+lightBlueCursor()+instance+closeCursor());
}
},
onComplete: function() {}
});
Java.choose("android.bluetooth.BluetoothGattServer",{
onMatch: function (instance){
...
onComplete: function() { console.log("[*] -----");}
});
Java.choose("android.bluetooth.BluetoothGattService",{
onMatch: function (instance){
...
onComplete: function() { console.log("[*] -----");}
});
Java.choose("android.bluetooth.BluetoothSocket",{
onMatch: function (instance){
...
onComplete: function() { console.log("[*] -----");}
});
Java.choose("android.bluetooth.BluetoothServerSocket",{
onMatch: function (instance){
...
onComplete: function() { console.log("[*] -----");}
});
Java.choose("android.bluetooth.BluetoothDevice",{
onMatch: function (instance){
...
onComplete: function() { console.log("[*] -----");}
});
});
},0);
该脚本首先枚举了很多蓝牙相关的类,然后choose了很多类,包括蓝牙接口信息以及蓝牙服务接口对象等,还加载了内存中已经分配好的蓝牙设备对象,也就是上文我们已经演示的信息。我们可以用这个脚本来“查看”App加载了哪些蓝牙的接口,App是否正在查找蓝牙设备、或者是否窃取蓝牙设备信息等。
在电脑上运行命令:
$ frida -U -l bluecrawl-1.0.0.js com.android.bluetooth
点击关注,共同学习!
[安全狗的自我修养](https://mp.weixin.qq.com/s/E6Kp0fd7_I3VY5dOGtlD4w)
[github haidragon](https://github.com/haidragon)
https://github.com/haidragon