Android逆向9-Frida入门

来自吾爱破解-正己

https://www.52pojie.cn/thread-1701353-1-1.html

Frida简介

介绍

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

项目地址

官网及使用文档

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

组件名称 功能描述
frida-gum 提供了inline-hook的核心实现,还包含了代码跟踪模块Stalker,用于内存访问监控的MemoryAccessMonitor,以及符号查找、栈回溯实现、内存扫描、动态代码生成和重定位等功能
frida-core fridahook的核心,具有进程注入、进程间通信、会话管理、脚本生命周期管理等功能,屏蔽部分底层的实现细节并给最终用户提供开箱即用的操作接口。包含了frida-server、frida-gadget、frida-agent、frida-helper、frida-inject等关键模块和组件,以及之间的互相通信底座
frida-gadget 本身是一个动态库,可以通过重打包修改动态库的依赖或者修改smali代码去实现向三方应用注入gadget,从而实现Frida的持久化或免root
frida-server 本质上是一个二进制文件,类似于前面学习到的android_server,需要在目标设备上运行并转发端口,在Frida hook中起到关键作用

Frida与Xposed的对比

工具 优点 缺点
Xposed 直接编写Java代码,Java层hook方便,可打包模块持久化hook 环境配置繁琐,兼容性较差,难以Hook底层代码。
Frida 配置简单,免重启hook。支持Java层和Native层的hook操作 持久化hook相对麻烦

基本使用

pip install frida-tools

  1. frida-ps -U 查看当前手机运行的进程
  2. frida-ps –help 查看help指令
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
frida-ps --help
使用方式: frida-ps [选项]

选项:
-h, --help 显示帮助信息并退出
-D ID, --device ID 连接到具有给定ID的设备
-U, --usb 连接到USB设备
-R, --remote 连接到远程frida-server
-H HOST, --host HOST 连接到HOST上的远程frida-server
--certificate CERTIFICATE
与HOST进行TLS通信,期望的CERTIFICATE
--origin ORIGIN 连接到设置了"Origin"头为ORIGIN的远程服务器
--token TOKEN 使用TOKEN验证HOST
--keepalive-interval INTERVAL
设置心跳包间隔(秒),或设置为0以禁用(默认为-1,根据传输方式自动选择)
--p2p 与目标建立点对点连接
--stun-server ADDRESS
设置与--p2p一起使用的STUN服务器地址
--relay address,username,password,turn-{udp,tcp,tls}
添加与--p2p一起使用的中继
-O FILE, --options-file FILE
包含额外命令行选项的文本文件
--version 显示程序版本号并退出
-a, --applications 只列出应用程序
-i, --installed 包括所有已安装的应用程序
-j, --json 以JSON格式输出结果

操作模式:

操作模式 描述 优点 主要用途
CLI(命令行)模式 通过命令行直接将JavaScript脚本注入进程中,对进程进行操作 便于直接注入和操作 在较小规模的操作或者需求比较简单的场景中使用
RPC模式 使用Python进行JavaScript脚本的注入工作,实际对进程进行操作的还是JavaScript脚本,可以通过RPC传输给Python脚本来进行复杂数据的处理 在对复杂数据的处理上可以通过RPC传输给Python脚本来进行,有利于减少被注入进程的性能损耗 在大规模调用中更加普遍,特别是对于复杂数据处理的需求

注入模式与启动命令:

注入模式 描述 命令或参数 优点 主要用途
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已经启动,或者我们只关心特定时刻或特定功能的行为时使用

Spawn模式

1
frida -U -f 进程名 -l hook.js

attach模式 :

1
frida -U 进程名 -l hook.js

frida_server自定义端口

1
2
3
4
5
frida server 默认端口:27042

taimen:/ $ su
taimen:/ # cd data/local/tmp/
taimen:/data/local/tmp # ./fs1280 -l 0.0.0.0:6666

logcat |grep "D.zj2595"日志捕获

adb connect 127.0.0.1:62001模拟器端口转发

基础语法

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
function main(){
Java.perform(function(){
hookTest1();
});
}
setImmediate(main);

Frida常用API

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//定义一个名为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;
}
}

frida -U wuaipojie -l hook.js

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
//定义一个名为hookTest1的函数
function hookTest1(){
//获取一个名为"类名"的Java类,并将其实例赋值给JavaScript变量utils
var utils = Java.use("com.zj.wuaipojie.Demo");
//修改"类名"的"method"方法的实现。这个新的实现会接收两个参数(a和b)
utils.a.implementation = function(str){
//将参数a和b的值改为123和456。
// a = 123;
// b = 456;
//调用修改过的"method"方法,并将返回值存储在`retval`变量中
str = "22"
var retval = this.a(str);
//在控制台上打印参数a,b的值以及"method"方法的返回值
console.log(str, retval);
//返回"method"方法的返回值
return "retval";
}
}


function main(){
Java.perform(function(){
hookTest1();
});
}
setImmediate(main);

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);
}
}

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

Hook构造函数

1
2
3
4
5
6
7
8
9
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
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

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

objection环境配置

已不更新,要和frida的版本匹配

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

安装不下来,简单介绍下吧

objection简单使用

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

objection内存漫游

内存搜刮类实例

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

  1. hook类的所有方法

    1
    android hooking watch class 类名
  2. hook方法的参数、返回值和调用栈

    1
    android hooking watch class_method 类名.方法名 --dump-args --dump-return --dump-backtrace
  3. hook 类的构造方法

    1
    android hooking watch class_method 类名.$init
  4. hook 方法的所有重载

    1
    android hooking watch class_method 类名.方法名

ZenTracer

现在14.2.18版本很难装上,过时了,如果使用15版本的frida会hook不了,只需要

image-20250507104910225

再把trace.js放进去就行

image-20250507104811982

r0trace

image-20250507112208591