Frida保姆级教程
Hook原理
在程序运行时通过修改函数地址或者替换函数指针,实现当程序调用函数的时候跳转到自定义的函数内,实现Hook,简单来说就是自定义一个函数,用于替换掉原本执行的函数,需要注意的是,自定义的函数中参数和返回值要与原函数一致,因为程序上下文的传参是规定好的,如果不同会报错。
配置Hook环境
设备
PC
雷电模拟器-9.1.28
Sdk工具-Adb
下载模块
uname -m
x86-64表示64位,i686或i386标识32位 在后续hook应用中可以用作条件判断语句
GitHub仓库地址:
配置模拟器环境
开启模拟器root权限
查看模拟器对应的系统架构
从github上下载对应的系统架构的frida-server文件
Win+R打开cmd窗口
adb push xxx/xxx/frida-server /data/local/tmp #将文件推送到tmp目录下
adb shell #进入模拟器shell页面,因为Android使用Linux内核,大多数命令都可以使用
su #管理员用户 同linux一样
chmod 777 frida-server #文件赋予权限
配置PC端环境
安装Python环境,可以通过官网或者清华镜像源下载对应系统的安装包
pip install frida==x.x.x
这里安装的版本需要与从仓库中下载的server文件上的版本号相同
Win+R打开cmd窗口进行测试 如下输出即为安装完毕
pip show frida
编写一个Js脚本进行测试
Java.perform(function() { const MainActivity = Java.use('com.example.hotfix.MainActivity'); MainActivity.getarrly.implementation = function() { console.log('getarrly() 方法被调用'); printStack(); this.getarrly(); };});
//打印调用栈
function printStack() {
Java.perform(function () {
var Exception = Java.use("java.lang.Exception");
var ins = Exception.$new("Exception");
var straces = ins.getStackTrace();
if (straces != undefined && straces != null) {
var strace = straces.toString();
var replaceStr = strace.replace(/,/g, "\r\n");
console.log("=============================Stack strat=======================");
console.log(replaceStr);
console.log("=============================Stack end=======================\r\n");
Exception.$dispose();
}
});
}
该脚本用于打印当MainActivity中getarrly()方法被调用时打印调用堆栈,主要用于追踪数据传输以及方法的调用
Frida使用方法
frida -U -l exploit.js -f xxx #启动app并注入脚本
frida-ps -U
frida-ps -Ua
frida -U -l exploit.js "XXXX" #app已经启动 注入脚本
Hook技术实战应用
现在需要你找出在Android系统中 启动一个App最先调用的方法是什么 并且通过Hook的形式实现查看该App的网络请求地址
采用如下思路
1:hook关键的类以及方法
2:打印出关键方法的堆栈调用信息
3:分析打印出的调用堆栈查找出最快启动的方法
4:打印出App调用的Android系统Api以及网络请求的库
5:通过查看网络请求的库从而编写Js脚本实现将该App发送的网络请求进行打印
6:使用Python编写脚本将请求地址进行去重
将App拖入Jadx进行反编译查看信息
在AndroidManifest.xml文件中有个标签为application,name字段下的值即为该App第一个启动的Application(这个Application不一定每一个App都有)根据这一点进入这个App进行Hook方法的确定
确定好需要Hook的方法
编写Js脚本打印堆栈信息
Java.perform(function() {
const MainActivity = Java.use('com.sevengms.myframe.MyApplication');
MainActivity.attachBaseContext.overload('android.content.Context').implementation = function(base) {
console.log(' 方法被调用');
printStack();
return this.attachBaseContext(base);
};
});
//打印调用栈
function printStack() {
Java.perform(function () {
var Exception = Java.use("java.lang.Exception");
var ins = Exception.$new("Exception");
var straces = ins.getStackTrace();
if (straces != undefined && straces != null) {
var strace = straces.toString();
var replaceStr = strace.replace(/,/g, "\r\n");
console.log("=============================Stack strat=======================");
console.log(replaceStr);
console.log("=============================Stack end=======================\r\n");
Exception.$dispose();
}
});
}
使用Frida进行Hook测试
这里需要注意的是 需要提前使用adb进入模拟器运行frida-server文件 之后窗口不要关闭 新开一个窗口去运行frida脚本
分析打印的堆栈信息
由调用堆栈可得最开始启动的方法名为main,该方法在ZygoteInit这个类里,在Android系统中Zygote进程(又叫孵化进程)为所有应用进程的父进程,在系统创建时进行创建,再通过fork机制去创建新的应用进程,所有使用同样的方法去hook不同的App,最先启动的方法都是这个作为入口函数的main
编写Js脚本打印出App调用的系统Api以及用作网络请求的第三方库
// android_api_logger.js
Java.perform(function () {
// 需要监控的系统API列表(可根据需求扩展)
const targetAPIs = [
'java.net.URL.openConnection',
'okhttp3.OkHttpClient.newCall',
'android.webkit.WebView.loadUrl',
'javax.net.ssl.HttpsURLConnection.getInputStream',
'android.net.http.X509TrustManagerExtensions.checkServerTrusted'
];
// 动态监控未明确指定的类
const dynamicClasses = [
/^okhttp3\./,
/^com.android.okhttp.internal/,
/^java\.net\./,
/^android\.net\./,
/^android\.webkit\./
];
// 监控通用网络相关方法
const commonMethods = [
'connect',
'execute',
'enqueue',
'loadUrl',
'getInputStream',
'doOutput'
];
// 记录已Hook的类和方法
const hookedMethods = new Set();
// Hook方法的核心函数
function hookMethod(className, methodName) {
const key = `${className}.${methodName}`;
if (hookedMethods.has(key)) return;
try {
const targetClass = Java.use(className);
const overloads = targetClass[methodName].overloads;
overloads.forEach(function (overload) {
overload.implementation = function () {
// 打印调用信息
console.log(`\n[${className}] 调用方法: ${methodName}`);
// 打印参数
const args = Array.prototype.slice.call(arguments);
args.forEach((arg, index) => {
console.log(` 参数 ${index + 1}: ${arg}`);
});
// 打印堆栈跟踪(可选)
// console.log(Java.use("android.util.Log").getStackTraceString(
// Java.use("java.lang.Throwable").$new()
// ));
// 调用原始方法
return this[methodName].apply(this, arguments);
};
});
hookedMethods.add(key);
console.log(`成功Hook: ${key}`);
} catch (e) {
// console.log(`Hook失败: ${key} - ${e}`);
}
}
// Hook预定义的目标API
targetAPIs.forEach(api => {
const [className, methodName] = api.split('.');
hookMethod(className, methodName);
});
// 动态查找并Hook网络相关类
Java.enumerateLoadedClasses({
onMatch: function (className) {
// 检查是否匹配动态类模式
if (dynamicClasses.some(regex => regex.test(className))) {
const targetClass = Java.use(className);
const methods = targetClass.class.getDeclaredMethods();
// Hook类中的所有方法
methods.forEach(method => {
const methodName = method.getName();
if (commonMethods.includes(methodName)) {
hookMethod(className, methodName);
}
});
}
},
onComplete: function () {}
});
// 监控SSL/TLS握手
const sslContextClass = Java.use("javax.net.ssl.SSLContext");
sslContextClass.init.overload(
'[Ljavax.net.ssl.KeyManager;',
'[Ljavax.net.ssl.TrustManager;',
'java.security.SecureRandom'
).implementation = function (km, tm, sr) {
console.log("\n[SSLContext] 初始化SSL上下文");
if (tm) {
tm.forEach(manager => {
console.log(` 使用TrustManager: ${manager.$className}`);
});
}
return this.init(km, tm, sr);
};
});