Frida

总体来自吾爱破解-正己的Frida篇教程

原理

Frida 是一款开源的动态插桩工具,可以插入一些代码到原生App的内存空间去动态地监视和修改其行为,支持Windows、Mac、Linux、Android或者iOS,从安卓层面来讲,可以实现Java层和NativeHook操作。

frida注入的原理就是找到目标进程,使用ptrace跟踪目标进程获取mmap,dlopen,dlsym等函数库的偏移获取mmap在目标进程申请一段内存空间将在目标进程中找到存放frida-agent-32/64.so的空间启动执行各种操作由agent去实现

插桩技术

frida使用的是动态二进制插桩技术DBI),首先来了解一下插桩技术:

插桩技术是指将额外的代码注入程序中以收集运行时的信息,可分为两种:
(1)源代码插桩[Source Code Instrumentation(SCI)]:顾名思义,在程序源代码的基础上增加(注入)额外的代码,从而达到预期目的或者功能;

img

源代码插桩实例

(2)二进制插桩(Binary Instrumentation):额外代码注入到二进制可执行文件中,通过修改汇编地址,改变程序运行内容,运行后再返回到原来程序运行出处,从而实现程序的额外功能。
●静态二进制插桩[Static Binary Instrumentation(SBI)]:在程序执行前插入额外的代码和数据,生成一个永久改变的可执行文件。
●动态二进制插桩[Dynamic Binary Instrumentation(DBI)]:在程序运行时实时地插入额外代码和数据,对可执行文件没有任何永久改变。*

DBI能做什么?

(1)访问进程的内存
(2)在应用程序运行时覆盖一些功能
(3)从导入的类中调用函数
(4)在堆上查找对象实例并使用这些对象实例
(5)Hook,跟踪和拦截函数等等

注入的两种模式

  1. attach模式
    attach到已经存在的进程,核心原理是ptrace修改进程内存,如果进程处于调试状态(traceid不等于0),则attach失败
  2. spawn模式
    启动一个新的进程并挂起,在启动的同时注入frida代码,适用于在进程启动前的一些hook,如hook RegisterNative等,注入完成后调用resume恢复进程。
注入模式 描述 命令或参数 优点 主要用途
Spawn模式 将启动App的权利交由Frida来控制,即使目标App已经启动,在使用Frida注入程序时还是会重新启动App 在CLI模式中,Frida通过加上 -f 参数指定包名以spawn模式操作App 适合于需要在App启动时即进行注入的场景,可以在App启动时即捕获其行为 当需要监控App从启动开始的所有行为时使用
Attach模式 在目标App已经启动的情况下,Frida通过ptrace注入程序从而执行Hook的操作 在CLI模式中,如果不添加 -f 参数,则默认会通过attach模式注入App 适合于已经运行的App,不会重新启动App,对用户体验影响较小 在App已经启动,或者我们只关心特定时刻或特定功能的行为时使用

用法

安装

1
2
3
4
5
6
pip install frida==12.8.0
pip install frida-tools==5.3.0


pip install objection
objection -h

