安卓APP的hook安装与使用

该文章已生成可运行项目,

frida

1 frida安装与使用

如果安卓版本比较低的话,最新版frida不是很稳定。推荐安卓7、8安装frida 12.8.0版本,安卓10/frida14,安卓12/frida16。

1.1、安装手机的frida

adb shell getprop ro.product.cpu.abi
PS C:\Users\Administrator> adb shell getprop ro.product.cpu.abi  // 查看Androld手机设备设置
arm64-v8a  // CPU架构

1.2 下载frida

官网地址:https:/github.com/frida/frida/releases
一定要对应的CPU架构
在这里插入图片描述
目前用的最多的是frida,frida-ps,frida-trace

  • frida 是hook命令,
  • frida-ps 是查看进程的,
  • frida-trace 是跟踪函数调用。

一般来讲模拟器使用的是x86或x86_64的架构,而真机就是 arm或arm64架构。这里返回的是x86_64,那么我们下载x86_64的。

1.3 安装与使用

下载后解压出来只有一个文件,需要把它推送到模拟器中/data/loacl/tmp下

	# 确保设备已通过USB连接并启用调试模式,执行:adb devices 验证连接。
	# 若提示`permission denied`,尝试:adb root 后再执行后续步骤。  
1. adb push frida-server-17.0.0 /data/local/tmp/    # 推送文件到设备    
	> e:\frida-server-17.0.0: 1 file pushed, 0 skipped. 66.2 MB/s (52018928 bytes in 0.749s)
	# 表示成功
2. adb shell   # 进入ADB Shell  
3. su  #切换管理员权限  
	> flame:/ #  执行后会出现一下效果表示root成功了
4. cd /data/local/tmp/  # 进入目标目录  
5. chmod 777 /data/local/tmp/frida-server-17.0.0  #  修改文件权限  
	# 注意:chmod 777 会开放全部权限,生产环境建议使用最小权限原则(如755)。  
6. ./frida-server-17.0.0 运行程序  
	# 避免检测,可以不使用这个端口启动,默认启动责使用 adb forward tcp:27042 tcp:27042
7. ./frida-server -10.0.0.0:6666
	# 端口转发,默认 adb forward tcp:27042 tcp:27042
8. adb forward tcp:6666 tcp:6666

在PC上安装Python的运行环境,安装完成后执行下面的命令安装frida

frida-tools==XXX
pip install frida==XXX

注意: server与client版本必须保持一致

2 Frida 命令列表

Frida 提供了一系列命令行工具,用于与目标进程交互、脚本注入和管理。以下是常用的 Frida 命令及其功能:

英文选项中文翻译
--version显示程序版本
-h, --help显示帮助信息
-D ID, --device=ID连接到指定设备
-U, --usb连接到USB设备
-R, --remote连接到远程服务器
-H HOST, --host=HOST连接到指定主机的远程服务器
-f FILE, --file=FILE启动指定文件
-n NAME附加到指定名称的进程
-p PID附加到指定PID的进程
--debug启用调试器
--enable-jit启用即时编译
-l SCRIPT加载本地脚本
-c URI加载CodeShare脚本
-e CODE执行单行代码
-q安静模式
--no-pause不暂停主线程
-o LOGFILE输出到日志文件

2.1 基本命令

frida --help
显示 Frida 的帮助信息,列出所有可用的命令和选项。

frida -l <script.js> -n <process_name>
将指定的 JavaScript 脚本注入到目标进程(通过进程名指定)。

frida -l <script.js> -p <pid>
将指定的 JavaScript 脚本注入到目标进程(通过进程 ID 指定)。

frida -U -l <script.js> -n <process_name>
在已连接的 USB 设备上注入脚本(-U 表示 USB 设备)。

frida-trace -n <process_name> -i <function_name>
动态跟踪目标进程中的函数调用(通过函数名指定)。

2.2 设备管理

frida-ls-devices
列出所有可连接的设备(USB、本地、远程)。

frida-ps
列出当前设备上运行的进程。

frida-ps -U
列出 USB 设备上运行的进程。

frida-ps -a
列出所有进程,包括系统进程。

2.3 脚本调试

frida --runtime=duk -l <script.js> -n <process_name>
使用 Duktape JavaScript 引擎运行脚本(默认引擎)。

