0x01 簡介
frida 是一款基于 python+javascript 的 hook 框架屏轰,可運行在 android、ios憋飞、linux霎苗、win等各個平臺,主要使用的動態(tài)二進制插樁技術榛做。
0x02 插樁技術
是指將額外的代碼注入程序中以收集運行時的信息唁盏,可分為源代碼插樁 SCI 和二進制插樁 BI。
-
源代碼插樁 SCI
Source Code Instrumentation检眯,額外代碼注入到程序源代碼中厘擂。
-
二進制插樁 BI
Binary Instrumentation,額外代碼注入到二進制可執(zhí)行文件中锰瘸。
靜態(tài)二進制插樁 SBI刽严,Static Binary Instrumentation,在程序執(zhí)行前插入額外的代碼和數(shù)據避凝,生成一個永久改變的可執(zhí)行文件舞萄。
動態(tài)二進制插樁 DBI
,Dynamic Binary Instrumentation管削,在程序運行時實時插入額外代碼和數(shù)據倒脓,對可執(zhí)行文件沒有任何永久改變。
0x03 Frida安裝
- PC 端
python 安裝
編譯安裝 - Android 端
已 root 設備
非 root 設備
一含思、PC端
安裝 frida CLI(command-line interface崎弃,命令行界面)
python 安裝
pip3 install frida
pip 安裝 frida CLI,pip install frida
含潘,過程遇到Running setup.py bdist_wheel for frida ...\
較慢饲做,請耐心等候
源碼編譯安裝
git clone git://github.com/frida/frida.git
cd frida
make
注意安裝版本,系列工具版本號要一致
查看 frida 版本號
frida --version
12.2.27
frida 共有6個工具:firda CLI调鬓,frida-ps艇炎,frida-trace,frida-discover腾窝,frida-ls-devices缀踪,frida-kill
二、Android 端
電腦 USB 連接安卓手機虹脯,針對設備是否 root 采用不同的方式
1. 已 root 設備
已經 root 的設備采用安裝 frida-server 的方式
1. 查看手機型號驴娃,下載系統(tǒng)對應版本的 frida-server
adb shell getprop ro.product.cpu.abi
例如型號為 arm64-v8a
,則下載 frida-server-12.2.27-android-arm64.xz
2. 將其解壓縮生成 frida-server-12.2.27-android-arm64 文件
3. 將解壓之后的文件push 到設備中循集,指定到 /data/local/tmp 路徑下重命名為 frida-server
adb push frida-server-12.2.27-android-arm64 /data/local/tmp/frida-server
4. 運行Android 設備中的 frida-server
adb shell
su
cd /data/local/tmp
chmod 755 frida-server
./frida-server
執(zhí)行完畢后為運行狀態(tài)唇敞。
保留此窗口 shell,以保證服務運行咒彤,關閉該shell 或者停止ctrl+c 則服務關閉疆柔。接下來的操作可另起shell 或該步驟命令另起 shell 執(zhí)行。
5. 進行端口轉發(fā)監(jiān)聽
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
27042 用于與frida-server通信的默認端口號,之后的每個端口對應每個注入的進程镶柱,檢查27042端口可檢測 Frida 是否存在旷档。
6. 檢查是否成功
執(zhí)行frida-ps -U
命令成功輸出進程列表,如下所示
$> frida-ps -U
PID Name
----- ----------------------------------------
4275 com.android.keyguard
4629 com.android.phone
5182 com.android.contacts:cacheservice
...
執(zhí)行frida -U -f com.xxx.xxx
進行連接歇拆,選擇一個進程鞋屈,等待一段時間則進入該應用
$> frida -U -f com.xxx.xx
____
/ _ | Frida 12.2.27 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | 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.xxx.xxx`. Use %resume to let the main thread start executing!
2. 非 root 設備
沒有 root 的設備采用安裝 frida-gadget 的方式,需要對目標應用 apk 進行反編譯注入和調用
手動注入frida-gadget
1. 反編譯 apk
apktool d target_app.apk -o target_app_floder
反編譯之后生成 target_app_floder 文件夾
2. 下載系統(tǒng)對應版本的 frida-gadget故觅,解壓并放到指定位置
下載之后將其進行解壓厂庇,然后放到target_app_floder//lib/armeabi/libfrida-gadget.so
,注意修改名字以 lib
開頭 .so
結尾输吏,對應下一步的代碼中的frida-gadger
注:本人手機是 arm64-v8a
权旷,所以下載 frida-gadget-12.2.27-android-arm64.so.xz,但最后回編譯打包之后贯溅,運行總是奔潰炼杖,不斷的嘗試之后才發(fā)現(xiàn)使用 frida-gadget-12.2.27-android-arm.so.xz 可以正常運行
3. 代碼中加載上一步so 文件,建議在應用的入口文件中執(zhí)行
根據 AndroidManifest.xml 文件找到程序的入口文件盗迟,例如 MainActivity坤邪,在反編譯生成的代碼 smali 中的 onCreate 方法中注入如下代碼
const-string v0, "frida-gadget"
invoke-static {v0}, Ljava/lang/System;>loadLibrary(Ljava/lang/String;)V
4. 檢查AndroidManifest.xml清單文件的網絡權限
<uses-permission android:name="android.permission.INTERNET" />
忌重復添加,會導致回編譯包出錯
5. 回編譯 apk
a. 重新打包
apktool b -o repackage.apk target_app_floder
b. 創(chuàng)建簽名文件罚缕,有的話可忽略此步驟
keytool -genkey -v -keystore mykey.keystore -alias mykeyaliasname -keyalg RSA -keysize 2048 -validity 10000
c. 簽名艇纺,以下任選其一
jarsigner 方式
jarsigner -sigalg SHA256withRSA -digestalg SHA1 -keystore mykey.keystore -storepass 你的密碼 repackaged.apk mykeyaliasname
apksigner 方式
apksigner sign --ks mykey.keystore --ks-key-alias mykeyaliasname repackaged.apk
如需要禁用 v2簽名 添加選項--v2-signing-enabled false
d. 驗證,以下任選其一
jarsigner方式
jarsigner -verify repackaged.apk
apksigner 方式
apksigner verify -v --print-certs repackaged.apk
keytool方式
keytool -printcert -jarfile repackaged.apk
e. 對齊
4字節(jié)對齊優(yōu)化
zipalign -v 4 repackaged.apk final.apk
檢查是否對齊
zipalign -c -v 4 final.apk
注:zipalign可以在V1簽名后執(zhí)行邮弹,但zipalign不能在V2簽名后執(zhí)行,只能在V2簽名之前執(zhí)行
6. 安裝 apk
adb install final.apk
7. 檢查是否成功
打開運行 final.apk黔衡,在注入代碼位置進入停止等待頁面
執(zhí)行frida-ps -U
命令,只顯示Gadget一個進程如下:
$> frida-ps -U
PID Name
----- ------
16251 Gadget
執(zhí)行frida -U gadget
命令進行連接
$> frida -U gadget
____
/ _ | Frida 12.2.27 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at http://www.frida.re/docs/home/
[HUAWEI ALE-UL00::gadget]->
使用objection自動完成frida gadget注入到apk中
pip3 install -U objection
objection patchapk -s target_app.apk
如果包里有錯誤會生成失敗
網上另一種非 root 方式
未實踐
https://bbs.pediy.com/thread-229970.htm
至此環(huán)境配置完成腌乡,接下來使用
使用方式說明
命令行方式
在命令行使用 API 進行操作
$> frida -U --no-pause -f com.android.chrome
____
/ _ | Frida 12.2.27 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | 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.android.chrome`. Resuming main thread!
[HUAWEI ALE-UL00::com.android.chrome]-> Java
{
"androidVersion": "5.0",
"available": true
}
[HUAWEI ALE-UL00::com.android.chrome]-> Java.perform(function(){Java.enumerateLo
adedClasses({"onMatch":function(className){ console.log(className) },"onComplete
":function(){}})})
android.app.NativeActivity
java.security.cert.CertificateExpiredException
android.util.EventLog
android.os.storage.StorageVolume$1
android.system.StructStat
com.android.org.conscrypt.AbstractSessionContext
sun.misc.Unsafe
....
frida-ps 命令:
eg: frida-ps -U
frida-ps -U -i
$> frida-ps -h
Usage: frida-ps [options]
Options:
--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
frida-trace 方式操作
frida-trace -i open -U com.android.chrome
執(zhí)行之后會在當前目錄盟劫,生成__handlers__
文件夾,open.js 對應命令中 -i open
的名字与纽,結構如下所示
.
├── __handlers__
│ └── libc.so
│ ├── close.js
│ └── open.js
open.js 內容如下(注釋已去掉)侣签,可修改該文件改變輸出內容
# !javascript
{
onEnter: function (log, args, state) {
log("open(" +
"path=\"" + Memory.readUtf8String(args[0]) + "\"" +
", oflag=" + args[1] +
")");
},
onLeave: function (log, retval, state) {
}
}
最終運行結果塘装,和open.js中 onEnter 方法一致,如下:
$> frida-trace -i "open" -U com.android.chrome
Instrumenting functions...
open: Auto-generated handler at "/Users/hych/Open/Frida/Workspace/__handlers__/libc.so/open.js"
Started tracing 1 function. Press Ctrl+C to stop.
/* TID 0x5e80 */
5556 ms open(path="/data/data/com.android.chrome/cache/Cache/index-dir/temp-index", oflag=0x241)
/* TID 0x5cf4 */
6950 ms open(path="/proc/vmstat", oflag=0x80000)
/* TID 0x5e87 */
7671 ms open(path="/dev/ashmem", oflag=0x2)
7674 ms open(path="/dev/ashmem", oflag=0x2)
/* TID 0x5cf4 */
57800 ms open(path="/proc/23796/cmdline", oflag=0x0)
load js 方式操作
-l : --load
frida -U -l chrome.js com.android.chrome
chrome.js 文件內容如下:
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")
}
});
});
});
執(zhí)行結果如下影所,發(fā)現(xiàn)創(chuàng)建了4個 View
frida -U -l chrome.js com.android.chrome
____
/ _ | Frida 12.2.27 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at http://www.frida.re/docs/home/
[HUAWEI ALE-UL00::com.android.chrome]-> [*] Starting script
[*] Instance found: android.view.View{21519670 V.ED.... ........ 0,0-0,0}
[*] Instance found: android.view.View{2338b297 G.ED.... ......ID 0,0-0,0 #7f0a0013 app:id/action_bar_black_background}
[*] Instance found: android.view.View{3e144ce4 V.ED.... ........ 0,1134-0,1134 #7f0a022c app:id/menu_anchor_stub}
[*] Instance found: android.view.View{16335de9 G.ED.... ......I. 0,0-0,0 #7f0a0219 app:id/location_bar_verbose_status_separator}
[*] Finished heap search
python 方式操作
python3 chrome.py
chrome.py 如下:
import frida
import sys
scr = """
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")
}
});
});
});
"""
# 采用 remote 方式必須進行端口轉發(fā) 或者使用get_usb_device()
rdev = frida.get_usb_device()
# 目標應用包名
session = rdev.attach("com.android.chrome")
script = session.create_script(scr)
def on_message(message, data):
print(message)
script.on("message", on_message)
script.load()
輸出如下
$> python3 chrome.py
[*] Starting script
[*] Instance found: android.view.View{25b22a6e V.ED.... ........ 0,0-0,0}
[*] Instance found: android.view.View{1ee5b916 G.ED.... ......ID 0,0-0,0 #7f0a0013 app:id/action_bar_black_background}
[*] Instance found: android.view.View{25d34877 V.ED.... ........ 0,1134-0,1134 #7f0a022c app:id/menu_anchor_stub}
[*] Instance found: android.view.View{14ddc50f G.ED.... ......I. 0,0-0,0 #7f0a0219 app:id/location_bar_verbose_status_separator}
[*] Finished heap search
Hook
在 Hook 之前蹦肴,對 JAVA 注入相關的 API 做一個簡單介紹,frida 的注入腳本是 JavaScript猴娩,因此我們后面都是通過 js 腳本來操作設備上的 Java 代碼阴幌,如下
// 確保我們的線程附加到 Java 的虛擬機上,function 是成功之后的回調卷中,之后的操作必須在這個回調里面進行矛双,這也是 frida 的硬性要求
Java.perfom(function(){})
Java.use(className) //動態(tài)獲取一個 JS 包裝了的 Java 類
$new() // 通過$new方法來調用這個類構造方法
$dispose() // 最后可以通過$dispose()方法來清空這個 JS 對象
// 將 JS 包裝類的實例 handle 轉換成另一種 JS 包裝類klass
Java.cast(handle, kclass)
// 當獲取到 Java 類之后,我們直接通過<wrapper>.<method>.implementations = function(){} 的方式來 hook wrapper 類的 method 方法蟆豫,不管是實例方法還是靜態(tài)方法都可以
var SQL = Java.use("com.xxx.xxx.database.SQLiteDatabase");
var ContentVaules = Java.use("android.content.ContentValues");
SQL.insert.implementation = function(arg1, arg2, arg3) {
var values = Java.cast(arg3, ContentVaules);
}
// 由于 js 代碼注入時可能會出現(xiàn)超時的錯誤议忽,為了防止這個問題,我們通常還需要在最外面包裝一層 setImmediate(function(){})代碼
setImmediate(function(){
Java.perform(function(){
// start hook
...
})
})
hook 本地代碼示例
Android 示例代碼如下:
CoinMoney 類:
public class CoinMoney {
private int money;
private String value;
public int extMoney;
public CoinMoney(int money) {
this.money = money;
}
public CoinMoney(int money, String value) {
this.money = money;
this.value = value;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public int getExtMoney() {
return extMoney;
}
public void setExtMoney(int extMoney) {
this.extMoney = extMoney;
}
}
Utils 類:
public class Utils {
public static String getPwd(String info) {
return info + "_fourbrother";
}
}
python 腳本代碼
hookNativeCode.py 如下:
# !python
import frida
import sys
scr = """
setImmediate(function() {
Java.perform(function(){
console.log("[*] Starting script --console");
var utils = Java.use("com.simple.hookapp.Utils");
var coinClass = Java.use("com.simple.hookapp.CoinMoney");
var clazz = Java.use("java.lang.Class");
var Exception = Java.use("java.lang.Exception");
// hook 構造方法 **init**
coinClass.$init.overload("int", "java.lang.String").implementation = function(money, value){
send("money: "+money+ ", value: "+value);
// 修改方法的參數(shù)
return this.$init(12, "12.0");
}
// hook 普通方法
coinClass.getValue.overload().implementation = function(){
// 修改方法的返回值
return this.getValue()+"_hook";
}
// hook 靜態(tài)方法
utils.getPwd.overload("java.lang.String").implementation = function(pwd){
var arg = arguments[0];
send("pwd 方式一 參數(shù)arguments獲取:"+ arg);
send("pwd 獲取方式二 參數(shù)聲明: "+ pwd)
// 拋出異常查看堆棧信息 adb logcat -s AndroidRuntime
throw Exception.$new("Utils getPwd Exception...")
// 修改方法的參數(shù)和返回值
var result = this.getPwd(arg + "_hook_")+"_hook";
send(result);
return result;
}
// 實例化對象 **new** 方式一
var testNewCoin1 = coinClass.$new(11, "11.00 - testNewCoin1");
send("testNewCoin1: "+ testNewCoin1);
// 實例化對象 **new** 方式二
var testNewCoin2 = coinClass.$new.overload("int", "java.lang.String").call(coinClass, 22, "22.00 - testNewCoin2");
send("testNewCoin2: " + testNewCoin2);
// 直接調用方法
send("直接調用方法 "+testNewCoin2.getValue());
// 反射調用方法
// var reflectCoinExtMoney = Java.cast(testNewCoin2.getClass(),clazz).getDeclaredMethod("getValue");
// 直接調用字段
var directCoinValue = testNewCoin2.value;
send(directCoinValue);// 輸出:{'value': '12.0', 'fieldType': 2, 'fieldReturnType': {'className': 'java.lang.String', 'name': 'Ljava/lang/String;', 'type': 'pointer', 'size': 1}}}
var directCoinExtMoney = testNewCoin2.extMoney;
send("直接調用字段 - public "+directCoinExtMoney);// 輸出:直接調用字段 - public [object Object]
// 反射調用字段
var reflectCoinValueName = Java.cast(testNewCoin2.getClass(),clazz).getDeclaredField("value");
reflectCoinValueName.setAccessible(true);
// 反射調用字段 - 獲取值
var reflectCoinValueGet = reflectCoinValueName.get(testNewCoin2);
send("反射調用字段 - private "+ reflectCoinValueGet);
// 反射調用字段 - 設置值 **基本類型修改值 setXXX 方法无埃,對象類型都是 set 方法即可**
reflectCoinValueName.set(testNewCoin2,"set value");
send(testNewCoin2.value);
// 靜態(tài)方法 在本腳本內使用沒有調用上方的hook函數(shù)
var newPwd = utils.getPwd("654321");
send(newPwd);
});
});
"""
# 采用 remote 方式必須進行端口轉發(fā) 或者使用get_usb_device()
rdev = frida.get_usb_device()
session = rdev.attach("com.simple.hookapp")
script = session.create_script(scr)
def on_message(message, data):
print(message)
script.on("message", on_message)
script.load()
sys.stdin.read()
執(zhí)行
python3 hookNativeCode.py
Hook so 代碼
hook 導出代碼
android cpp 示例代碼
native-lib.cpp
extern "C" JNIEXPORT jstring JNICALL
Java_com_simple_hookapp_JavaMainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello Java from C++";
return env->NewStringUTF(hello.c_str());
}
python 腳本示例:
import frida
import sys
scr = """
setImmediate(function() {
Java.perform(function(){
console.log("[*] Starting script --console");
// hook export function
var nativePointer = Module.findExportByName("libnative-lib.so", "Java_com_simple_hookapp_JavaMainActivity_stringFromJNI");
send("hookapp export native pointers: "+ nativePointer);
Interceptor.attach(nativePointer, {
onEnter: function(args){
send("hookapp export so onEnter args: "+ args);
},
onLeave: function(retval){
send("hookapp export so onLeave retval: "+ retval);
var env = Java.vm.getEnv();
var jstrings = env.newStringUtf("fourbroeher");
// 修改返回值方式
return retval.replace(jstrings);
}
});
});
});
"""
# 采用 remote 方式必須進行端口轉發(fā) 或者使用get_usb_device()
rdev = frida.get_usb_device()
session = rdev.attach("com.simple.hookapp")
# session = rdev.attach("com.simple.hookapp")
script = session.create_script(scr)
def on_message(message, data):
print(message)
script.on("message", on_message)
script.load()
sys.stdin.read()
hook 未導出代碼
- 查看 so 文件的內存基地址
使用命令行查看 root徙瓶,下面ec64e000
為內存基地址
$ adb shell
shell@hwALE-H:/ # su
root@hwALE-H:/ # ps | grep maker
u0_a5624 31315 2333 1771148 155332 ffffffff f771fdfc S com.smile.gifmaker
u0_a5624 31380 2333 1644536 83624 ffffffff f771fdfc S com.smile.gifmaker:QS
u0_a5624 31455 2333 1640868 85960 ffffffff f771fdfc S com.smile.gifmaker:messagesdk
u0_a5624 31601 2333 1667096 100384 ffffffff f771fdfc S com.smile.gifmaker:pushservice
1|root@hwALE-H:/ # cat /proc/31315/maps | grep libmtp.so
ec64e000-ec661000 r-xp 00000000 103:06 2171 /system/lib/libmtp.so
ec661000-ec663000 r--p 00012000 103:06 2171 /system/lib/libmtp.so
ec663000-ec664000 rw-p 00014000 103:06 2171 /system/lib/libmtp.so
函數(shù)的相對地址
IDA 打開 so 文件查看,例如.text:00005070
上述倆個地址相加嫉称,然后+1侦镇。 例如下面示例中
0xEC644071
setImmediate(function() {
Java.perform(function(){
console.log("[*] Starting script --console");
// hook unexport function
var nativePointer = new NativePointer(0xEC644071);
send("hookapp native pointers: "+ nativePointer);
var result_pointer;
Interceptor.attach(nativePointer, {
onEnter: function(args){
// result_pointer = args[2].toInt32();
// send("hookapp so args: "+ Memory.readCString(args[0]) + ", " + args[1] + ", "+ args[2]);
},
onLeave: function(retval){
// memory alloc string
}
});
});
});
Demo地址:https://github.com/simplehych/HookApp
參考資料:
感謝以下文章作者
http://www.4hou.com/info/news/4113.html
https://baijiahao.baidu.com/s?id=1608313750146067893&wfr=spider&for=pc
https://fuping.site/2017/04/01/Android-HOOK-%E6%8A%80%E6%9C%AF%E4%B9%8BFrida%E7%9A%84%E5%88%9D%E7%BA%A7%E4%BD%BF%E7%94%A8/
https://blog.csdn.net/qingemengyue/article/details/80061491
https://sec.xiaomi.com/article/43
https://blog.csdn.net/jiangwei0910410003/article/details/80372118