下载frida-server并解压(https://github.com/frida/frida/releases/download/12.8.0/frida-server-12.8.0-android-arm64.xz)

先adb shell,然后切换到root权限,把之前push进来的frida server改个名字叫fs,然后运行frida

1
2
3
adb push /Users/sakura/Desktop/lab/alpha/tools/android/frida-server-12.8.0-android-arm64 /data/local/tmp
chmod +x fs
./fs

如果要监听端口,就

1
./fs -l 0.0.0.0:8888

安装源代码

1
2
3
git clone https://github.com/oleavr/frida-agent-example.git
cd frida-agent-example/
npm install
  1. 使用vscode打开此工程,在agent文件夹下编写js,会有智能提示。
  2. npm run watch会监控代码修改自动编译生成js文件
  3. python脚本或者cli加载_agent.js
    frida -U -f com.example.android --no-pause -l _agent.js

frida-server基本操作

frida-ps -U查看通过usb连接的android手机上的进程。

基础语法

API名称 描述
Java.use(className) 获取指定的Java类并使其在JavaScript代码中可用。
Java.perform(callback) 确保回调函数在Java的主线程上执行。
Java.choose(className, callbacks) 枚举指定类的所有实例。
Java.cast(obj, cls) 将一个Java对象转换成另一个Java类的实例。
Java.enumerateLoadedClasses(callbacks) 枚举进程中已经加载的所有Java类。
Java.enumerateClassLoaders(callbacks) 枚举进程中存在的所有Java类加载器。
Java.enumerateMethods(targetClassMethod) 枚举指定类的所有方法。
console.log() 使用JavaScript直接进行日志打印 多用于在CLI模式中,console.log()直接输出到命令行界面,使用户可以实时查看。在RPC模式中,console.log()同样输出在命令行,但可能被Python脚本的输出内容掩盖。
send() Frida的专有方法,用于发送数据或日志到外部Python脚本 多用于RPC模式中,它允许JavaScript脚本发送数据到Python脚本,Python脚本可以进一步处理或记录这些数据。

Hook普通方法、打印参数和修改返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//定义一个名为hookTest1的函数
function hookTest1(){
//获取一个名为"类名"的Java类,并将其实例赋值给JavaScript变量utils
var utils = Java.use("类名");
//修改"类名"的"method"方法的实现。这个新的实现会接收两个参数(a和b)
utils.method.implementation = function(a, b){
//将参数a和b的值改为123和456。
a = 123;
b = 456;
//调用修改过的"method"方法,并将返回值存储在`retval`变量中
var retval = this.method(a, b);
//在控制台上打印参数a,b的值以及"method"方法的返回值
console.log(a, b, retval);
//返回"method"方法的返回值
return retval;
}
}

Hook重载参数

1
2
3
4
5
6
7
8
9
10
11
12
13
// .overload()
// .overload('自定义参数')
// .overload('int')
function hookTest2(){
var utils = Java.use("com.zj.wuaipojie.Demo");
//overload定义重载函数,根据函数的参数类型填
utils.Inner.overload('com.zj.wuaipojie.Demo$Animal','java.lang.String').implementation = function(a,b){
b = "aaaaaaaaaa";
this.Inner(a,b);
console.log(b);
}
}

Hook构造函数

1
2
3
4
5
6
7
8
9
10
function hookTest3(){
var utils = Java.use("com.zj.wuaipojie.Demo");
//修改类的构造函数的实现,$init表示构造函数
utils.$init.overload('java.lang.String').implementation = function(str){
console.log(str);
str = "52";
this.$init(str);
}
}

Hook字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function hookTest5(){
Java.perform(function(){
//静态字段的修改
var utils = Java.use("com.zj.wuaipojie.Demo");
//修改类的静态字段"flag"的值
utils.staticField.value = "我是被修改的静态变量";
console.log(utils.staticField.value);
//非静态字段的修改
//使用`Java.choose()`枚举类的所有实例
Java.choose("com.zj.wuaipojie.Demo", {
onMatch: function(obj){
//修改实例的非静态字段"_privateInt"的值为"123456",并修改非静态字段"privateInt"的值为9999。
obj._privateInt.value = "123456"; //字段名与函数名相同 前面加个下划线
obj.privateInt.value = 9999;
},
onComplete: function(){

}
});
});

}

Hook内部类

1
2
3
4
5
6
7
8
9
10
11
12
function hookTest6(){
Java.perform(function(){
//内部类
var innerClass = Java.use("com.zj.wuaipojie.Demo$innerClass");
console.log(innerClass);
innerClass.$init.implementation = function(){
console.log("eeeeeeee");
}

});
}

枚举所有的类与类的所有方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function hookTest7(){
Java.perform(function(){
//枚举所有的类与类的所有方法,异步枚举
Java.enumerateLoadedClasses({
onMatch: function(name,handle){
//过滤类名
if(name.indexOf("com.zj.wuaipojie.Demo") !=-1){
console.log(name);
var clazz =Java.use(name);
console.log(clazz);
var methods = clazz.class.getDeclaredMethods();
console.log(methods);
}
},
onComplete: function(){}
})
})
}

枚举所有方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function hookTest8(){
Java.perform(function(){
var Demo = Java.use("com.zj.wuaipojie.Demo");
//getDeclaredMethods枚举所有方法
var methods =Demo.class.getDeclaredMethods();
for(var j=0; j < methods.length; j++){
var methodName = methods[j].getName();
console.log(methodName);
for(var k=0; k<Demo[methodName].overloads.length;k++){
Demo[methodName].overloads[k].implementation = function(){
for(var i=0;i<arguments.length;i++){
console.log(arguments[i]);
}
return this[methodName].apply(this,arguments);
}
}
}
})
}

主动调用

静态方法

1
2
var ClassName=Java.use("com.zj.wuaipojie.Demo"); 
ClassName.privateFunc("传参");

非静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
var ret = null;
Java.perform(function () {
Java.choose("com.zj.wuaipojie.Demo",{ //要hook的类
onMatch:function(instance){
ret=instance.privateFunc("aaaaaaa"); //要hook的方法
},
onComplete:function(){
//console.log("result: " + ret);
}
});
})
//return ret;

Objection

objection是基于frida的命令行hook集合工具, 可以让你不写代码, 敲几句命令就可以对java函数的高颗粒度hook, 还支持RPC调用。可以实现诸如内存搜索、类和模块搜索、方法hook打印参数返回值调用栈等常用功能,是一个非常方便的,逆向必备、内存漫游神器。

1
2
3
4
# python使用的版本建议大于3.8,不然可能会报错,或者你调低frida以及objection的版本
pip install objection==1.11.0
pip install frida-tools==9.2.4
frida 14.2.18

上手

命令注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
objection --help(help命令)
Checking for a newer version of objection...
Usage: objection [OPTIONS] COMMAND [ARGS]...

_ _ _ _
___| |_|_|___ ___| |_|_|___ ___
| . | . | | -_| _| _| | . | |
|___|___| |___|___|_| |_|___|_|_|
|___|(object)inject(ion)

Runtime Mobile Exploration
by: @leonjza from @sensepost

默认情况下,通信将通过USB进行,除非提供了`--network`选项。

选项:
-N, --network 使用网络连接而不是USB连接。
-h, --host TEXT [默认: 127.0.0.1]
-p, --port INTEGER [默认: 27042]
-ah, --api-host TEXT [默认: 127.0.0.1]
-ap, --api-port INTEGER [默认: 8888]
-g, --gadget TEXT 要连接的Frida Gadget/进程的名称。 [默认: Gadget]
-S, --serial TEXT 要连接的设备序列号。
-d, --debug 启用带有详细输出的调试模式。(在堆栈跟踪中包括代{过}{滤}理源图)
--help 显示此消息并退出。

命令:
api 以无头模式启动objection API服务器。
device-type 获取关于已连接设备的信息。
explore 启动objection探索REPL。
patchapk 使用frida-gadget.so补丁一个APK。
patchipa 使用FridaGadget dylib补丁一个IPA。
run 运行单个objection命令。
signapk 使用objection密钥对APK进行Zipalign和签名。
version 打印当前版本并退出。

注入命令

1
2
3
4
5
6
objection -g 包名 explore

- help:不知道当前命令的效果是什么,在当前命令前加help比如:help env,回车之后会出现当前命令的解释信息
- 按空格:不知道输入什么就按空格,会有提示出来
- jobs:可以进行多项hook
- 日志:objection的日志文件生成在 C:\Users\Administrator\.objection

启动前就hook

1
objection -g 进程名 explore --startup-command "android hooking watch class 路径.类名"

基本命令

memory list modules -查看内存中加载的库

1
2
3
4
5
6
7
8
9
10
11
12
memory list modules
Save the output by adding `--json modules.json` to this command
Name Base Size Path
---------------------------------------------------------------- ------------ ------------------- ------------------------------------------------------------------------------
app_process64 0x57867c9000 40960 (40.0 KiB) /system/bin/app_process64
linker64 0x72e326a000 229376 (224.0 KiB) /system/bin/linker64
libandroid_runtime.so 0x72e164e000 2113536 (2.0 MiB) /system/lib64/libandroid_runtime.so
libbase.so 0x72dfa67000 81920 (80.0 KiB) /system/lib64/libbase.so
libbinder.so 0x72dec1c000 643072 (628.0 KiB) /system/lib64/libbinder.so
libcutils.so 0x72de269000 86016 (84.0 KiB) /system/lib64/libcutils.so
libhidlbase.so 0x72df4cc000 692224 (676.0 KiB) /system/lib64/libhidlbase.so
liblog.so 0x72e0be1000 98304 (96.0 KiB) /system/lib64/liblog

memory list exports so名称 - 查看库的导出函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
memory list exports liblog.so
Save the output by adding `--json exports.json` to this command
Type Name Address
-------- ------------------------------------ ------------
function android_log_write_int32 0x72e0be77c8
function android_log_write_list_begin 0x72e0be76f0
function __android_log_bswrite 0x72e0be9bd8
function __android_log_security 0x72e0bf2144
function __android_log_bwrite 0x72e0be9a18
function android_log_reset 0x72e0be75ec
function android_log_write_string8 0x72e0be7a38
function android_logger_list_free 0x72e0be8c04
function __android_log_print 0x72e0be9728
function __android_logger_property_get_bool 0x72e0bf2248
function android_logger_get_id 0x72e0be8270
function android_logger_set_prune_list 0x72e0be8948

android hooking list activities -查看内存中加载的activity /android hooking list services -查看内存中加载的services

android intent launch_activity 类名 -启动activityservice(可以用于一些没有验证的activity,在一些简单的ctf中有时候可以出奇效)

关闭ssl校验 android sslpinning disable

关闭root检测 android root disable

内存漫游

内存搜刮类实例

1
2
3
4
5
android heap search instances 类名(命令)
Class instance enumeration complete for com.zj.wuaipojie.Demo
Hashcode Class toString()
--------- --------------------- -----------------------------
215120583 com.zj.wuaipojie.Demo com.zj.wuaipojie.Demo@cd27ac7

调用实例方法

1
2
3
4
5
android heap execute <handle> getPublicInt(实例的hashcode+方法名)
# 如果是带参数的方法,则需要进入编辑器环境
android heap evaluate <handle>
console.log(clazz.a("吾爱破解"));
# 按住esc+enter触发

android hooking list classes -列出内存中所有的类(结果比静态分析的更准确)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
android hooking list classes 

tw.idv.palatis.xappdebug.MainApplication
tw.idv.palatis.xappdebug.xposed.HookMain
tw.idv.palatis.xappdebug.xposed.HookMain$a
tw.idv.palatis.xappdebug.xposed.HookMain$b
tw.idv.palatis.xappdebug.xposed.HookMain$c
tw.idv.palatis.xappdebug.xposed.HookMain$d
tw.idv.palatis.xappdebug.xposed.HookSelf
u
v
void
w
xposed.dummy.XResourcesSuperClass
xposed.dummy.XTypedArraySuperClass

Found 10798 classes

android hooking search classes 关键类名 -在内存中所有已加载的类中搜索包含特定关键词的类

1
2
3
4
5
6
7
8
9
10
11
12
android hooking search classes wuaipojie
Note that Java classes are only loaded when they are used, so if the expected class has not been found, it might not have been loaded yet.
com.zj.wuaipojie.Demo
com.zj.wuaipojie.Demo$Animal
com.zj.wuaipojie.Demo$Companion
com.zj.wuaipojie.Demo$InnerClass
com.zj.wuaipojie.Demo$test$1
com.zj.wuaipojie.MainApplication
com.zj.wuaipojie.databinding.ActivityMainBinding
...

Found 38 classes

android hooking search methods 关键方法名 -在内存中所有已加载的类的方法中搜索包含特定关键词的方法(一般不建议使用,特别耗时,还可能崩溃)

android hooking list class_methods 类名 -内存漫游类中的所有方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
android hooking list class_methods com.zj.wuaipojie.ui.ChallengeSixth
private static final void com.zj.wuaipojie.ui.ChallengeSixth.onCreate$lambda-0(com.zj.wuaipojie.ui.ChallengeSixth,android.view.View)
private static final void com.zj.wuaipojie.ui.ChallengeSixth.onCreate$lambda-1(com.zj.wuaipojie.ui.ChallengeSixth,android.view.View)
private static final void com.zj.wuaipojie.ui.ChallengeSixth.onCreate$lambda-2(com.zj.wuaipojie.ui.ChallengeSixth,android.view.View)
private static final void com.zj.wuaipojie.ui.ChallengeSixth.onCreate$lambda-3(com.zj.wuaipojie.ui.ChallengeSixth,android.view.View)
protected void com.zj.wuaipojie.ui.ChallengeSixth.onCreate(android.os.Bundle)
public final java.lang.String com.zj.wuaipojie.ui.ChallengeSixth.hexToString(java.lang.String)
public final java.lang.String com.zj.wuaipojie.ui.ChallengeSixth.unicodeToString(java.lang.String)
public final void com.zj.wuaipojie.ui.ChallengeSixth.toastPrint(java.lang.String)
public static void com.zj.wuaipojie.ui.ChallengeSixth.$r8$lambda$1lrkrgiCEFWXZDHzLRibYURG1h8(com.zj.wuaipojie.ui.ChallengeSixth,android.view.View)
public static void com.zj.wuaipojie.ui.ChallengeSixth.$r8$lambda$IUqwMqbTKaOGiTaeOmvy_GjNBso(com.zj.wuaipojie.ui.ChallengeSixth,android.view.View)
public static void com.zj.wuaipojie.ui.ChallengeSixth.$r8$lambda$Kc_cRYZjjhjsTl6GYNHbgD-i6sE(com.zj.wuaipojie.ui.ChallengeSixth,android.view.View)
public static void com.zj.wuaipojie.ui.ChallengeSixth.$r8$lambda$PDKm2AfziZQo6Lv1HEFkJWkUsoE(com.zj.wuaipojie.ui.ChallengeSixth,android.view.View)

Found 12 method(s)

ObjectionHook

hook类的所有方法

1
android hooking watch class 类名

hook方法的参数、返回值和调用栈

1
android hooking watch class_method 类名.方法名 --dump-args --dump-return --dump-backtrace

hook 类的构造方法

1
android hooking watch class_method 类名.$init

hook 方法的所有重载

1
android hooking watch class_method 类名.方法名

Hook Native

Process 对象代表当前被Hook的进程,能获取进程的信息,枚举模块,枚举范围等

API 含义
Process.id 返回附加目标进程的 PID
Process.isDebuggerAttached() 检测当前是否对目标程序已经附加
Process.enumerateModules() 枚举当前加载的模块,返回模块对象的数组
Process.enumerateThreads() 枚举当前所有的线程,返回包含 id, state, context 等属性的对象数组

Module 对象代表一个加载到进程的模块(例如,在 Windows 上的 DLL,或在 Linux/Android 上的 .so 文件),能查询模块的信息,如模块的基址、名称、导入/导出的函数等

API 含义
Module.load() 加载指定so文件,返回一个Module对象
enumerateImports() 枚举所有Import库函数,返回Module数组对象
enumerateExports() 枚举所有Export库函数,返回Module数组对象
enumerateSymbols() 枚举所有Symbol库函数,返回Module数组对象
Module.findExportByName(exportName)、Module.getExportByName(exportName) 寻找指定so中export库中的函数地址
Module.findBaseAddress(name)、Module.getBaseAddress(name) 返回so的基地址

Memory是一个工具对象,提供直接读取和修改进程内存的功能,能够读取特定地址的值、写入数据、分配内存等

方法 功能
Memory.copy() 复制内存
Memory.scan() 搜索内存中特定模式的数据
Memory.scanSync() 同上,但返回多个匹配的数据
Memory.alloc() 在目标进程的堆上申请指定大小的内存,返回一个NativePointer
Memory.writeByteArray() 将字节数组写入一个指定内存
Memory.readByteArray 读取内存

枚举导入导出表

  1. 导出表(Export Table):列出了库中可以被其他程序或库访问的所有公开函数和符号的名称。
  2. 导入表(Import Table):列出了库需要从其他库中调用的函数和符号的名称。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function hookTest1(){
Java.perform(function(){
//打印导入表
var imports = Module.enumerateImports("lib52pojie.so");
for(var i =0; i < imports.length;i++){
if(imports[i].name == "vip"){
console.log(JSON.stringify(imports[i])); //通过JSON.stringify打印object数据
console.log(imports[i].address);
}
}
//打印导出表
var exports = Module.enumerateExports("lib52pojie.so");
for(var i =0; i < exports.length;i++){
console.log(JSON.stringify(exports[i]));
}

})
}

Native函数的基础Hook打印

整数型、布尔值类型、char类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function hookTest2(){
Java.perform(function(){
//根据导出函数名打印地址
var helloAddr = Module.findExportByName("lib52pojie.so","Java_com_zj_wuaipojie_util_SecurityUtil_checkVip");
console.log(helloAddr);
if(helloAddr != null){
//Interceptor.attach是Frida里的一个拦截器
Interceptor.attach(helloAddr,{
//onEnter里可以打印和修改参数
onEnter: function(args){ //args传入参数
console.log(args[0]); //打印第一个参数的值
console.log(this.context.x1); // 打印寄存器内容
console.log(args[1].toInt32()); //toInt32()转十进制
console.log(args[2].readCString()); //读取字符串 char类型
console.log(hexdump(args[2])); //内存dump

},
//onLeave里可以打印和修改返回值
onLeave: function(retval){ //retval返回值
console.log(retval);
console.log("retval",retval.toInt32());
}
})
}
})
}

字符串类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function hookTest2(){
Java.perform(function(){
//根据导出函数名打印地址
var helloAddr = Module.findExportByName("lib52pojie.so","Java_com_zj_wuaipojie_util_SecurityUtil_vipLevel");
if(helloAddr != null){
Interceptor.attach(helloAddr,{
//onEnter里可以打印和修改参数
onEnter: function(args){ //args传入参数
// 方法一
var jString = Java.cast(args[2], Java.use('java.lang.String'));
console.log("参数:", jString.toString());
// 方法二
var JNIEnv = Java.vm.getEnv();
var originalStrPtr = JNIEnv.getStringUtfChars(args[2], null).readCString();
console.log("参数:", originalStrPtr);
},
//onLeave里可以打印和修改返回值
onLeave: function(retval){ //retval返回值
var returnedJString = Java.cast(retval, Java.use('java.lang.String'));
console.log("返回值:", returnedJString.toString());
}
})
}
})
}

Native函数的基础Hook修改

整数型修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function hookTest3(){
Java.perform(function(){
//根据导出函数名打印地址
var helloAddr = Module.findExportByName("lib52pojie.so","Java_com_zj_wuaipojie_util_SecurityUtil_checkVip");
console.log(helloAddr);
if(helloAddr != null){
Interceptor.attach(helloAddr,{
onEnter: function(args){ //args参数
args[0] = ptr(1000); //第一个参数修改为整数 1000,先转为指针再赋值
console.log(args[0]);

},
onLeave: function(retval){ //retval返回值
retval.replace(20000); //返回值修改
console.log("retval",retval.toInt32());
}
})
}
})
}

字符串类型修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function hookTest2(){
Java.perform(function(){
//根据导出函数名打印地址
var helloAddr = Module.findExportByName("lib52pojie.so","Java_com_zj_wuaipojie_util_SecurityUtil_vipLevel");
if(helloAddr != null){
Interceptor.attach(helloAddr,{
//onEnter里可以打印和修改参数
onEnter: function(args){ //args传入参数
var JNIEnv = Java.vm.getEnv();
var originalStrPtr = JNIEnv.getStringUtfChars(args[2], null).readCString();
console.log("参数:", originalStrPtr);
var modifiedContent = "至尊";
var newJString = JNIEnv.newStringUtf(modifiedContent);
args[2] = newJString;
},
//onLeave里可以打印和修改返回值
onLeave: function(retval){ //retval返回值
var returnedJString = Java.cast(retval, Java.use('java.lang.String'));
console.log("返回值:", returnedJString.toString());
var JNIEnv = Java.vm.getEnv();
var modifiedContent = "无敌";
var newJString = JNIEnv.newStringUtf(modifiedContent);
retval.replace(newJString);
}
})
}
})
}

SO基址的获取方式

1
2
3
var moduleAddr1 = Process.findModuleByName("lib52pojie.so").base;  
var moduleAddr2 = Process.getModuleByName("lib52pojie.so").base;
var moduleAddr3 = Module.findBaseAddress("lib52pojie.so");

Hook未导出函数与函数地址计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function hookTest6(){
Java.perform(function(){
//根据导出函数名打印基址
var soAddr = Module.findBaseAddress("lib52pojie.so");
console.log(soAddr);
var funcaddr = soAddr.add(0x1071C);
console.log(funcaddr);
if(funcaddr != null){
Interceptor.attach(funcaddr,{
onEnter: function(args){ //args参数

},
onLeave: function(retval){ //retval返回值
console.log(retval.toInt32());
}
})
}
})
}

函数地址计算???????????????????

  1. 安卓里一般32 位的 so 中都是thumb指令,64 位的 so 中都是arm指令
  2. 通过IDA里的opcode bytes来判断,arm 指令为 4 个字节(options -> general -> Number of opcode bytes (non-graph) 输入4)
  3. thumb 指令,函数地址计算方式: so 基址 + 函数在 so 中的偏移 + 1
    arm 指令,函数地址计算方式: so 基址 + 函数在 so 中的偏移

Hook_dlopen

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function hook_dlopen() {
var dlopen = Module.findExportByName(null, "dlopen");
Interceptor.attach(dlopen, {
onEnter: function (args) {
var so_name = args[0].readCString();
if (so_name.indexOf("lib52pojie.so") >= 0) this.call_hook = true;
}, onLeave: function (retval) {
if (this.call_hook) hookTest2();
}
});
// 高版本Android系统使用android_dlopen_ext
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
Interceptor.attach(android_dlopen_ext, {
onEnter: function (args) {
var so_name = args[0].readCString();
if (so_name.indexOf("lib52pojie.so") >= 0) this.call_hook = true;
}, onLeave: function (retval) {
if (this.call_hook) hookTest2();
}
});
}

Frida 写数据

1
2
3
4
5
6
7
8
//一般写在app的私有目录里,不然会报错:failed to open file (Permission denied)(实际上就是权限不足)
var file_path = "/data/user/0/com.zj.wuaipojie/test.txt";
var file_handle = new File(file_path, "wb");
if (file_handle && file_handle != null) {
file_handle.write(data); //写入数据
file_handle.flush(); //刷新
file_handle.close(); //关闭
}

Frida_inlineHook与读写汇编

什么是inlinehook?
Inline hook(内联钩子)是一种在程序运行时修改函数执行流程的技术。它通过修改函数的原始代码,将目标函数的执行路径重定向到自定义的代码段,从而实现对目标函数的拦截和修改。
简单来说就是可以对任意地址的指令进行hook读写操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function inline_hook() {
var soAddr = Module.findBaseAddress("lib52pojie.so");
if (soAddr) {
var func_addr = soAddr.add(0x10428);
Java.perform(function () {
Interceptor.attach(func_addr, {
onEnter: function (args) {
console.log(this.context.x22); //注意此时就没有args概念了
this.context.x22 = ptr(1); //赋值方法参考上一节课
},
onLeave: function (retval) {
}
}
)
})
}
}

将地址的指令解析成汇编

1
2
3
var soAddr = Module.findBaseAddress("lib52pojie.so");
var codeAddr = Instruction.parse(soAddr.add(0x10428));
console.log(codeAddr.toString());

Frida Api arm转hex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var soAddr = Module.findBaseAddress("lib52pojie.so");
var codeAddr = soAddr.add(0x10428);
Memory.patchCode(codeAddr, 4, function(code) {
const writer = new Arm64Writer(code, { pc: codeAddr });
writer.putBytes(hexToBytes("20008052"));
writer.flush();
});
function hexToBytes(str) {
var pos = 0;
var len = str.length;
if (len % 2 != 0) {
return null;
}
len /= 2;
var hexA = new Array();
for (var i = 0; i < len; i++) {
var s = str.substr(pos, 2);
var v = parseInt(s, 16);
hexA.push(v);
pos += 2;
}
return hexA;
}

普通函数与jni函数的主动调用

数据类型 描述
void 无返回值
pointer 指针
int 整数
long 长整数
char 字符
float 浮点数
double 双精度浮点数
bool 布尔值
1
2
3
4
5
6
7
var funcAddr = Module.findBaseAddress("lib52pojie.so").add(0x1054C);
//声明函数指针
//NativeFunction的第一个参数是地址,第二个参数是返回值类型,第三个[]里的是传入的参数类型(有几个就填几个)
var aesAddr = new NativeFunction(funcAddr , 'pointer', ['pointer', 'pointer']);
var encry_text = Memory.allocUtf8String("OOmGYpk6s0qPSXEPp4X31g=="); //开辟一个指针存放字符串
var key = Memory.allocUtf8String('wuaipojie0123456');
console.log(aesAddr(encry_text ,key).readCString());

jni的主动调用
参考java的主动调用,简单快捷

frida-trace

frida-trace 可以一次性监控一堆函数地址。还能打印出比较漂亮的树状图,不仅可以显示调用流程,还能显示调用层次。并且贴心的把不同线程调用结果用不同的颜色区分开了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
D:\> frida-trace.exe --help  
用法: frida-trace [options] target

位置参数:
args extra arguments and/or target

选项:
-h, --help 显示帮助
-D ID, --device ID 通过 ID 连接设备
-U, --usb 通过 USB 连接设备
-R, --remote 连接到远程 frida-server
-H HOST, --host HOST 连接到远程 host 上的 frida-server
--certificate 证书 设置证书,通过 TSL 与 host 交互
--origin ORIGIN 设置连接到远程服务的 "Origin" 头部
--token TOKEN 设置 与host 认证
--keepalive-interval 时间间隔。0表示禁用,-1表示基于传输自动选择
--p2p 建立一个点对点的连接
--stun-server ADDRESS 设置--p2p 的 STUN 服务地址
--relay address,username,password,turn-{udp,tcp,tls} 添加--p2p 延迟
-f TARGET, --file TARGET spawn 模式
-F, --attach-frontmost 附加到最前端的 application
-n NAME, --attach-name NAME 附加到一个名字
-N IDENTIFIER, --attach-identifier IDENTIFIER 附加到标识符
-p PID, --attach-pid PID 附加到 pid
-W PATTERN, --await PATTERN
await spawn matching PATTERN
--stdio {inherit,pipe}
stdio behavior when spawning (defaults to “inherit”)
--aux option set aux option when spawning, such as “uid=(int)42” (supported types are:
string, bool, int)
--realm {native,emulated} 附件的范围
--runtime {qjs,v8} 使用的脚本运行环境
--debug 启用 Node.js 兼容的脚本调试器
--squelch-crash 如果启用,将不会将崩溃报告转储到控制台
-O FILE, --options-file FILE 将信息保存到文件中
--version 显示版本号
  • -i / -a: 跟踪 C 函数或 so 库中的函数。
    PS:-a 包含模块+偏移跟踪,一般用于追踪未导出函数,例子:-a “lib52pojie.so!0x4793c”

包含/排除模块或函数:

  • -I : 包含指定模块。
  • -X : 排除指定模块。

Java 方法跟踪:

  • -j JAVA_METHOD: 包含 Java 方法。
  • -J JAVA_METHOD: 排除 Java 方法。

附加方式:

  • -f:通过 spwan 方式启动
  • -F:通过 attach 方式附加当前进程

日志输出:
-o:日志输出到文件

使用案例:
frida-trace -U -F -I “lib52pojie.so” -i “Java_” #附加当前进程并追踪lib52pojie.so里的所有Java_开头的jni导出函数