frida --runtime=v8 -l <script.js> -n <process_name>
使用 V8 JavaScript 引擎运行脚本(性能更高)。

frida -f <executable> -l <script.js>
启动目标可执行文件并注入脚本。

2.4 高级功能

frida-discover
发现目标进程中的模块和函数。

frida-kill <pid>
终止目标进程(通过进程 ID 指定)。

frida-repl -n <process_name>
启动交互式 REPL(Read-Eval-Print Loop)与目标进程交互。

2.5 附加选项

--debug
启用调试模式,输出更多日志信息。

--no-pause
注入脚本后不暂停目标进程。

--enable-jit
启用 JavaScript JIT 编译(提升性能)。

3 常见用法示例

动态跟踪函数调用:
frida-trace -n Telegram -i "recv*"
跟踪 Telegram 进程中所有以 recv 开头的函数调用。

注入脚本到 Android 应用:
frida -U -l hook.js -n com.example.app
在 USB 连接的 Android 设备上注入 hook.jscom.example.app 进程。

启动 REPL 交互:
frida-repl -n Calculator
与 Windows 计算器进程交互并动态执行脚本。

3.1 Frida两种操作模式

操作模式说明
CLI命令行Javascript脚本注入进程
RPCPython进行Javascript脚本注入

3.2 Frida 操作 APP 的两种方式

方式名称方式说明CLI下启动方式使用举例
spwan将启动APP的权利交由Frida来控制。不管APP是否启动,都会重新启动APP。-f参数指定包名frida -U -l myhook.js -f 包名 --no-pause
attach建立在目标APP已经启动的情况下,Frida通过ptrace注入程序从而执行Hook的操作不加-f参数frida -U -l myhook.js 包名
spawn模式详解

原理
启动一个新的进程并将其挂起,同时注入 Frida 的 JavaScript 代码,注入完成后调用resume恢复进程的正常运行。

命令解析

  • frida -U -l myhook.js -f com.xxx.xxxx --no-pause
    • -U 用于连接 USB 设备;
    • -l 用于指定要加载的 Frida 脚本(这里是myhook.js );
    • -f 用于指定要重启并注入脚本的进程(通过应用包名指定,这里是com.xxx.xxxx ) ;
    • --no-pause 表示自动运行程序,即注入脚本后不暂停应用启动,直接让应用继续执行。

myhook.js 脚本

Java.perform(function () {
    // 假设应用启动时会调用某个类的初始化函数来加载配置
    var targetClass = Java.use('com.example.demoapp.utils.ConfigLoader'); 
    targetClass.init.implementation = function () {
        console.log('Hooked: ConfigLoader.init is called');
        // 执行原函数逻辑
        this.init(); 
    };
});

python脚本

import frida
import sys
def on_message(message, data):
    print("message", message)
    print("data", data)
# 通过Spawn模式启动一个新的应用程序进程,并在该进程中加载Frida脚本
device = frida.get_usb_device()
pid = device.spawn(["com.luoge.com"])
device.resume(pid) # 恢复应用程序的执行
session = device.attach(pid)
with open("myhook.js") as f:
    script = session.create_script(f.read())
script.on("message", on_message)
script.load()
sys.stdin.read() # 阻塞主线程,以保持脚本运行
attach模式详解

原理
attach模式下,Frida利用ptrace机制修改进程内存,将JavaScript脚本注入到处于启动状态的Android目标进程中。ptrace是Linux系统提供的一种进程调试机制,允许一个进程(调试器)控制另一个进程(被调试进程),能读写被调试进程的内存和寄存器等 ,这使得Frida可注入脚本并执行Hook等操作。

命令解析

  • frida -U -l myhook.js com.xxx.xxxx 这条命令用于在attach模式下注入脚本。
    • -U :指定操作对象为USB连接的设备。在Frida操作中,当设备通过USB与开发机相连时,使用该参数可准确连接到对应的设备进行后续操作 。
    • -l :用于指定要加载的JavaScript脚本文件,这里myhook.js就是具体的脚本文件,该脚本中可编写各种Hook函数、逻辑代码来实现对目标进程的监控、修改等功能。
    • com.xxx.xxxx :指定目标进程名。若想指定进程的PID,可使用-p选项。例如frida -U -l myhook.js -p <pid> 。可通过frida-ps -U命令查看当前通过USB连接设备上正在运行的进程列表及其PID信息,方便准确指定目标进程。

