FRIDA
一 简介
Frida是一款基于python + javascript 的hook框架,适用于android/ios/linux/win/osx等平台。Frida的动态代码执行功能,主要是在它的核心引擎Gum中用C语言来实现的。
注入模式:大部分情况下,我们都是附加到一个已经运行到进程,或者是在程序启动到时候进行劫持,然后再在目标进程中运行我们的代码逻辑。这种方式是Frida最常用的使用方式。注入模式的大致实现思路是这样的,带有GumJS的Frida核心引擎被打包成一个动态连接库,然后把这个动态连接库注入到目标进程中,同时提供了一个双向通信通道,这样控制端就可以和注入的模块进行通信了,在不需要的时候,还可以在目标进程中把这个注入的模块给卸载掉。
嵌入模式:针对没root过的设备Frida提供了一个动态连接库组件 frida-gadget, 可以把这个动态库集成到程序里面来使用Frida的动态执行功能。一旦集成了gadget,就可以和程序使用Frida进行交互。
预加载模式:自动加载Js文件。
1.下载FRIDA
pip install frida
2.下载frida-server
frida-server 是一个守护进程,通过TCP和Frida核心引擎通信,默认的监听端口是27042
地址: https://github.com/frida/frida/releases
注意:版本和类型对应,框架和设备对应
3.连接设备并确认连接
adb devices
4.将frida-server压缩包解压并push到设备中
adb push /Users/用户名/Desktop/frida-server-10.7.7-android-arm data/local/tmp/frida-server
5.运行frida-server
$adb shell
$su
$cd /data/local/tmp
$chmod 755 frida-server
$./frida-server
6.检查是否正常
转发android TCP端口到本地
adb forward tcp:27042 tcp:27043
adb forward tcp:27043 tcp:27043
Frida-ps -U
-U 代表着 USB,并且让 Frida 检查 USB-Device真机,出现图上这样就是成功了。
三 常用API
1.JAVA
(1)Java.perform(fn)
frida的main,注意:所有的脚本必须放在这里面,示例:
Java.perform(function () {
var Activity = Java.use(“android.app.Activity”);//获得类包相当于js的new()
Activity.onResume.implementation = function () {//改变onResume函数
send("onResume() got called! Let's call the original implementation”);
this.onResume();
};
});
(2)Java.use(className)
动态获取一个类的对象,为以后改变对象方法的实现,或者用$new()实例化对象, $dispose()销毁对象,示例:
Java.perform(function () {
var Activity = Java.use("android.app.Activity");
var Exception = Java.use("java.lang.Exception");
Activity.onResume.implementation = function () {
throw Exception.$new(“Hello World!");
};
});
(3)Java.enumerateLoadedClasses(callbacks)
列出当前已经加载的类,用回调函数处理
回调函数onMatch:function(className){ }
找到加载的每个类的时候被调用,参数就是类的名字,这个参数可以传给java.use()来获得一个js类包
回调函数onComplete: function ()
列出所有类之后被调用
2.JavaScript
(1)Interceptor.attach(target, callbacks)
在target指定的位置进行函数调用拦截,target是一个NativePointer参数,用来指定你想要拦截的函数的地址。callbacks参数是一个对象:
onEnter: function(args): 被拦截函数调用之前回调,其中原始函数的 参数使用args数组(NativePointer对象数组)来表示,可以在这里修改函数的调用参数。
onLeave: function(retval): 被拦截函数调用之后回调,其中retval表示原始函数的返回值,retval是从NativePointer继承来的,是对原始返回值的一个封装,可以使用retval.replace(1337)调用来修改返回值的内容。注意:retval对象只在 onLeave函数作用域范围内有效,因此如果你要保存这个对象以备后续使用的话,一定要使用深拷贝来保存对象,比如:ptr(retval.toString())
示例:
Interceptor.attach(Module.findExportByName(“libc.so”,”read”),{
onEnter:function(args){
this.fileDescriptor = args[0].toInt32();
},
onLeave:function(retval){
if(retval.toInt32( ) > 0){
/* do something with this.fileDescriptor */
}
}
});
此外,this 对象还包含了一些额外的比较有用的属性:
returnAddress: 返回NativePointer类型的 address 对象
context: 包含 pc,sp,以及相关寄存器比如 eax, ebx等,可以在回调函数中直接修改
errno: (UNIX)当前线程的错误值
lastError: (Windows) 当前线程的错误值
threadId: 操作系统线程Id
depth: 函数调用层次深度
(2)Interceptor.detachAll()
取消之前所有的拦截调用
(3)Interceptor.replace(target, replacement):
函数实现代码替换,注意:这种情况主要用于想要完全替换掉一个原有函数的实现,replacement参数使用JavaScript形式的一个NativeCallback来实现,后续如果想要取消这个替换效果,可以使用 Interceptor.revert调用来实现
四 工具
(1)frida-ps
用于枚举进程
--version show program's version number and exit
-h, --help show this help message and exit
-D ID, --device=ID connect to device with the given ID
-U, --usb connect to USB device
-R, --remote connect to remote frida-server
-H HOST, --host=HOST connect to remote frida-server on HOST
-a, --applications list only applications
-i, --installed include all installed applications
(2)frida-trace
动态跟踪
--version show program's version number and exit
-h, --help show this help message and exit
-D ID, --device=ID connect to device with the given ID
-U, --usb connect to USB device
-R, --remote connect to remote frida-server
-H HOST, --host=HOST connect to remote frida-server on HOST
-f FILE, --file=FILE spawn FILE
-n NAME, --attach-name=NAME attach to NAME
-p PID, --attach-pid=PID attach to PID
--debug enable the Node.js compatible script debugger
--disable-jit disable JIT
-I MODULE, --include-module=MODULE include MODULE
-X MODULE, --exclude-module=MODULE exclude MODULE
-i FUNCTION, --include=FUNCTION include FUNCTION
-x FUNCTION, --exclude=FUNCTION exclude FUNCTION
-a MODULE!OFFSET, --add=MODULE!OFFSET add MODULE!OFFSET
-T, --include-imports include program's imports
-t MODULE, --include-module-imports=MODULE include MODULE imports
-m OBJC_METHOD, --include-objc-method=OBJC_METHOD include OBJC_METHOD
示例:
启动手机中的Chrome浏览器
frida-trace -i "open" -U com.android.chrome
可以看到终端中出现:
open:Loaded handler at :”/用户名/__handlers__/libc.so/open.js”
frida-trace会生成一个javascript文件,然后Frida会将其注入到进程中,并跟踪特定的调用。生成的open.js脚本将钩住libc.so中的open函数并输出参数.
open.js:
/*
* Auto-generated by Frida. Please modify to match the signature of open.
* This stub is currently auto-generated from manpages when available.
*
* For full API reference, see: http://www.frida.re/docs/javascript-api/
*/
{
/**
* Called synchronously when about to call open.
*
* @this {object} - Object allowing you to store state for use in onLeave.
* @param {function} log - Call this function with a string to be presented to the user.
* @param {array} args - Function arguments represented as an array of NativePointer objects.
* For example use Memory.readUtf8String(args[0]) if the first argument is a pointer to a C string encoded as UTF-8.
* It is also possible to modify arguments by assigning a NativePointer object to an element of this array.
* @param {object} state - Object allowing you to keep state across function calls.
* Only one JavaScript function will execute at a time, so do not worry about race-conditions.
* However, do not use this to store function arguments across onEnter/ onLeave, but instead
* use "this" which is an object for keeping state local to an invocation.
*/
onEnter: function (log, args, state) {
log("open(" +
"path=\"" + Memory.readUtf8String(args[0]) + "\"" +
", oflag=" + args[1] +
")");
},
/**
* Called synchronously when about to return from open.
*
* See onEnter for details.
*
* @this {object} - Object allowing you to access state stored in onEnter.
* @param {function} log - Call this function with a string to be presented to the user.
* @param {NativePointer} retval - Return value represented as a NativePointer object.
* @param {object} state - Object allowing you to keep state across function calls.
*/
onLeave: function (log, retval, state) {
}
}
(3)Python绑定
示例:
从Python注入chrome.js脚本
注意:从命令行加载脚本然后生成一个命令的进程时,Frida容易崩溃。所以先生成进程,再让Frida注入脚本
#!/usr/bin/python
import frida
# js
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 = process.create_script(jscode)
# and load the script
script.load()
五 使用
(1)挂钩libc.so中的函数
frida-trace -i "xxx" -U com.android.xxxxx
启动FRIDA和应用,在应用程序启动主进程之前注入FRIDA代码。—no-pause不会中断应用程序,并将生成的进程的任务留给FRIDA,这会打开一个shell,在里边使用Javascript API向Frida写命令
frida -U -f com.android.xxxxx —no-pause
例如:重写onResume函数,如图,一旦调用onResume()就会得到如下结果。
(2)Hook函数
FRIDA可以动态改变Android应用的行为,比如可以绕过检测Android设备是否处于root状态的函数。对于在ART(Android Runtime,Android运行时)环境中运行的应用来说,可以使用Java.perform来hook函数。
需要工具:
dex2jar:将apk使用压缩工具打开提出classes.dex放入dex2jar文件夹中,为d2j_invoke.sh和d2j-dex2jar.sh增加执行权限 ,执行sh d2j-dex2jar.sh classes.dex得到classes-dex2jar.jar。
JD-GUI:阅读classes-dex2jar.jar
示例:
JavaScript
setImmediate(function() { //包裹在setImmediate中以防超时
Java.perform(function(){ //所有脚本必须在这里面
classObject = Java.use("com.android.MainActivity");
classObject .targetFunction.implementation = function(v){
send("argetFunction hooked");
} });
});
(3)Hook native函数
使用Frida中的Interceptor函数,深入到设备的底层内存中,hook特定的库或者内存地址
可能需要工具:
Radare2(或者其他的反汇编工具):可以使用r2frida将Radare2连接到Frida,然后对进程的内存进行静态分析和反汇编处理。使用Radare2的数据包管理程序来安装r2frida,注意先安装Radare2。
r2pm install r2frida
访问进程以及让r2frida执行注入操作的语法如下所示:
r2 frida://DEVICE-ID/PROCESS
示例:
JavaScript
setImmediate(function() {
Java.perform(function() {
var targetFunction = undefined;
imports = Module.enumerateImportsSync("libfoo.so");//通过导入函数表 获取 targetFunction函数的地址
for(i = 0; i < imports.length; i++) {
if(imports[i].name == "targetFunction") {
targetFunction = imports[i].address;
break;
}
}
Interceptor.attach(targetFunction, {
onEnter: function (args) {
//do something
},
});
console.log("[*] Intercepting targetFunction”);
});
});
六 嵌入模式-unroot设备
需要的工具:
apktool下载地址:https://bitbucket.org/iBotPeaches/apktool/downloads/
1.下载frida-Gadget
2.解码app
apktool d targetapp.apk -o targetappFolder
3.将对应的frida-Gadget复制到targetappFolder/lib/或者targetappFolder/lib/armeabi下,注意版本对应。
4.打开manifest.xml找到MainActivity,因为我们希望在执行任何其他代码之前注入frida-Gadget,所以将以下smali代码复制到MainActivity的onCreate()方法中
const-string v0, "frida-gadget"
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
5.如果manifest.xml没有Internet permission需要进行添加
6.将apk重新打包
apktool b -o repackaged.apk targetappFolder/
7.签名
创建keystore(如果没有的话)
keytool -genkey -v -keystore custom.keystore -alias mykeyaliasname - keyalg RSA -keysize 2048 -validity 10000
签名
jarsigner -sigalg SHA256withRSA -digestalg SHA1 -keystore custom.keystore -storepass 你的密码 repackaged.apk mykeyaliasname
验证
jarsigner -verify repackaged.apk
zipalign(在sdk的build-tools里面,注意路径)
zipalign 4 repackaged.apk final.apk
8.安装apk
adb install final.apk
七 frida-Gadget使用
经过第六节重打包的apk在启动时会进入等待页面,这时使用frida-ps -U只显示Gadget一个进程,使用frida -U Gadget进行连接(依旧可以使用frida-trace等工具)。
我们可以通过%load 脚本.js进行操作(脚本依旧要包裹在Java.perform()中)。
八 目前存在的一些检测FRIDA的方法
(1)方法:frida-server通过 TCP 对外与 frida 通信, Java 遍历运行的进程列表能够检查到 frida-server 是否在运行
措施:重命名 frida-server
(2)方法:frida-server 默认的TCP 端口是 27047,检查端口是否开放
措施:命令行指定参数改变frida-server的监听端口
(3)方法:frida-server 使用 D-Bus 协议通信,为每个开放的端口发送 D-Bus 的认证消息,回复的端口是 frida-server
措施:Frida 提供了不需要 frida-server 运行的模式
(4)方法:利用frida 运行时映射到内存的库,直接逐一检查加载的库。在内存中扫描 frida 的库特征 “gadgets”。例如:字符串 “LIBFRIDA”,它在所有 frida-gadget 和 frida-agent 的版本中都有出现
措施:打补丁,对修改的apk进行重建和签名(例如:apktool)