环境准备
- 已root的Android环境, 真机 或 虚拟机
配置frida
-
pip install frida
: frida 的 python 接口, 可以省略, 因为安装frida-tools
时会自动安装, 但是当frida
升级时, 为统一frida-server
和frida
的版本, 需要手动升级pip install -U frida
-
pip install frida-tools
: frida cli 工具 -
下载对应Android系统的 frida-server github.com/frida/frida…
- 使用 adb 连接 Android 设备
adb devices
/adb connect 192.168.1.12:5555
- 将 frida-server 文件解压出来并 push 到 Android 设备中:
adb push frida-server /data/local/tmp
adb shell
下获取root
权限su
- 修改 frida-server 权限, 并转移至
/system/bin
路径下chmod a+x frida-server
mv /data/local/tmp/frida-server /system/bin
, 不转移可能会出现奇怪问题
- 运行 frida-server
/system/bin/frida-server &
- 端口转发
adb forward tcp:27042 tcp:27042
- 网上也有看到同时转发另一个端口的
adb forward tcp:27043 tcp:27043
, 暂时不知道默认含义
- 使用 adb 连接 Android 设备
-
如果出现 无法 connect frida server 相关错误
adb forward tcp:27042 tcp:27042
- 关闭 MagiskHide
-
修改默认端口举例
# adb frida-server -l 0.0.0.0:9999 -D # pc cmd # adb forward tcp:7777 tcp:9999 adb forward tcp:27042 tcp:9999 # frida-ps -H localhost:7777 # 如果本地使用默认端口 27042, 但是由于frida的自身机制, 依然需要进行指定, 因为 frida-server 用的不是默认端口 frida-ps -H localhost:27042
frida cli
- 获取远端(Android设备)进程
frida-ps -U
- 管理远端进程
frida-kill -U <process>
adb shell
- 获取全部进程
ps -A
ps -A | grep frida
- 杀死进程
kill -9 <process>
- 杀死frida
ps -A | grep frida | awk '{ print "kill -9 " $2} ' | sh
- 重启 adb
adb kill-server
: 重启之后需重新转发端口adb forward tcp:27042 tcp:27042
直接使用 frida 操作
略
代码示例
Python API
"""frida"""
import sys
import frida
def on_message(message, data):
"""..."""
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
device = frida.get_usb_device()
pid = device.spawn('com.tencent.mm')
device.resume(pid)
session = device.attach(pid)
jscode = open('java.js').read()
# 开启调试
# script = session.create_script(jscode, runtime='v8')
# session.enable_debugger()
script = session.create_script(jscode)
script.on('message', on_message)
print('[*] Running Frida')
script.load()
sys.stdin.read()
JavsScript API
frida 代码提示: github.com/DefinitelyT…
示例1 - hook 静态函数 或 没有重载的方法
Java.perform(function () {
var CommUrlApi = Java.use('com.tencent.mm.ui.CommUrlApi')
CommUrlApi.MD5.implementation = function (s) {
console.log(s)
let rt = this.MD5(s)
console.log(rt)
return rt
}
})
示例2 - hook StringBuilder
const jStringBuilder = Java.use('java.lang.StringBuilder');
jStringBuilder.toString.implementation = function () {
let rt = this.toString();
console.log(rt);
return rt;
}
示例3 - hook 重载方法
当一个方法有重载, 那么不管你想hook的方法有没有参数, 你都必须指定 override
, 否则会报错
{
'type': 'error',
'description': "Error: a(): has more than one overload, use .overload(<signature>) to choose from:\n\t.overload()\n\t.overload('java.lang.String')\n\t.overload('java.lang.String', 'java.lang.String')\n\t.overload('java.util.regex.Pattern', 'java.lang.String')\n\t.overload('int', 'java.lang.String')",
'stack': "Error: a(): has more than one overload, use .overload(<signature>) to choose from:\n\t.overload()\n\t.overload('java.lang.String')\n\t.overload('java.lang.String', 'java.lang.String')\n\t.overload('java.util.regex.Pattern', 'java.lang.String')\n\t.overload('int', 'java.lang.String')\n at X (frida/node_modules/frida-java-bridge/lib/class-factory.js:563)\n at K (frida/node_modules/frida-java-bridge/lib/class-factory.js:558)\n at set (frida/node_modules/frida-java-bridge/lib/class-factory.js:925)\n at <anonymous> (/script1.js:32)\n at <anonymous> (frida/node_modules/frida-java-bridge/lib/vm.js:11)\n at _performPendingVmOps (frida/node_modules/frida-java-bridge/index.js:238)\n at <anonymous> (frida/node_modules/frida-java-bridge/index.js:213)\n at <anonymous> (frida/node_modules/frida-java-bridge/lib/vm.js:11)\n at _performPendingVmOpsWhenReady (frida/node_modules/frida-java-bridge/index.js:232)\n at perform (frida/node_modules/frida-java-bridge/index.js:192)\n at <eval> (/script1.js:36)",
'fileName': 'frida/node_modules/frida-java-bridge/lib/class-factory.js',
'lineNumber': 563,
'columnNumber': 1
}
在报错信息中, 可以找到我们 override
所需的参数
Java.use('com.iflytek.ys.common.k.d.a.b').a.overload().implementation = function () {
let ret = this.a();
console.log(ret);
return ret
}
示例4 - hook 静态方法
const jAntiHacker = Java.use('com.iflytek.yousheng.AntiHacker')
jAntiHacker.a.implementation = function (b) {
let ret = jAntiHacker.a(b);
console.log(12345);
console.log(ret);
return ret;
}
示例5 - apply arguments
const encrypt = Java.use('com.iflytek.readassistant.biz.b.d');
// encrypt.a.overload('com.iflytek.ys.core.g.c.d', 'java.lang.String', '[B', 'boolean', 'java.lang.String', 'com.iflytek.readassistant.biz.b.d$b').implementation = function () {
encrypt.a.overload('java.lang.String', '[B', '[B', 'boolean').implementation = function (str, b1, b2) {
console.log(str);
console.log(jString.$new(b1))
if (b2 == null) {
console.log('========-=-=-=-=');
}
return this.a.apply(this, arguments);
}
示例6 - 打印堆栈
Java.perform(function() {
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()))
});
一些有用信息
Java
数据类型 对应 Frida
Java | Frida | Java | Frida |
---|---|---|---|
boolean | boolean | boolean[] | [Z |
byte | byte | byte[] | [B |
short | short | short[] | [S |
int | int | int[] | [I |
long | long | long[] | [J |
float | float | float[] | [F |
double | double | double[] | [D |
java.lang.String | java.lang.String | java.lang.String[] | [Ljava.lang.String; |
在JavaScript
脚本判断数据类型
Java.perform(function () {
const jFrida = Java.use('com.example.file01.Frida');
const jString = Java.use('java.lang.String');
const jArrays = Java.use('java.util.Arrays');
function viewFrida(funName, args) {
console.log(funName, args.length);
let ret = eval(`jFrida.${funName}`).apply(jFrida, args);
args[args.length] = ret;
for (let i = 0; i <= args.length; i++) {
let value = args[i];
if (value === undefined) {
console.log(i, value);
continue;
}
let type = value.constructor.name;
switch (type) {
case 'x':
// java object
console.log(i, value.$className, value.toString());
break;
case 'Array':
// java object array
// todo
console.log(i, value.$w.$className, jArrays.toString(value));
break;
case 'Boolean':
// boolean
console.log(i, type, value);
break;
case 'g':
// base data type array
type = value.$s.typeName;
switch (type) {
case 'byte':
// byte[]
console.log(i, `${type}[]`, jString.$new(value), value);
break;
case 'char':
// char[]
console.log(i, `${type}[]`, jString.$new(value), value);
break;
case 'boolean':
// boolean[]
console.log(i, `${type}[]`, value);
break;
case 'short':
// short[]
console.log(i, `${type}[]`, value);
break;
case 'int':
// int[]
console.log(i, `${type}[]`, value);
break;
case 'long':
// long[]
console.log(i, `${type}[]`, value);
break;
case 'float':
// float[]
console.log(i, `${type}[]`, value);
break;
case 'double':
// double[]
console.log(i, `${type}[]`, value);
break;
default:
console.log(i, type, value, 'unknown');
break;
}
break;
case 'Number':
// byte, short, int, float, double
console.log(i, type, value);
break;
case 'String':
// char, String,
console.log(i, type, value);
break;
case 'Int64':
// char, String,
console.log(i, 'long', value);
break;
case 'Object':
// char, String,
console.log(i, type, value);
break;
default:
console.log(i, type, value, 'unknown');
break;
}
}
console.log();
return ret;
}
jFrida.Test.overload('com.example.file01.MainActivity', '[Lcom.example.file01.MainActivity;', 'boolean', '[Z', 'byte', '[B', 'char', '[C', 'short', '[S', 'int', '[I', 'long', '[J', 'float', '[F', 'double', '[D', 'java.lang.String', '[Ljava.lang.String;').implementation = function () {
viewFrida('Test', arguments);
};
})
hook 技巧
Java.perform(function () {
const jClass = Java.use('com.iflytek.msc.MSC')
const jString = Java.use('java.lang.String');
const jArrays = Java.use('java.util.Arrays');
function viewFrida(funName, javaClass, args) {
console.log(funName, args.length);
let ret = eval(`javaClass.${funName}`).apply(javaClass, args);
args[args.length] = ret;
for (let i = 0; i <= args.length; i++) {
let value = args[i];
if (value === undefined || value === null) {
console.log(i, value);
continue;
}
let type = value.constructor.name;
switch (type) {
case 'x':
// java object
console.log(i, value.$className, value.toString());
break;
case 'Array':
// java object array
// todo
console.log(i, value.$w.$className, jArrays.toString(value));
break;
case 'Boolean':
// boolean
console.log(i, type, value);
break;
case 'g':
// base data type array
type = value.$s.typeName;
switch (type) {
case 'byte':
// byte[]
// console.log(i, `${type}[]`, jString.$new(value), value);
console.log(i, `${type}[]`, jString.$new(value, 'GB2312'), value);
break;
case 'char':
// char[]
console.log(i, `${type}[]`, jString.$new(value), value);
break;
case 'boolean':
// boolean[]
console.log(i, `${type}[]`, value);
break;
case 'short':
// short[]
console.log(i, `${type}[]`, value);
break;
case 'int':
// int[]
console.log(i, `${type}[]`, value);
break;
case 'long':
// long[]
console.log(i, `${type}[]`, value);
break;
case 'float':
// float[]
console.log(i, `${type}[]`, value);
break;
case 'double':
// double[]
console.log(i, `${type}[]`, value);
break;
default:
console.log(i, type, value, 'unknown');
break;
}
break;
case 'Number':
// byte, short, int, float, double
console.log(i, type, value);
break;
case 'String':
// char, String,
console.log(i, type, value);
break;
case 'Int64':
// char, String,
console.log(i, 'long', value);
break;
case 'Object':
// char, String,
console.log(i, type, value);
break;
default:
console.log(i, type, value, 'unknown');
break;
}
}
console.log();
return ret;
}
function hookMethod(funName, javaClass) {
console.log('hook method', funName);
eval(`javaClass.${funName}`).implementation = function () {
return viewFrida(funName, this, arguments);
};
}
// hook method QTTSSessionEnd
// hook method QTTSGetParam
// hook method QTTSAudioGet
// hook method QTTSTextPut
// hook method QTTSFini
// hook method QTTSInit
// hook method QTTSAudioInfo
// hook method QTTSSessionBegin
// jClass.$ownMembers.forEach(function (value) {
// if (value.startsWith('QTTS')) {
// hookMethod(value, jClass);
// }
// });
hookMethod('QTTSSessionBegin', jClass);
}
相关链接