myhook.js 脚本

Java.perform(function () {
    // 假设应用启动时会调用某个类的初始化函数来加载配置
    var targetClass = Java.use('com.example.demoapp.utils.ConfigLoader'); 
    targetClass.init.implementation = function () {
        console.log('Hooked: ConfigLoader.init is called');
        // 执行原函数逻辑
        this.init(); 
    };
});

python脚本

import frida
import sys
def on_message(message, data):
    print("message", message)
    print("data", data)
# 通过attach模式启动一个新的应用程序进程,并在该进程中加载Frida脚本
device = frida.get_usb_device()
pid = device.get_process("com.jjj.com").pid # 获取目标应用程序的PID
session = device.attach(pid) # 附加到目标进程
with open("myhook.js") as f:
    script = session.create_script(f.read())
script.on("message", on_message)
script.load()
sys.stdin.read() # 阻塞主线程,以保持脚本运行

4 Hook Java方法

这段内容是对使用Frida进行Java方法Hook时关键概念的讲解,以下是结合示例的进一步说明:

名称说明
Java.use()作用是获取Java类对象,获取后就能在Frida的JavaScript环境中对该类进行操作。
Java.perform()用于在Frida的JavaScript环境中创建一个执行上下文,在这个上下文中可以安全地对Java类和方法进行操作。因为Frida注入的脚本执行环境与Java虚拟机环境交互需要特定的上下文设置,Java.perform() 就提供了这样的环境。
implementation该属性用于重新定义被Hook方法的实现逻辑。通过它可以在原方法执行前后添加自定义代码,甚至完全替换原方法的行为。
this 和 参数在自定义的implementation函数中,this指向原方法所属的对象实例,可以用来调用原方法或访问对象属性;函数的参数则是原方法被调用时传入的参数,可用于修改参数值等操作。
调用原始方法在自定义实现中,通过this.原方法名.apply(this, arguments) 形式调用原始方法,确保原方法的逻辑也能执行,避免完全覆盖原方法功能。arguments是 JavaScript 中保存函数调用时传入参数的类数组对象。

示例

Java.perform(() => {
    // 获取java.util.Date类对象
    const Date = Java.use('java.util.Date'); 
    // 调用Date类的方法,比如获取当前时间戳
    const now = Date.now(); 
    console.log('Current timestamp:', now);
});

// 不使用Java.perform() 直接操作会报错
// const Date = Java.use('java.util.Date'); 这样写是错误的
Java.perform(() => {
    const Date = Java.use('java.util.Date'); 
    // 正确在上下文中操作Java类
}); 

// 示例:假设要 Hook java.util.Date 的 getTime 方法,在获取时间戳前后添加日志:
Java.perform(() => {
    const Date = Java.use('java.util.Date'); 
    Date.getTime.implementation = function () {
        console.log('Before getting time');
        const result = this.getTime();
        console.log('After getting time');
        return result;
    };
    const date = new Date();
    date.getTime();
});

// 示例:假设Hook一个带参数的方法`add` (比如在某个自定义的`Calculator`类中) :
Java.perform(() => {
    const Calculator = Java.use('com.example.Calculator'); 
    Calculator.add.implementation = function (a, b) {
        // 可以修改传入参数
        a = a + 1; 
        b = b + 1;
        // 使用this调用原始方法
        const result = this.add(a, b); 
        return result;
    };
    const calculator = new Calculator();
    const sum = calculator.add(1, 2);
    console.log('Sum:', sum);
});

