體驗了一把frida,感覺像是發(fā)現(xiàn)了新大陸一樣,不信你繼續(xù)看~
frida跟xposed對比:
xposed:root + xposed環(huán)境 + 寫xposed模塊
frida:root + 寫js腳本
frida只需要設備有root權限,無需刷xposed環(huán)境,就能實現(xiàn)hook功能,而且不需要重啟App,修改js腳本之后直接注入即可。本文將手把手帶你上手frida。
frida github:
https://github.com/frida/frida
一、準備
frida分客戶端環(huán)境和服務端環(huán)境。
在客戶端我們可以編寫js代碼或者Python代碼,連接遠程設備,提交要注入的代碼到遠程,接受服務端的發(fā)來的消息等。
在服務端,我們需要用Javascript代碼注入到目標進程,操作內(nèi)存數(shù)據(jù),給客戶端發(fā)送消息等操作。我們也可以把客戶端理解成控制端,服務端理解成被控端。
假如我們要用PC來對Android設備上的某個進程進行操作,那么PC就是客戶端,而Android設備就是服務端。
1.1 準備frida服務端環(huán)境
根據(jù)自己的平臺下載frida服務端并解壓
https://github.com/frida/frida/releases

解壓,重命名為 frida-server
電腦連接手機,通過adb執(zhí)行以下命令將服務端推到手機的 /data/local/tmp 目錄
adb push frida-server /data/local/tmp/frida-server
執(zhí)行以下命令修改frida-server文件權限
adb shell chmod 777 /data/local/tmp/frida-server
啟動服務
adb shell
cd /data/local/tmp
su
./frida-server

1.2 準備客戶端環(huán)境
我用的是mac,windows同理,
首先需要python環(huán)境,這個自己安裝,然后通過pip安裝frida
命令如下
pip install frida-tools
pip install frida
等待一會兒,測試frida是否安裝成功

1.3 測試
查看進程
frida-ps -U
參數(shù)解釋:
-U 指定對USB設備操作
-l 指定加載一個Javascript腳本
最后指定一個進程名,如果想指定進程pid,用-p選項。正在運行的進程可以用frida-ps -U命令查看

這個報錯是由于服務端沒啟動,前面的啟動服務是不是沒成功,回去看 1.1節(jié)。
然后在另一個控制臺執(zhí)行 frida-ps -U

可以看到手機正在運行的進程pid都列出來了。
接下來將一個腳本 myhook.js 注入到Android目標進程
frida -U -l myhook.js com.lanshifu.demo_module

成功了,這個myhook.js內(nèi)容如下
if(Java.available){
Java.perform(function(){
var MainActivity = Java.use("com.lanshifu.demo_module.ui.activity.DemoMainActivity");
MainActivity.testCrash.overload("int").implementation=function(chinese,math){
console.log("[javascript] testCrash method be called.");
send("hook testCrash method success>>>");
return this.testCrash(12345678);
}
});
}
攔截DemoMainActivity 的 testCrash(int)方法,調(diào)用的時候打印信息并且將入?yún)⒏臑?2345678。日志打印出來了,界面上看也成功了,就不上圖了,下面會詳細介紹hook的各種操作。
1.4 控制腳本命令
frida運行過程中,執(zhí)行%resume重新注入,執(zhí)行%reload來重新加載腳本;執(zhí)行exit結束腳本注入

