0x01 简介
frida 是一款基于 python+javascript 的 hook 框架,可运行在 android、ios、linux、win等各个平台,主要使用的动态二进制插桩技术。
0x02 插桩技术
是指将额外的代码注入程序中以收集运行时的信息,可分为源代码插桩 SCI 和二进制插桩 BI。
- 源代码插桩 SCI
Source Code Instrumentation,额外代码注入到程序源代码中。 二进制插桩 BIBinary Instrumentation,额外代码注入到二进制可执行文件中。
PC 端
python 安装
编译安装- Android 端
已 root 设备
非 root 设备一、PC端
安装 frida CLI(command-line interface,命令行界面)python 安装
pip 安装 frida CLI,pip3 install frida
pip install frida,过程遇到Running setup.py bdist_wheel for frida ...\较慢,请耐心等候源码编译安装
git clone git://github.com/frida/frida.gitcd fridamake
注意安装版本,系列工具版本号要一致
查看 frida 版本号
frida --version12.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. 查看手机型号,下载系统对应版本的 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 shellsucd /data/local/tmpchmod 755 frida-server./frida-server
执行完毕后为运行状态。
保留此窗口 shell,以保证服务运行,关闭该shell 或者停止ctrl+c 则服务关闭。接下来的操作可另起shell 或该步骤命令另起 shell 执行。
5. 进行端口转发监听
adb forward tcp:27042 tcp:27042adb forward tcp:27043 tcp:27043
27042 用于与frida-server通信的默认端口号,之后的每个端口对应每个注入的进程,检查27042端口可检测 Frida 是否存在。
6. 检查是否成功
执行frida-ps -U 命令成功输出进程列表,如下所示
$> frida-ps -UPID Name----- ----------------------------------------4275 com.android.keyguard4629 com.android.phone5182 com.android.contacts:cacheservice...
执行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. 下载系统对应版本的 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,但最后回编译打包之后,运行总是奔溃,不断的尝试之后才发现使用 frida-gadget-12.2.27-android-arm.so.xz 可以正常运行
3. 代码中加载上一步so 文件,建议在应用的入口文件中执行
根据 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. 创建签名文件,有的话可忽略此步骤
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字节对齐优化
zipalign -v 4 repackaged.apk final.apk
检查是否对齐
zipalign -c -v 4 final.apk
注:zipalign可以在V1签名后执行,但zipalign不能在V2签名后执行,只能在V2签名之前执行
6. 安装 apk
adb install final.apk
7. 检查是否成功
打开运行 final.apk,在注入代码位置进入停止等待页面
执行frida-ps -U 命令,只显示Gadget一个进程如下:
$> frida-ps -UPID Name----- ------16251 Gadget
执行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 objectionobjection patchapk -s target_app.apk
网上另一种非 root 方式
未实践
https://bbs.pediy.com/thread-229970.htm
使用方式说明
命令行方式
在命令行使用 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.enumerateLoadedClasses({"onMatch":function(className){ console.log(className) },"onComplete":function(){}})})android.app.NativeActivityjava.security.cert.CertificateExpiredExceptionandroid.util.EventLogandroid.os.storage.StorageVolume$1android.system.StructStatcom.android.org.conscrypt.AbstractSessionContextsun.misc.Unsafe....
frida-ps 命令:
eg: frida-ps -U frida-ps -U -i
$> frida-ps -hUsage: 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
执行之后会在当前目录,生成__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.chromeInstrumenting 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")}});});});
执行结果如下,发现创建了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 fridaimport sysscr = """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 方式必须进行端口转发 或者使用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
Java API 说明
在 Hook 之前,对 JAVA 注入相关的 API 做一个简单介绍,frida 的注入脚本是 JavaScript,因此我们后面都是通过 js 脚本来操作设备上的 Java 代码,如下
// 确保我们的线程附加到 Java 的虚拟机上,function 是成功之后的回调,之后的操作必须在这个回调里面进行,这也是 frida 的硬性要求Java.perfom(function(){})Java.use(className) //动态获取一个 JS 包装了的 Java 类$new() // 通过$new方法来调用这个类构造方法$dispose() // 最后可以通过$dispose()方法来清空这个 JS 对象// 将 JS 包装类的实例 handle 转换成另一种 JS 包装类klassJava.cast(handle, kclass)// 当获取到 Java 类之后,我们直接通过<wrapper>.<method>.implementations = function(){} 的方式来 hook wrapper 类的 method 方法,不管是实例方法还是静态方法都可以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 代码注入时可能会出现超时的错误,为了防止这个问题,我们通常还需要在最外面包装一层 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 如下:
# !pythonimport fridaimport sysscr = """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);// 修改方法的参数return this.$init(12, "12.0");}// hook 普通方法coinClass.getValue.overload().implementation = function(){// 修改方法的返回值return this.getValue()+"_hook";}// hook 静态方法utils.getPwd.overload("java.lang.String").implementation = function(pwd){var arg = arguments[0];send("pwd 方式一 参数arguments获取:"+ arg);send("pwd 获取方式二 参数声明: "+ pwd)// 抛出异常查看堆栈信息 adb logcat -s AndroidRuntimethrow Exception.$new("Utils getPwd Exception...")// 修改方法的参数和返回值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);// 静态方法 在本脚本内使用没有调用上方的hook函数var newPwd = utils.getPwd("654321");send(newPwd);});});"""# 采用 remote 方式必须进行端口转发 或者使用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()
执行
python3 hookNativeCode.py
Hook so 代码
hook 导出代码
android cpp 示例代码
native-lib.cpp
extern "C" JNIEXPORT jstring JNICALLJava_com_simple_hookapp_JavaMainActivity_stringFromJNI(JNIEnv* env,jobject /* this */) {std::string hello = "Hello Java from C++";return env->NewStringUTF(hello.c_str());}
python 脚本示例:
import fridaimport sysscr = """setImmediate(function() {Java.perform(function(){console.log("[*] Starting script --console");// hook export functionvar 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 方式必须进行端口转发 或者使用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 shellshell@hwALE-H:/ # suroot@hwALE-H:/ # ps | grep makeru0_a5624 31315 2333 1771148 155332 ffffffff f771fdfc S com.smile.gifmakeru0_a5624 31380 2333 1644536 83624 ffffffff f771fdfc S com.smile.gifmaker:QSu0_a5624 31455 2333 1640868 85960 ffffffff f771fdfc S com.smile.gifmaker:messagesdku0_a5624 31601 2333 1667096 100384 ffffffff f771fdfc S com.smile.gifmaker:pushservice1|root@hwALE-H:/ # cat /proc/31315/maps | grep libmtp.soec64e000-ec661000 r-xp 00000000 103:06 2171 /system/lib/libmtp.soec661000-ec663000 r--p 00012000 103:06 2171 /system/lib/libmtp.soec663000-ec664000 rw-p 00014000 103:06 2171 /system/lib/libmtp.so
函数的相对地址
IDA 打开 so 文件查看,例如.text:00005070上述俩个地址相加,然后+1。 例如下面示例中
0xEC644071setImmediate(function() {Java.perform(function(){console.log("[*] Starting script --console");// hook unexport functionvar 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