// 示例:还是以`Calculator`类的`add`方法为例:
```javascript
Java.perform(() => {
    const Calculator = Java.use('com.example.Calculator'); 
    Calculator.add.implementation = function (a, b) {
        // 这里也可以不修改参数直接调用原始方法
        const result = this.add.apply(this, arguments); 
        return result;
    };
    const calculator = new Calculator();
    const sum = calculator.add(1, 2);
    console.log('Sum:', sum);
});

4.1 重载函数常用的类型

  • 提示:如果出现以上没有的类型,则app源码文件.class.对象类型
java中的类型frida里面的类型
intint
floatfloat
booleanboolean
stringjava.lang.String
char[C
byte[B
listjava.util.List
HashMapjava.util.HashMap
ArrayListjava.util.ArrayList
JavaObjectjava.lang.Object
String[][Ljava.lang.String

4.2 载入类Java.use(类名位置.类名)

Java.use方法用于加载一个Java类,相当于Java中的Class.forName()。比如要加载一个string类:
var stringclass =ava.use("java.lang.string");
加载内部类:
var MyClass_InnerClass = Java.use("com.luoyesigiu.Myclass$InnerClass");

4.3 重载方法overload()

示例XXXX包下面的 Test.java
public class Test{
	public int a;
	private static int b = 200;
	public Test(int a){
		this.a=a
	};
	public int a(){
		return 10
	};
	public static void seta(int a){
		this.a = a
	};
	public void seta1(int a){
		this.a = a
	};
	public InnerTest(){
		public int a;
		public InnerTest(int a){
			this.a = a
		};
	};
}
示例Java类(Calculator.java)
import XXXX.Test
public class Calculator {
    public int add(int a, int b) {
        return a + b;
    };
    public float add(float a, float b) {
        return a + b;
    };
    public String add(String a, String b) {
        return a + b;
    };
    public String add(Test test) {
        return test.a;
    };
}
Hook方法重载的JavaScript脚本
  • Java.perform():创建一个Frida的JavaScript执行上下文,用于安全地操作Java对象和方法。
  • Calculator = Java.use('Calculator');:获取Calculator类的代理对象,以便在JavaScript中操作该类。
  • Calculator.add.overload('int', 'int').implementation = function() {... }:使用overload方法指定要Hook的特定重载方法,这里通过传入参数类型来区分不同的重载版本,并重新定义其实现,在原方法执行前后添加自定义的日志打印逻辑。

通过这种方式,就可以针对Java中重载的方法,使用Frida进行分别Hook和自定义处理。

指定单个重载调用
Java.perform(() => {
    // 获取Calculator类
    const Calculator = Java.use('Calculator'); 
    // Hook int add(int a, int b) 方法
    Calculator.add.overload('int', 'int').implementation = function (a, b) {
        console.log('传递的参数 a:', a, ', b:', b);
        const result = this.add(a, b);
        console.log('执行后的结果 result:', result);
        return result;
    };

    // Hook float add(float a, float b) 方法
    Calculator.add.overload('float', 'float').implementation = function (a, b) {
        console.log('传递的参数, a:', a, ', b:', b);
        const result = this.add(a, b);
        console.log('执行后的结果, result:', result);
        return result;
    };

    // Hook String add(String a, String b) 方法
    Calculator.add.overload('java.lang.String', 'java.lang.String').implementation = function (a, b) {
        console.log('传递的参数, a:', a, ', b:', b);
        const result = this.add(a, b);
        console.log('执行后的结果:', result);
        return result;
    };
});

    // Hook int add(Test test) 方法
    Calculator.add.overload('XXXX.Test').implementation = function (arg) {
        console.log('传递的参数, arg:', arg);
        const result = this.add(arg);
        console.log('执行后的结果:', result);
        return result;
    };
});
指定全部重载调用 overloads,apply()
// 指定某个方法
Java.perform(() => {
    const Calculator = Java.use('Calculator');
    Calculator.add.overloads.forEach(overload => {
        overload.implementation = function() {
            console.log(JSON.stringify(Array.from(arguments))); // 打印每个参数
            const originalResult = overload.apply(this, arguments); // 传递参数调用结果
            console.log('Return value:', originalResult);
            return originalResult;
        };
    });
});

// 模糊匹配某个方法
Java.perform(() => {
	const Calculator = Java.use('Calculator'); 
	// 使用overloads函数获取所有重载方法
	var methods = targetClass['add'].overloads;
	console.log(methods);
	console.log(methods.length, '多少个重载方法');
	
	// 遍历所有的重载方法并进行钩子
	methods.forEach(function (method) {
	    method.implementation = function () {
	        console.log('方法被调用:', method);
	        // 在这里可以添加你的自定义逻辑
	        // 调用原始方法
	        return method.apply(this, arguments);
	    };
	});
	}

4.4 hook构造函数TestClass.$init

Java.perform(function() {
    // 获取目标类
    var TestClass = Java.use('Test');
    // Hook构造函数
    TestClass.$init.overload('int').implementation = function(a) {
        // 在构造函数执行前打印参数
        console.log('[+] 正在创建Test对象,参数a:', a);
        // 调用原始构造函数
        this.$init(a);
        // 在构造函数执行后打印对象状态
        console.log('[+] Test对象创建完成,成员变量a的值:', this.a.value);
        // 可以选择修改成员变量的值
        this.a.value = a * 2;
        console.log('[+] 修改后的成员变量a的值:', this.a.value);
    };
});
4.5 Hook主动调用

主动调用分2中情况,静态方法、实例方法,如果是Hook,是不区分静态和实例的。

// 静态方法调用
Java.perform(function() {
    // 获取目标类
    var TestClass = Java.use('Test');
    // 直接调用
	TestClass.seta(12)
});

// 实例方法调用
Java.perform(function() {
    // 获取目标类
    var TestClass = Java.use('Test');
    Java.choose("Test", {
        // 当找到匹配的实例时调用
        onMatch: function (obj) {
        	int a = 11;
            // 调用实例的 getInfo 方法并打印结果
            console.log("找到 Test 实例,其信息为:", obj.seta1(a));
        },
        // 搜索完成时调用
        onComplete: function () {
            console.log("内存中 Test 类实例搜索操作完毕");
        }
    });
})
  1. 获取类引用:使用 Java.use("Test") 获取 Test 类的引用,以便后续操作。
  2. 搜索实例:通过 Java.choose("Test", { ... }) 在 Java 堆中搜索 Test 类的实例。
  3. 处理找到的实例:在 onMatch 回调函数中,对找到的每个 Test 实例调用 seta1 方法,并将结果打印到控制台。
  4. 搜索完成处理:在 onComplete 回调函数中,打印一条消息表示搜索操作已经完成。
    通过这个示例,我们可以在运行时动态地对 Test 类的实例进行监控和分析,获取其相关信息,这对于逆向工程和应用程序调试非常有帮助。

5 Hook Java类

获取和修改类的字段、hook内部类、枚举所有加载的类

5.1 修改类字段
// 静态字段的修改
Java.perform(function() {
	var TestClass = Java.use('Test');
	TestClass.b.value = '100';
});
// 非静态字段的修改
Java.perform(function() {
    Java.choose("Test", {
        onMatch: function (obj) {
        	obj._a.value = 100; // 字段名与方法名相同时,前面需要加下划线区分
        },
        // 搜索完成时调用
        onComplete: function () {
            console.log("内存中 Test 类实例搜索操作完毕");
        }
    });
})
5.2 hook内部类

需要再类和内部类之间加一个$符号,innerTestClass.$init进行内部构造函数的重载调用

Java.perform(function() {
	var innerTestClass = Java.use('Test$InnerClass');
	innerTestClass.$init.overload('int').implementation = function(a){
		this.$init(a) // 调用构造函数
		return this.$init(a)
	};
});
5.3 枚举所有加载类和方法

枚举所有加载类——无法获取构造方法
getDeclaredMethods() 获取该类的所有方法,返回一个对象数组
getName 获取当前方法的名称

Java.perform(function() {
	var TestClass = Java.use('Test');
	var methods = TestClass.class.getDeclaredMethods();
	for (var i=0; i<methods.length; i++ ){
		console.log(methods[i].getName);
	};
});
5.4 枚举所有加载类

首先枚举类的所有方法和HOOK类的所有重载方法写出来

Java.perform(function() {
	var TestClass = Java.use('Test');
	var methods = TestClass.class.getDeclaredMethods();
	for (var i=0; i<methods.length; i++ ){
		var methodName = methods[i].getName;
		console.log("方法名:",methodName )

		for (var j=0; j<TestClass[methodName].overloads.length; j++ ){
			TestClass[methodName].overloads[k].implementation = function(a){
				for (var k=0; k<arguments.length; k++){
					console.log(arguments[k])
				}:
				return this[methodName].apply(this, arguments)
			};
		};
	};
});

apk下载网站

https://www.liqucn.com/
https://www.wandoujia.com/
http://www.2265.com/
本文章已经生成可运行项目
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值