一般情況下,修改js腳本,然后輸入exit結束腳本注入,再重新輸入注入命令即可
二、Hook 實戰(zhàn)
2.1 載入類
Java.use方法用于聲明一個Java類,在用一個Java類之前首先得聲明。比如聲明一個String類,要指定完整的類名:
var StringClass=Java.use("java.lang.String");
2.2 參數(shù)
修改一個函數(shù)的實現(xiàn)是逆向調(diào)試中相當有用的。修改一個函數(shù)的實現(xiàn)后,如果這個函數(shù)被調(diào)用,我們的Javascript代碼里的函數(shù)實現(xiàn)也會被調(diào)用。
參數(shù)類型表示(跟JNI里面的方法簽名基本一樣)
對于基本類型,直接用它在Java中的表示方法就可以了,不用改變,例如:
int
short
char
byte
boolean
float
double
long基本類型數(shù)組,用左中括號接上基本類型的縮寫
基本類型 縮寫:
boolean: Z
byte: B
char: C
double: D
float: F
int: I
long: J
short: S
例如:int[]類型,在重載時要寫成[I
- 任意類,直接寫完整類名即可
例如:java.lang.String
- 對象數(shù)組,用左中括號接上完整類名再接上分號
例如:[java.lang.String;
2.3 hook 無參構造函數(shù)
//聲明一個Java類
var MyClass = Java.use("com.lanshifu.demo_module.ui.activity.DemoHookTestActivity$MyClass");
//hook 無參構造
MyClass.$init.overload().implementation=function(){
//調(diào)用原來構造
this.$init();
send("hook 無參構造 ");
}
2.3 hook 有參構造函數(shù)
var MyClass = Java.use("com.lanshifu.demo_module.ui.activity.DemoHookTestActivity$MyClass");
//hook 有參構造,入?yún)⑹?int[]
MyClass.$init.overload('[I').implementation=function(param){
send("hook 有參構造 init(int[] i) method ");
}
2.4 hook 多參數(shù)的構造函數(shù)的實現(xiàn)
ClassName.$init.overload('[B','int','int').implementation=function(param1,param2,param3){
//do something
}
注意:當構造函數(shù)(函數(shù))有多種重載形式,比如一個類中有兩個形式的func:void func()和void func(int),要加上overload來對函數(shù)進行重載,否則可以省略overload
2.5 hook 一般函數(shù)
//void函數(shù)
MyClass.method1.overload().implementation=function(){
send("hook method1 ");
}
//有入?yún)⒌暮瘮?shù)
MyClass.method2.overload("int","int").implementation=function(param1,param2){
send("hook method2 ");
this.method2(100,100)
}
2.6 調(diào)用函數(shù)
和Java一樣,創(chuàng)建類實例就是調(diào)用構造函數(shù),而在這里用$new表示一個構造函數(shù)。
//實例化一個類并調(diào)用它的method3方法
var ClassName=Java.use("com.lanshifu.demo_module.ui.activity.DemoHookTestActivity$MyClass");
var instance = ClassName.$new();
instance.method3(1,2,3);
2.7 類型轉換
用Java.cast方法來對一個對象進行類型轉換,如將variable轉換成java.lang.String:
var StringClass=Java.use("java.lang.String");
var NewTypeClass=Java.cast(variable,StringClass);
2.8 Java.perform方法
Java.perform(fn)在Javascript代碼成功被附加到目標進程時調(diào)用,我們核心的代碼要在里面寫。格式:
Java.perform(function(){
//do something...
});
三、配置python腳本注入
3.1 創(chuàng)建 FridaDemo.py
我直接在Pycharm 里面創(chuàng)建了 FridaDemo.py,然后代碼如下
# -*- coding: UTF-8 -*-
import frida, sys
jscode = """
if(Java.available){
Java.perform(function(){
var MainActivity = Java.use("com.lanshifu.demo_module.ui.activity.DemoMainActivity");
MainActivity.testCrash.overload("int").implementation=function(chinese,math){
console.log("[javascript] testCrash method be called.");
send("hook testCrash method success>>>");
return this.testCrash(12345678);
}
//聲明一個Java類
var MyClass = Java.use("com.lanshifu.demo_module.ui.activity.DemoHookTestActivity$MyClass");
//hook 無參構造
MyClass.$init.overload().implementation=function(){
//調(diào)用原來構造
this.$init();
send("hook 無參構造 ");
}
//hook 有參構造,入?yún)⑹?int[]
MyClass.$init.overload('[I').implementation=function(param){
send("hook 有參構造 init(int[] i) method ");
}
//hook 多個參數(shù)構造,入?yún)⑹?int,int
MyClass.$init.overload("int","int").implementation=function(param1,param2){
send("hook success ");
send(param1);
send(param2);
//修改返回值
return 100;
}
//void函數(shù)
MyClass.method1.overload().implementation=function(){
send("hook method1 ");
this.method1();
}
//有入?yún)⒌暮瘮?shù)
MyClass.method2.overload("int","int").implementation=function(param1,param2){
send("hook method2 ");
//實例化一個類并調(diào)用它的method3方法
var ClassName=Java.use("com.lanshifu.demo_module.ui.activity.DemoHookTestActivity$MyClass");
var instance = ClassName.$new();
instance.method3(1,2,3);
this.method2(100,100)
}
//
MyClass.method3.overload("int","int","int").implementation=function(param1,param2,param3){
send("hook method3 ");
this.method3(100,100,100)
}
});
}
"""
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
pass
# 查找USB設備并附加到目標進程
session = frida.get_usb_device().attach('com.lanshifu.demo_module')
# 在目標進程里創(chuàng)建腳本
script = session.create_script(jscode)
# 注冊消息回調(diào)
script.on('message', on_message)
print('[*] Start attach')
# 加載創(chuàng)建好的javascript腳本
script.load()
# 讀取系統(tǒng)輸入
sys.stdin.read()
執(zhí)行這個python腳本,手機打開app執(zhí)行到該方法,看到打印了日志

ctrl + c停止腳本,看起來方便一些。
接下來分析一下步驟:
- 通過調(diào)用f
rida.get_usb_device()方法來得到一個連接中的USB設備(Device類)實例 - 調(diào)用
attach附加到目標進程 - 調(diào)用Session類的
create_script()方法創(chuàng)建一個腳本,傳入需要注入的javascript代碼并得到Script類實例 - 調(diào)用Script類的
on()方法添加一個消息回調(diào),第一個參數(shù)是信號名,乖乖傳入message就行,第二個是回調(diào)函數(shù) - 最后調(diào)用Script類的
load()方法來加載剛才創(chuàng)建的腳本。
四、總結
- 手機配置frida 服務端環(huán)境
- 電腦配置frida 客戶端環(huán)境
- 編寫js腳本
- 通過adb啟動服務,
adb shell 'su -c /data/local/tmp/frida-server' - 電腦執(zhí)行腳本:
frida -U -l myhook.js com.lanshifu.demo_module - 在命令行輸入exit,回車,停止注入代碼
體驗了一把frida之后, 發(fā)現(xiàn)一個很明顯的優(yōu)勢,就是不需要寫xposed模塊,直接一個js腳本,就能馬上測試hook點,不需要重啟應用。
手機frida服務端的配置踩了一個坑,下載錯了,我的6.0 手機是arm架構的,我下載了arm64的,導致注入js之后手機崩潰。
本文參考:
http://www.itdecent.cn/p/f98aca8f3c05
https://www.frida.re/docs/examples/android/
如有雷同,純屬copy。
我的簡書主頁:
藍師傅_Android