frida hook java


Frida 启动

attach 启动

直接附加到指定包名的应用中

1
frida -U com.kevin.android -l hook.js

直接附加到当前应用中

1
frida -UF -l hook.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import sys
import time
import frida

def on_message(message,data):
print("message",message)
print("data",data)

device = frida.get_usb_device()
session = device.attach("com.kevin.demo1")

with open("./demo1.js","r") as f:
script = session.create_script(f.read())

script.on("message",on_message)
script.load()
sys.stdin.read()

spawn 启动

1
frida -U -f com.kevin.android -l demo1.js --no-pause
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import sys
import time
import frida

def on_message(message,data):
print("message",message)
print("data",data)

device = frida.get_usb_device()
pid = device.spawn(["com.kevin.demo1"])
device.resume(pid)
session = device.attach(pid)

with open("./rpc_demo.js",'r') as f:
script = session.create_script(f.read())

script.on("message",on_message)
script.load()

sys.stdin.read()

frida-server 自定义端口

frida server

更改 frida server 默认端口: 27042 并开启远程连接

1
2
3
4
5
6
7
8
9
adb shell
su -
cd /data/local/tmp

# 输入 wifiadb 对应的 ip 和自定义端口
./frida-server -l 192.168.0.1:6666

# 也可以使用默认端口启动
./frida-server -l 192.168.0.1

frida

frida 远程连接自定义端口

1
2
3
4
5
# 连接指定 6666 端口
frida -H 192.168.0.1:6666 com.demo1.app -l demo1.js

# 默认使用端口 27042
frida -H 192.168.0.1 -l demo1.js

python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# -*- coding: UTF-8 -*-

import frida, sys

jsCode = """
console.log("test");
"""

def message(message, data):
if message['type'] == 'send':
print(f"[*] {message['payload']}")
else:
print(message)
# ./fs120800 -l "0.0.0.0:6666"
# adb wifi 10.0.0.23
process = frida.get_device_manager().add_remote_device('127.0.0.1:6666').attach('com.kevin.app')
script = process.create_script(jsCode)
script.on("message",message)
script.load()
input()

Frida rpc 远程调用

python

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
37
38
39
40
41
42
43
44
45
46
47
48
49
import frida
import json
from flask import Flask, jsonify, request

def message(message, data):
if message['type'] == 'send':
print(f"[*] {message['payload']}")
else:
print(message)

# ./fs120800 -l "0.0.0.0:6666"
# adb wifi 10.0.0.123
# 远程 frida-server 路径 adb wifi 的 ip : frida-server 启动的端口
session = frida.get_device_manager().add_remote_device('10.0.0.123:6666').attach('com.example.demoso1')
with open("/Users/zhangyang/codes/fridaProject/rpcDemo/hook.js") as f:
jsCode = f.read()

# print("加载代码", jsCode)
script = session.create_script(jsCode)
script.on("message",message)
script.load()

# print("加密","1213")
# encodeResult = script.exports.invokemethod01("123")
# decodeResult = script.exports.invokemethod02(encodeResult)
# print(decodeResult)

app = Flask(__name__)

@app.route('/encrypt', methods=['POST'])#data解密
def decrypt_class():
data = request.get_data()
json_data = json.loads(data.decode("utf-8"))
postdata = json_data.get("data")
res = script.exports.invokemethod01(postdata)
return res


@app.route('/decrypt', methods=['POST'])#url加密
def encrypt_class():
data = request.get_data()
json_data = json.loads(data.decode("utf-8"))
postdata = json_data.get("data")
print(postdata)
res = script.exports.invokemethod02(postdata)
return res

if __name__ == "__main__":
app.run()

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
///<reference path='/Users/zhangyang/node_modules/@types/frida-gum/index.d.ts'/>

// 先 hook 方法 method01
// function hookmethod1(){
// Java.perform(function(){
// var targetClass = Java.use("com.example.demoso1.MainActivity");
// targetClass.method01.implementation = function(str){
// console.log("str is ", str);
// var result = this.method01(str);
// console.log("result is ", result);
// return result;
// }
// })
// };

// 主动调用
function fridamethod01(inputStr){
var result = null;
Java.perform(function(){
var targetClass = Java.use("com.example.demoso1.MainActivity");
result = targetClass.method01(inputStr);
});
return result;
}

function fridamethod02(inputStr){
var result = null;
// public native String method02(String str);
Java.perform(function(){
Java.choose("com.example.demoso1.MainActivity",{
onMatch: function(ins){
result = ins.method02(inputStr);
},
onComplete: function(){}
})
});
return result;
}

// 优先测试 js 中的主动调用
// function main(){
// console.log("你好 -> 结果为:", fridamethod01("你好"));
// console.log("27cae29a0913f6791705ca10be31a3e0 -> 结果为", fridamethod02("27cae29a0913f6791705ca10be31a3e0"))

// }
// setImmediate(main);

// 基于主动调用设置 rpc
rpc.exports = {
invokemethod01: fridamethod01,
invokemethod02: fridamethod02,
}

压力测试

tmp.json

1
{"data": "62feb9a98a01945ab06c0dd7823adc57"}

命令

1
siege -c30 -r1 "<http://127.0.0.1:5000/encrypt> POST < tmp.json"

nps 进行内网穿透

  1. nps server 启动
    mac: sudo nps start

  2. 新建客户端

    安卓手机连接客户端 ./npc -server=10.0.0.124:8024 -vkey=hm40rtjpf2j3c1up -type=tcp

  3. 给客户端添加和 frida server 的端口映射
    安卓手机启动 frida-server: ./fs12800 -l 0.0.0.0:6666

    将目标 frida-server 的端口映射到 56666 端口上

  4. python 脚本更改和 frida-server 的连接
    此时就可以将 frida-server 开放到公网了;

    1
    session = frida.get_device_manager().add_remote_device('10.0.0.124:56666').attach('com.example.demoso1')

Hook 普通方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function main(){
Java.perform(function(){
var UtilsClass = Java.use("com.kevin.app.Utils");
UtilsClass.getCalc.implementation = function (a,b){
// 打印信息
console.log('a:' + a + ' ' + 'b:' + b);
// 调用原方法获取结果
var value = this.getCalc(a, b);
console.log('result:',value);
// 修改返回值
return 123456;
}
})
}

setImmediate(main);

Hook 重载方法

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
37
38
39
40
41
42
function main(){
Java.perform(function(){
var UtilsClass = Java.use("com.kevin.app.Utils");

// 重载无参方法
UtilsClass.test.overload().implementation = function () {
console.log("hook overload no args");
return this.test();
}

// 重载有参方法 - 基础数据类型
UtilsClass.test.overload('int').implementation = function(num){
console.log("hook overload int args");
var myNum = 9999;
var oriResult = this.test(num);
console.log("oriResult is :" + oriResult);
return this.test(myNum);
}

// 重载有参方法 - 引用数据类型
UtilsClass.test.overload('com.kevin.app.Money').implementation = function(money){
console.log("hook Money args");
return this.test(money);
}

// hook 指定方法的所有重载
var ClassName = Java.use("com.xiaojianbang.app.Utils");
var overloadsLength = ClassName.test.overloads.length;
for (var i = 0; i < overloadsLength; i++){
ClassName.test.overloads[i].implementation = function () {
// 遍历打印 arguments
for (var a = 0; a < arguments.length; a++){
console.log(a + " : " + arguments[a]);
}
// 调用原方法
return this.test.apply(this,arguments);
}
}
})
}

setImmediate(main);

Hook 构造方法

1
2
3
4
5
6
7
8
9
10
11
12
function main(){
Java.perform(function (){
// hook 构造方法 $init
var MoneyClass = Java.use("com.kevin.app.Money");
MoneyClass.$init.overload().implementation = function(){
console.log("hook Money $init");
this.$init();
}
})
}

setImmediate(main);

Hook 对象

  1. 通过 Java.choose找到指定对象
  2. 通过Java.use找到对应的类, 在手动调用构造方法构造对象
  3. hook 动态方法, 此时的this就是对象本身;
  4. hook 以目标对象作为参数的方法, 此时该参数就是对象;

使用 choose 查找对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function main(){
Java.perform(function(){
// hook instance
Java.choose("com.xiaojianbang.app.Money",{
onMatch : function(instance){
console.log("find it!!", instance.getInfo());
// something to do...
},

onComplete: function(){
console.log("compelete!!!");
}
})
})
}

setImmediate(main);

使用 retain 保存对象

1
2
3
4
5
6
7
8
Java.perform(() => {
const Activity = Java.use('android.app.Activity');
let lastActivity = null;
Activity.onResume.implementation = function () {
lastActivity = Java.retain(this);
this.onResume();
};
});

Hook 动静态成员属性

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
function main(){
Java.perform(function(){
var MoneyClass = Java.use("com.xiaojianbang.app.Money");

// get static properties
// need to use .value
var ori_property = MoneyClass.flag.value;
console.log("ori_property: ", ori_property);

// change static properties
MoneyClass.flag.value = "change the value";
console.log("change to : ", MoneyClass.flag.value);

// get dynamic properties
Java.choose("com.xiaojianbang.app.Money",{
onMatch: function(instance){
instance.num.value = 50000;
// 当对象的成员属性和成员方法名重复时,成员属性前加`_`,进行区分
instance._name.value = "ouyuan";
console.log(instance._name.value, instance.num.value, instance.flag.value);
},

onComplete: function(){
console.log("complete!!")
}
})
})
}

setImmediate(main);

Hook 内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function main(){
Java.perfor(function(){
// hook 内部类
// 内部类使用$进行分隔 不使用.
var InnerClass = Java.use("com.xiaojianbang.app.Money$innerClass");
// 重写内部类的 $init 方法
InnerClass.$init.overload("java.lang.String","int").implementation = function(x,y){
console.log("x: ",x);
console.log("y: ",y);
this.$init(x,y);
}
})
}

setImmediate(main)

Hook 匿名类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 接口, 抽象类, 不可以被new
// 接口, 抽象类 要使用必须要实例化, 实例化不是通过new, 而是通过实现接口方法, 继承抽象类等方式
// new __接口__{} 可以理解成 new 了一个实现接口的匿名类, 在匿名类的内部(花括号内),实现了这个接口

function main(){
Java.perform(function(){
// hook 匿名类
// 匿名类在 smail中以 $1, $2 等方式存在, 需要通过 java 行号去 smail 找到准确的匿名类名称
var NiMingClass = Java.use("com.xiaojianbang.app.MainActivity$1");
NiMingClass.getInfo.implementation = function (){
return "kevin change 匿名类";
}
})
}

setImmediate(main)

Hook 类的所有方法

  • Java.enumerateLoadedClasses()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function main(){
Java.perform(function(){
Java.enumerateLoadedClasses({
onMatch: function(name,handle){
if (name.indexOf("com.xiaojianbang.app.Money") != -1){
console.log(name,handle);
// 利用反射 获取类中的所有方法
var TargetClass = Java.use(name);
// return Method Object List
var methodsList = TargetClass.class.getDeclaredMethods();
for (var i = 0; i < methodsList.length; i++){
// Method Objection getName()
console.log(methodsList[i].getName());
}
}
},

onComplete: function(){
console.log("complete!!!")
}
})
})
}
  • Java.enumerateLoadedClassesSync()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function main(){
Java.perform(function(){
// return String[] class name
var classList = Java.enumerateLoadedClassesSync();
for (var i=0; i < classList.length; i++){
var targetClass = classList[i];
if (targetClass.indexOf("com.xiaojianbang.app.Money") != -1){
console.log("hook the class: ", targetClass);
var TargetClass = Java.use(targetClass);
// 利用反射获取类中的所有方法
var methodsList = TargetClass.class.getDeclaredMethods();
for (var k=0; k < methodsList.length; k++){
console.log(methodsList[k].getName());
}
}
}
})
}

setImmediate(main)

Hook 类的所有方法及重载

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
37
38
39
function main(){
Java.perform(function(){
// hook md5 class in app
// 1. iterate classes
var classList = Java.enumerateLoadedClassesSync();
for (var i = 0; i < classList.length; i++){
// 筛选过滤 只遍历 MD5 下面的方法
if (classList[i].indexOf("com.xiaojianbang.app.MD5") != -1){
var className = classList[i];
console.log("class name is :", className);

// 2. get methods of the class
// 返回一个 Methods对象的数组
var methodsList = Java.use(className).class.getDeclaredMethods();
for (var k=0; k<methodsList.length; k++){
// console.log("method is :",methodsList[k],typeof(methodsList[k]));

// 3. Method object.getName() --> methodName and class[methodName] to hook method
var methodName = methodsList[k].getName(); //

// console.log('methodName',methodName);

// 4. use apply and arguments to implementation
var hookClass = Java.use(className);
// 5. overloads
for (var o = 0; o< hookClass[methodName].overloads.length; o++){
hookClass[methodName].overloads[o].implementation = function(){
for (var a=0; a<arguments.length; a++){
console.log('argument ',a,arguments[a]);
}
// return this[methodName].apply(this,arguments);
return "fucking the md5"
}
}
}
}
}
})
}

Hook 动态加载的 dex

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
function main(){
Java.perform(function(){
Java.enumerateClassLoaders({
onMatch : function(loader){
try {
// loadClass or findClass
if (loader.loadClass("com.xiaojianbang.app.Dynamic")){
Java.classFactory.loader = loader;
var hookClass = Java.use("com.xiaojianbang.app.Dynamic");
console.log("success hook it :", hookClass);
// something to do;
}
} catch (error) {
// pass
}
},

onComplete: function () {
console.log("complete !!! ")
}
})
})
}

setImmediate(main);

经常在加壳的 app 中, 没办法正确找到正常加载 app 类的 classloader, 可以使用以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function hook() {
Java.perform(function () {
Java.enumerateClassLoadersSync().forEach(function (classloader) {
try {
console.log("classloader", classloader);
classloader.loadClass("com.kanxue.encrypt01.MainActivity");
Java.classFactory.loader = classloader;
var mainActivityClass = Java.use("com.kanxue.encrypt01.MainActivity");
console.log("mainActivityClass", mainActivityClass);
} catch (error) {
console.log("error", error);
}
});
})
}

Hook 主动构造数组

1
2
3
4
5
6
7
8
9
function mainArray(){
Java.perform(function(){
var myCharList = Java.array("char",['一','去','二','三','里']);
var myStringList = Java.array("java.lang.String",["一","二","三"]);
var ArrayClass = Java.use("java.util.Arrays");
console.log(ArrayClass.toString(myCharList));
console.log(ArrayClass.toString(myStringList));
})
}

Hook cast 强制类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Java.cast() 子类可以强转成父类, 父类不能转成子类
// 可以使用Java.cast()将子类强转成父类, 再调用父类的动态方法

function castDemo(){
Java.perform(function(){
var JuiceHandle = null; // 用来存储内存中找到的Juice对象
var WaterClass = Java.use("com.r0ysue.a0526printout.Water");

Java.choose("com.r0ysue.a0526printout.Juice",{
onComplete: function(){},
onMatch: function(instance){
JuiceHandle = instance;
console.log("instance:", instance);
// 调用Juice对象的方法
console.log(JuiceHandle.fillEnergy());
// 子类Juice转父类Water 并调用父类的动态方法
var WaterInstance = Java.cast(JuiceHandle,WaterClass);
console.log(WaterInstance.still(WaterInstance));
}
})
})
}

Hook 打印类实现的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function searchInterface(){
Java.perform(function(){
Java.enumerateLoadedClasses({
onComplete: function(){},
onMatch: function(name,handle){
if (name.indexOf("com.r0ysue.a0526printout") > -1) { // 使用包名进行过滤
console.log("find class");
var targetClass = Java.use(name);
var interfaceList = targetClass.class.getInterfaces(); // 使用反射获取类实现的接口数组
if (interfaceList.length > 0) {
console.log(name) // 打印类名
for (var i in interfaceList) {
console.log("\t", interfaceList[i].toString()); // 直接打印接口名称
}
}
}
}
})
})
}

Hook enum 枚举

1
2
3
4
5
6
7
8
9
10
11
function enumPrint(){
Java.perform(function(){
Java.choose("com.r0ysue.a0526printout.Signal",{
onComplete: function(){},
onMatch: function(instance){
console.log('find it ,',instance);
console.log(instance.class.getName());
}
})
})
}

Hook 获取 context

1
2
3
4
5
6
7
8
9
10
11
function getContext(){
Java.perform(function(){
var currentApplication = Java.use("android.app.ActivityThread").currentApplication();
console.log(currentApplication);
var context = currentApplication.getApplicationContext();
console.log(context);
var packageName = context.getPackageName();
console.log(packageName);
console.log(currentApplication.getPackageName());
})
}

Hook 主动调用构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function main(){
Java.perform(function(){
var StringClass = Java.use("java.lang.String");
var MoneyClass = Java.use("com.xiaojianbang.app.Money");
MoneyClass.$init.overload('java.lang.String','int').implementation = function(x,y){
console.log('hook Money init');
var myX = StringClass.new("Hello World!");
var myY = 9999;
this.$init(myX,myY);
}
})
}

setImmediate(main);

Hook 主动调用静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function main_rsa(){
Java.perform(function(){
var RSA = Java.use("com.xiaojianbang.app.RSA");
var StringClass = Java.use("java.lang.String");
var base64Class = Java.use("android.util.Base64");
var myBytes = StringClass.$new("Hello World").getBytes();
var result = RSA.encrypt(myBytes);
console.log("result is :", result);
console.log("json result is: ",JSON.stringify(result));
console.log("base64 result is :", base64Class.encodeToString(result,0));
// console.log("new String is : ", StringClass.$new(result)); // 加密之后的内容有很多不可见字符, 不能直接 new String()

})
}

setImmediate(main_rsa);

Hook 主动调用动态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 非静态方法的主动调用 自定义instance 并调用 非静态方法
function main_getInfo(){
Java.perform(function(){
var instance = Java.use("com.xiaojianbang.app.Money").$new("日元",300000);
console.log(instance.getInfo());
})
}

// 遍历所有的对象并调用 需要进行过滤
function main_instance_getInfo(){
Java.perform(function(){
Java.choose("com.xiaojianbang.app.Money",{
onComplete: function(){},
onMatch: function(instance){
console.log(instance.getInfo());
}
})
})
}

Hook frida 和 python 交互

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
frida 传递参数
function main(){
Java.perform(function () {
console.log("enter perform");
// 获取要hook的类
var TextViewClass = Java.use("android.widget.TextView");
// 要hook的方法
TextViewClass.setText.overload('java.lang.CharSequence').implementation = function (ori_input) {
console.log('enter', 'java.lang.CharSequence');
console.log('ori_input',ori_input.toString());

// 定义用于接受python传参的data
var receive_data;
// 将原参数传递给python 在python中进行处理
send(ori_input.toString());
// recv 从python接收传递的内容 默认传过来的是个json对象
recv(function (json_data) {
console.log('data from python ' + json_data.data);
receive_data = json_data.data;
console.log(typeof (receive_data));
}).wait(); //wait() 等待python处理 阻塞

// 转java字符串
receive_data = Java.use("java.lang.String").$new(receive_data);
this.setText(receive_data);
};
})
}

setImmediate(main);
python 处理收到的参数
# -*- coding: utf-8 -*-
__author__ = "K"
__time__ = "2020-08-06 09:48"

import sys
import time
import base64
import frida
from loguru import logger

def on_message(message,data):
logger.info(str(message)) # dict
logger.info(str(data) if data else "None")

if message['type'] == 'error':
logger.error('error:' + str(message['description']))
logger.error('stack: ' + str(message['stack']))

if message['type'] == 'send':
logger.info('get message [*] --> ' + message['payload'])

payload = message['payload']
# 处理逻辑 sending to the server: YWFhOmJiYg==
tmp = payload.split(':')
sts = tmp[0]
need_to_db64 = tmp[1]
user_pass = base64.b64decode(need_to_db64.encode()).decode()

mine_str = 'admin' + ':' + user_pass.split(':')[-1]
mine_b64_str = base64.b64encode(mine_str.encode()).decode()
mine_b64_str = sts + mine_b64_str
logger.info(mine_b64_str)

# python返回数据给js script.post
script.post({'data':mine_b64_str})
logger.info('python complete')

device = frida.get_usb_device()
# pid = device.spawn(['com.kevin.demo04'])
# time.sleep(1)
session = device.attach('com.kevin.demo02')
with open('./hulianhutong.js','r') as f:
script = session.create_script(f.read())

script.on("message",on_message)
script.load()
input()

Hook 打印 char

1
2
3
4
5
6
7
8
9
10
11
12
// 打印char字符, 直接调用java.lang.Character toString()即可

function main(){
Java.perform(function(){
var CharClass = Java.use("java.lang.Character");
CharClass.toString.overload("char").implementation = function(inputChar){
var result = this.toString(inputChar);
console.log("inputChar, result: ", inputChar, result);
return result;
}
})
}

Hook 打印 char 数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1. 使用 java.util.Arrays 的 toString 方法 打印 [C 
// 2. 使用 js 的 JSON.stringify 打印 [C
function printCharArray(){
Java.perform(function(){
var ArrayClass = Java.use("java.util.Arrays");
ArrayClass.toString.overload('[C').implementation = function(charArray){
// 1. java.util.Arrays.toString()
var result = this.toString(charArray);
// 2. javascript JSON.stringify()
var result1 = JSON.stringify(charArray);
console.log('charArray, result : ', charArray, result);
console.log('charArray, result :', charArray, result1);
}
})
}

Hook 打印和修改 HashMap

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
遍历打印
function main(){
Java.perform(function(){
var targetClass = Java.use("com.xiaojianbang.app.ShufferMap");
targetClass.show.implementation = function(map){
// 遍历 map
var result = "";
var it = map.keySet().iterator();
while (it.hasNext()){
var keyStr = it.next();
var valueStr = map.get(keyStr);
result += valueStr;
}
console.log("result :", result);

// 修改 map
map.put("pass","fxxk");
map.put("code","Hello World");
console.log(JSON.stringify(map));
this.show(map);

return this.show(map);
}
})
}

setImmediate(main);

// cast打印 HashMap
function main(){
Java.perform(function(){
var HashMapNode = Java.use("java.util.HashMap$Node");
var targetClass = Java.use("com.xiaojianbang.app.ShufferMap");

var targetClass.show.implementation = function(map){
var result = "";
var iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
console.log("entry", iterator.next());
var entry = Java.cast(iterator.next(), HashMapNode);
console.log(entry.getKey());
console.log(entry.getValue());
return += entry.getValue();
}

console.log("result is :", result);
}
})
}

setImmediate(main);

toString()打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function main(){
Java.perform(function(){
var targetClass = Java.use("com.xiaojianbang.app.ShufferMap");
targetClass.show.implementation = function(map){
// 直接调用 toString()
console.log("打印hashmap: -> " + map.toString());
return this.show.apply(this,arguments);
}
})
}

setImmediate(main);

function printHashMap(flag, param_hm) {
Java.perform(function () {
var HashMap = Java.use('java.util.HashMap');
var args_map = Java.cast(param_hm, HashMap);
send(flag +":" + args_map.toString());
})
}

Hook 打印 byte 数组

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
方法 1
function main(){
Java.perform(function(){
var StringClass = Java.use("java.lang.String");
var byteArray = StringClass.$new("Hello World").getBytes();

// load r0gson
// openClassFile 返回 dex对象, dex对象.load()加载dex文件内容
Java.openClassFile("/data/local/tmp/r0gson.dex").load();
var gson = Java.use("com.r0ysue.gson.Gson");
console.log(gson.$new().toJson(byteArray));

// // console byte[]
// var ByteString = Java.use("com.android.okhttp.okio.ByteString");
// console.log(ByteString.of(byteArray).hex()); // byte转16进制字符串

// // 创建自定义Java数组 并打印
// var MyArray = Java.array("byte",[13,4,4,2]);
// console.log(gson.$new().toJson(MyArray));

var TargetClass = Java.use("com.xiaojianbang.app.ShufferMap");
TargetClass.show.implementation = function(map){
console.log(gson.$new().toJson(map));
return this.show(map);
}
})
}

setImmediate(main);
方法 2
// 1. 使用 java.util.Arrays.toString() 打印 [B
// 2. 使用 javascript JSON.stringify() 打印 [B

function printByteArray(){
Java.perform(function(){
var ArrayClass = Java.use("java.util.Arrays");
ArrayClass.toString.overload('[B').implementation = function(byteArray){
// 1. 使用 java.util.Arrays.toString() 打印 [B
var result = this.toString(byteArray);
// 2. 使用 javascript JSON.stringify() 打印 [B
var result1 = JSON.stringify(byteArray);

console.log('byteArray,result: ', byteArray, result);
console.log('byteArray,result1 :', byteArray, result1);

return result
}
})
}
方法 3
function printByteArray(byteArray){
Java.perform(function(){
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
console.log(ByteString.of(byteArray).hex())
})
}

Hook 打印调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
function printStacks(name){
console.log("====== printStacks start ====== " + name + "==============================")

// sample 1
var throwable = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new());
console.log(throwable);

// sample 2
var exception = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new());
console.log(exception);

console.log("====== printStacks end ======== " + name + "==============================")
}

Hook gson 打印

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 main(){
Java.perform(function(){
var StringClass = Java.use("java.lang.String");
var byteArray = StringClass.$new("Hello World").getBytes();

// load r0gson
// openClassFile 返回 dex对象, dex对象.load()加载dex文件内容
Java.openClassFile("/data/local/tmp/r0gson.dex").load();
var gson = Java.use("com.r0ysue.gson.Gson");
console.log(gson.$new().toJson(byteArray));

// // console byte[]
// var ByteString = Java.use("com.android.okhttp.okio.ByteString");
// console.log(ByteString.of(byteArray).hex()); // byte转16进制字符串

// // 创建自定义Java数组 并打印
// var MyArray = Java.array("byte",[13,4,4,2]);
// console.log(gson.$new().toJson(MyArray));

var TargetClass = Java.use("com.xiaojianbang.app.ShufferMap");
TargetClass.show.implementation = function(map){
console.log(gson.$new().toJson(map));
return this.show(map);
}
})
}

setImmediate(main);

Hook 打印 non-ascii 和特殊字符

一些特殊字符和不可见字符, 可以先通过编码再解码的方式进行 hook

1
2
3
int ֏(int x) {
return x + 100;
}

针对上面的֏, 直接用js编码, 在通过类名[js解码的方法名]进行implementation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Java.perform(
function x() {

var targetClass = "com.example.hooktest.MainActivity";

var hookCls = Java.use(targetClass);
var methods = hookCls.class.getDeclaredMethods();

for (var i in methods) {
console.log(methods[i].toString());
console.log(encodeURIComponent(methods[i].toString().replace(/^.*?\.([^\s\.\(\)]+)\(.*?$/, "$1")));
}

hookCls[decodeURIComponent("%D6%8F")]
.implementation = function (x) {
console.log("original call: fun(" + x + ")");
var result = this[decodeURIComponent("%D6%8F")](900);
return result;
}
}
)

简易 wallbreaker 内存打印

内存漫游, 打印实例的字段和方法

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
function main(){
Java.perform(function(){
var Class = Java.use("java.lang.Class");

function inspectObject(obj){
var obj_class = Java.cast(obj.getClass(), Class);
var fields = obj_class.getDeclaredFields();
var methods = obj_class.getMethods();
console.log("Inspectiong " + obj.getClass().toString());
console.log("\t Fields:")
for (var i in fields){
console.log("\t\t" + fields[i].toString());
}
console.log("\t Methods:")
for (var i in methods){
console.log("\t\t" + methods[i].toString())
}
}

Java.choose("com.baidu.lbs.waimai.WaimaiActivity",{
onComplete: function(){
console.log("complete!");

},
onMatch: function(instance){
console.log("find instance", instance);
inspectObject(instance);
}
})
})
}

setImmediate(main)

hook frida 实现 runnable

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
Java.perform(function() {
// https://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_SECURE
var FLAG_SECURE = 0x2000;
var Runnable = Java.use("java.lang.Runnable");
var DisableSecureRunnable = Java.registerClass({
name: "me.bhamza.DisableSecureRunnable",
implements: [Runnable],
fields: {
activity: "android.app.Activity",
},
methods: {
$init: [{
returnType: "void",
argumentTypes: ["android.app.Activity"],
implementation: function (activity) {
this.activity.value = activity;
}
}],
run: function() {
var flags = this.activity.value.getWindow().getAttributes().flags.value; // get current value
flags &= ~FLAG_SECURE; // toggle it
this.activity.value.getWindow().setFlags(flags, FLAG_SECURE); // disable it!
console.log("Done disabling SECURE flag...");
}
}
});

Java.choose("com.example.app.FlagSecureTestActivity", {
"onMatch": function (instance) {
var runnable = DisableSecureRunnable.$new(instance);
instance.runOnUiThread(runnable);
},
"onComplete": function () {}
});
});

Hook 监控控件 onClick

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
var jclazz = null;
var jobj = null;

function getObjClassName(obj) {
if (!jclazz) {
var jclazz = Java.use("java.lang.Class");
}
if (!jobj) {
var jobj = Java.use("java.lang.Object");
}
return jclazz.getName.call(jobj.getClass.call(obj));
}

function watch(obj, mtdName) {
var listener_name = getObjClassName(obj);
var target = Java.use(listener_name);
if (!target || !mtdName in target) {
return;
}
// send("[WatchEvent] hooking " + mtdName + ": " + listener_name);
target[mtdName].overloads.forEach(function (overload) {
overload.implementation = function () {
//send("[WatchEvent] " + mtdName + ": " + getObjClassName(this));
console.log("[WatchEvent] " + mtdName + ": " + getObjClassName(this))
return this[mtdName].apply(this, arguments);
};
})
}

function OnClickListener() {
Java.perform(function () {

//以spawn启动进程的模式来attach的话
Java.use("android.view.View").setOnClickListener.implementation = function (listener) {
if (listener != null) {
watch(listener, 'onClick');
}
return this.setOnClickListener(listener);
};

//如果frida以attach的模式进行attch的话
Java.choose("android.view.View$ListenerInfo", {
onMatch: function (instance) {
instance = instance.mOnClickListener.value;
if (instance) {
console.log("mOnClickListener name is :" + getObjClassName(instance));
watch(instance, 'onClick');
}
},
onComplete: function () {
}
})
})
}
setImmediate(OnClickListener);

Hook startActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Java.perform(function () {
var Activity = Java.use("android.app.Activity");
//console.log(Object.getOwnPropertyNames(Activity));
Activity.startActivity.overload('android.content.Intent').implementation=function(p1){
console.log("Hooking android.app.Activity.startActivity(p1) successfully,p1="+p1);
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
console.log(decodeURIComponent(p1.toUri(256)));
this.startActivity(p1);
}
Activity.startActivity.overload('android.content.Intent', 'android.os.Bundle').implementation=function(p1,p2){
console.log("Hooking android.app.Activity.startActivity(p1,p2) successfully,p1="+p1+",p2="+p2);
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
console.log(decodeURIComponent(p1.toUri(256)));
this.startActivity(p1,p2);
}
Activity.startService.overload('android.content.Intent').implementation=function(p1){
console.log("Hooking android.app.Activity.startService(p1) successfully,p1="+p1);
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
console.log(decodeURIComponent(p1.toUri(256)));
this.startService(p1);
}
})

Hook frida 实现 activity 跳转

1
2
3
4
5
6
7
8
9
10
11
function jumpActivity() {
Java.perform(function () {
var context = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext();
var intentClazz = Java.use("android.content.Intent");
var activityClazz = Java.use("ctrip.android.hotel.view.UI.inquire.HotelInquireActivity");
var intentObj = intentClazz.$new(context, activityClazz.class);
intentObj.setFlags(0x10000000);
context.startActivity(intentObj);
console.log("startActivity");
})
}

Hook frida 绕过 root 检测

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
// $ frida -l antiroot.js -U -f com.example.app --no-pause
// CHANGELOG by Pichaya Morimoto (p.morimoto@sth.sh):
// - I added extra whitelisted items to deal with the latest versions
// of RootBeer/Cordova iRoot as of August 6, 2019
// - The original one just fucked up (kill itself) if Magisk is installed lol
// Credit & Originally written by: https://codeshare.frida.re/@dzonerzy/fridantiroot/
// If this isn't working in the future, check console logs, rootbeer src, or libtool-checker.so

Java.perform(function() {

var RootPackages = ["com.noshufou.android.su", "com.noshufou.android.su.elite", "eu.chainfire.supersu",
"com.koushikdutta.superuser", "com.thirdparty.superuser", "com.yellowes.su", "com.koushikdutta.rommanager",
"com.koushikdutta.rommanager.license", "com.dimonvideo.luckypatcher", "com.chelpus.lackypatch",
"com.ramdroid.appquarantine", "com.ramdroid.appquarantinepro", "com.devadvance.rootcloak", "com.devadvance.rootcloakplus",
"de.robv.android.xposed.installer", "com.saurik.substrate", "com.zachspong.temprootremovejb", "com.amphoras.hidemyroot",
"com.amphoras.hidemyrootadfree", "com.formyhm.hiderootPremium", "com.formyhm.hideroot", "me.phh.superuser",
"eu.chainfire.supersu.pro", "com.kingouser.com", "com.android.vending.billing.InAppBillingService.COIN","com.topjohnwu.magisk"
];

var RootBinaries = ["su", "busybox", "supersu", "Superuser.apk", "KingoUser.apk", "SuperSu.apk","magisk"];

var RootProperties = {
"ro.build.selinux": "1",
"ro.debuggable": "0",
"service.adb.root": "0",
"ro.secure": "1"
};

var RootPropertiesKeys = [];

for (var k in RootProperties) RootPropertiesKeys.push(k);

var PackageManager = Java.use("android.app.ApplicationPackageManager");

var Runtime = Java.use('java.lang.Runtime');

var NativeFile = Java.use('java.io.File');

var String = Java.use('java.lang.String');

var SystemProperties = Java.use('android.os.SystemProperties');

var BufferedReader = Java.use('java.io.BufferedReader');

var ProcessBuilder = Java.use('java.lang.ProcessBuilder');

var StringBuffer = Java.use('java.lang.StringBuffer');

var loaded_classes = Java.enumerateLoadedClassesSync();

send("Loaded " + loaded_classes.length + " classes!");

var useKeyInfo = false;

var useProcessManager = false;

send("loaded: " + loaded_classes.indexOf('java.lang.ProcessManager'));

if (loaded_classes.indexOf('java.lang.ProcessManager') != -1) {
try {
//useProcessManager = true;
//var ProcessManager = Java.use('java.lang.ProcessManager');
} catch (err) {
send("ProcessManager Hook failed: " + err);
}
} else {
send("ProcessManager hook not loaded");
}

var KeyInfo = null;

if (loaded_classes.indexOf('android.security.keystore.KeyInfo') != -1) {
try {
//useKeyInfo = true;
//var KeyInfo = Java.use('android.security.keystore.KeyInfo');
} catch (err) {
send("KeyInfo Hook failed: " + err);
}
} else {
send("KeyInfo hook not loaded");
}

PackageManager.getPackageInfo.overload('java.lang.String', 'int').implementation = function(pname, flags) {
var shouldFakePackage = (RootPackages.indexOf(pname) > -1);
if (shouldFakePackage) {
send("Bypass root check for package: " + pname);
pname = "set.package.name.to.a.fake.one.so.we.can.bypass.it";
}
return this.getPackageInfo.call(this, pname, flags);
};

NativeFile.exists.implementation = function() {
var name = NativeFile.getName.call(this);
var shouldFakeReturn = (RootBinaries.indexOf(name) > -1);
if (shouldFakeReturn) {
send("Bypass return value for binary: " + name);
return false;
} else {
return this.exists.call(this);
}
};

var exec = Runtime.exec.overload('[Ljava.lang.String;');
var exec1 = Runtime.exec.overload('java.lang.String');
var exec2 = Runtime.exec.overload('java.lang.String', '[Ljava.lang.String;');
var exec3 = Runtime.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;');
var exec4 = Runtime.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;', 'java.io.File');
var exec5 = Runtime.exec.overload('java.lang.String', '[Ljava.lang.String;', 'java.io.File');

exec5.implementation = function(cmd, env, dir) {
if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id" || cmd == "sh") {
var fakeCmd = "grep";
send("Bypass " + cmd + " command");
return exec1.call(this, fakeCmd);
}
if (cmd == "su") {
var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled";
send("Bypass " + cmd + " command");
return exec1.call(this, fakeCmd);
}
if (cmd == "which") {
var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled";
send("Bypass which command");
return exec1.call(this, fakeCmd);
}
return exec5.call(this, cmd, env, dir);
};

exec4.implementation = function(cmdarr, env, file) {
for (var i = 0; i < cmdarr.length; i = i + 1) {
var tmp_cmd = cmdarr[i];
if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id" || tmp_cmd == "sh") {
var fakeCmd = "grep";
send("Bypass " + cmdarr + " command");
return exec1.call(this, fakeCmd);
}

if (tmp_cmd == "su") {
var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled";
send("Bypass " + cmdarr + " command");
return exec1.call(this, fakeCmd);
}
}
return exec4.call(this, cmdarr, env, file);
};

exec3.implementation = function(cmdarr, envp) {
for (var i = 0; i < cmdarr.length; i = i + 1) {
var tmp_cmd = cmdarr[i];
if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id" || tmp_cmd == "sh") {
var fakeCmd = "grep";
send("Bypass " + cmdarr + " command");
return exec1.call(this, fakeCmd);
}

if (tmp_cmd == "su") {
var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled";
send("Bypass " + cmdarr + " command");
return exec1.call(this, fakeCmd);
}
}
return exec3.call(this, cmdarr, envp);
};

exec2.implementation = function(cmd, env) {
if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id" || cmd == "sh") {
var fakeCmd = "grep";
send("Bypass " + cmd + " command");
return exec1.call(this, fakeCmd);
}
if (cmd == "su") {
var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled";
send("Bypass " + cmd + " command");
return exec1.call(this, fakeCmd);
}
return exec2.call(this, cmd, env);
};

exec.implementation = function(cmd) {
for (var i = 0; i < cmd.length; i = i + 1) {
var tmp_cmd = cmd[i];
if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id" || tmp_cmd == "sh") {
var fakeCmd = "grep";
send("Bypass " + cmd + " command");
return exec1.call(this, fakeCmd);
}

if (tmp_cmd == "su") {
var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled";
send("Bypass " + cmd + " command");
return exec1.call(this, fakeCmd);
}
}

return exec.call(this, cmd);
};

exec1.implementation = function(cmd) {
if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id" || cmd == "sh") {
var fakeCmd = "grep";
send("Bypass " + cmd + " command");
return exec1.call(this, fakeCmd);
}
if (cmd == "su") {
var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled";
send("Bypass " + cmd + " command");
return exec1.call(this, fakeCmd);
}
return exec1.call(this, cmd);
};

String.contains.implementation = function(name) {
if (name == "test-keys") {
send("Bypass test-keys check");
return false;
}
return this.contains.call(this, name);
};

var get = SystemProperties.get.overload('java.lang.String');

get.implementation = function(name) {
if (RootPropertiesKeys.indexOf(name) != -1) {
send("Bypass " + name);
return RootProperties[name];
}
return this.get.call(this, name);
};

Interceptor.attach(Module.findExportByName("libc.so", "fopen"), {
onEnter: function(args) {
var path1 = Memory.readCString(args[0]);
var path = path1.split("/");
var executable = path[path.length - 1];
var shouldFakeReturn = (RootBinaries.indexOf(executable) > -1)
if (shouldFakeReturn) {
Memory.writeUtf8String(args[0], "/ggezxxx");
send("Bypass native fopen >> "+path1);
}
},
onLeave: function(retval) {

}
});

Interceptor.attach(Module.findExportByName("libc.so", "fopen"), {
onEnter: function(args) {
var path1 = Memory.readCString(args[0]);
var path = path1.split("/");
var executable = path[path.length - 1];
var shouldFakeReturn = (RootBinaries.indexOf(executable) > -1)
if (shouldFakeReturn) {
Memory.writeUtf8String(args[0], "/ggezxxx");
send("Bypass native fopen >> "+path1);
}
},
onLeave: function(retval) {

}
});

Interceptor.attach(Module.findExportByName("libc.so", "system"), {
onEnter: function(args) {
var cmd = Memory.readCString(args[0]);
send("SYSTEM CMD: " + cmd);
if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id") {
send("Bypass native system: " + cmd);
Memory.writeUtf8String(args[0], "grep");
}
if (cmd == "su") {
send("Bypass native system: " + cmd);
Memory.writeUtf8String(args[0], "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled");
}
},
onLeave: function(retval) {

}
});

/*
TO IMPLEMENT:
Exec Family
int execl(const char *path, const char *arg0, ..., const char *argn, (char *)0);
int execle(const char *path, const char *arg0, ..., const char *argn, (char *)0, char *const envp[]);
int execlp(const char *file, const char *arg0, ..., const char *argn, (char *)0);
int execlpe(const char *file, const char *arg0, ..., const char *argn, (char *)0, char *const envp[]);
int execv(const char *path, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
*/

BufferedReader.readLine.overload().implementation = function() {
var text = this.readLine.call(this);
if (text === null) {
// just pass , i know it's ugly as hell but test != null won't work :(
} else {
var shouldFakeRead = (text.indexOf("ro.build.tags=test-keys") > -1);
if (shouldFakeRead) {
send("Bypass build.prop file read");
text = text.replace("ro.build.tags=test-keys", "ro.build.tags=release-keys");
}
}
return text;
};

var executeCommand = ProcessBuilder.command.overload('java.util.List');

ProcessBuilder.start.implementation = function() {
var cmd = this.command.call(this);
var shouldModifyCommand = false;
for (var i = 0; i < cmd.size(); i = i + 1) {
var tmp_cmd = cmd.get(i).toString();
if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd.indexOf("mount") != -1 || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd.indexOf("id") != -1) {
shouldModifyCommand = true;
}
}
if (shouldModifyCommand) {
send("Bypass ProcessBuilder " + cmd);
this.command.call(this, ["grep"]);
return this.start.call(this);
}
if (cmd.indexOf("su") != -1) {
send("Bypass ProcessBuilder " + cmd);
this.command.call(this, ["justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"]);
return this.start.call(this);
}

return this.start.call(this);
};

if (useProcessManager) {
var ProcManExec = ProcessManager.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;', 'java.io.File', 'boolean');
var ProcManExecVariant = ProcessManager.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;', 'java.lang.String', 'java.io.FileDescriptor', 'java.io.FileDescriptor', 'java.io.FileDescriptor', 'boolean');

ProcManExec.implementation = function(cmd, env, workdir, redirectstderr) {
var fake_cmd = cmd;
for (var i = 0; i < cmd.length; i = i + 1) {
var tmp_cmd = cmd[i];
if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id") {
var fake_cmd = ["grep"];
send("Bypass " + cmdarr + " command");
}

if (tmp_cmd == "su") {
var fake_cmd = ["justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"];
send("Bypass " + cmdarr + " command");
}
}
return ProcManExec.call(this, fake_cmd, env, workdir, redirectstderr);
};

ProcManExecVariant.implementation = function(cmd, env, directory, stdin, stdout, stderr, redirect) {
var fake_cmd = cmd;
for (var i = 0; i < cmd.length; i = i + 1) {
var tmp_cmd = cmd[i];
if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id") {
var fake_cmd = ["grep"];
send("Bypass " + cmdarr + " command");
}

if (tmp_cmd == "su") {
var fake_cmd = ["justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"];
send("Bypass " + cmdarr + " command");
}
}
return ProcManExecVariant.call(this, fake_cmd, env, directory, stdin, stdout, stderr, redirect);
};
}

if (useKeyInfo) {
KeyInfo.isInsideSecureHardware.implementation = function() {
send("Bypass isInsideSecureHardware");
return true;
}
}

});

Hook frida 强制在主线程运行

针对使用一些方法的时候出现报错 on a thread that has not called Looper.prepare()

强制让代码运行在主线程中

1
2
3
4
5
6
7
8
9
Java.perform(function() {
var Toast = Java.use('android.widget.Toast');
var currentApplication = Java.use('android.app.ActivityThread').currentApplication();
var context = currentApplication.getApplicationContext();

Java.scheduleOnMainThread(function() {
Toast.makeText(context, "Hello World", Toast.LENGTH_LONG.value).show();
})
})

Hook frida 指定方法中过滤打印

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
function hook_lnf() {
var activate = false;

Java.perform(function(){
var hashmapClass = Java.use("java.util.HashMap");
hashmapClass.put.implementation = function(key,value){
if (activate){
console.log("key:", key, "value:", value);
}
return this.put(key,value);
};
});

Java.perform(function () {
var lnfClazz = Java.use("tb.lnf");
lnfClazz.a.overload('java.util.HashMap', 'java.util.HashMap', 'java.lang.String',
'java.lang.String', 'boolean').implementation = function (hashmap, hashmap2, str, str2, z) {
printHashMap("hashmap", hashmap);
printHashMap("hashmap2", hashmap2);
console.log("str", str);
console.log("str2", str2);
console.log("boolean", z);
activate = true;
var result = this.a(hashmap, hashmap2, str, str2, z);
activate = false
printHashMap("result", result);
return result;
};
})
}

Hook 禁止 app 退出

1
2
3
4
5
6
7
8
9
10
11
12
function hookExit(){
Java.perform(function(){
console.log("[*] Starting hook exit");
var exitClass = Java.use("java.lang.System");
exitClass.exit.implementation = function(){
console.log("[*] System.exit.called");
}
console.log("[*] hooking calls to System.exit");
})
}

setImmediate(hookExit);

Hook 修改设备参数

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// frida hook 修改设备参数
Java.perform(function() {
var TelephonyManager = Java.use("android.telephony.TelephonyManager");

//IMEI hook
TelephonyManager.getDeviceId.overload().implementation = function () {
console.log("[*]Called - getDeviceId()");
var temp = this.getDeviceId();
console.log("real IMEI: "+temp);
return "867979021642856";
};
// muti IMEI
TelephonyManager.getDeviceId.overload('int').implementation = function (p) {
console.log("[*]Called - getDeviceId(int) param is"+p);
var temp = this.getDeviceId(p);
console.log("real IMEI "+p+": "+temp);
return "867979021642856";
};

//IMSI hook
TelephonyManager.getSimSerialNumber.overload().implementation = function () {
console.log("[*]Called - getSimSerialNumber(String)");
var temp = this.getSimSerialNumber();
console.log("real IMSI: "+temp);
return "123456789";
};
//////////////////////////////////////

//ANDOID_ID hook
var Secure = Java.use("android.provider.Settings$Secure");
Secure.getString.implementation = function (p1,p2) {
if(p2.indexOf("android_id")<0) return this.getString(p1,p2);
console.log("[*]Called - get android_ID, param is:"+p2);
var temp = this.getString(p1,p2);
console.log("real Android_ID: "+temp);
return "844de23bfcf93801";

}

//android的hidden API,需要通过反射调用
var SP = Java.use("android.os.SystemProperties");
SP.get.overload('java.lang.String').implementation = function (p1) {
var tmp = this.get(p1);
console.log("[*]"+p1+" : "+tmp);

return tmp;
}
SP.get.overload('java.lang.String', 'java.lang.String').implementation = function (p1,p2) {


var tmp = this.get(p1,p2)
console.log("[*]"+p1+","+p2+" : "+tmp);
return tmp;
}
// hook MAC
var wifi = Java.use("android.net.wifi.WifiInfo");
wifi.getMacAddress.implementation = function () {
var tmp = this.getMacAddress();
console.log("[*]real MAC: "+tmp);
return tmp;
}

})

Hook 打印请求调用栈

1
2
3
4
5
6
7
8
9
10
11
var class_Socket = Java.use("java.net.Socket");
class_Socket.getOutputStream.overload().implementation = function(){
send("getOutputSteam");
var result = this.getOutputStream();
var bt = Java.use("android.util.Log").getStackTraceString(
Java.use("java.lang.Exception").$new();
)
console.log("Backtrace:" + bt);
send(result);
return result;
}

Hook UI thread 注入

1
2
3
4
5
6
7
8
9
Java.perform(function() {
var Toast = Java.use('android.widget.Toast');
var currentApplication = Java.use('android.app.ActivityThread').currentApplication();
var context = currentApplication.getApplicationContext();

Java.scheduleOnMainThread(function() {
Toast.makeText(context, "Hello World", Toast.LENGTH_LONG.value).show();
})
})

常用打印转换

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
//工具相关函数
var base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
base64DecodeChars = new Array((-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), 62, (-1), (-1), (-1), 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, (-1), (-1), (-1), (-1), (-1), (-1), (-1), 0, 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, (-1), (-1), (-1), (-1), (-1), (-1), 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, (-1), (-1), (-1), (-1), (-1));

// base64 解码
function stringToBase64(e) {
var r, a, c, h, o, t;
for (c = e.length, a = 0, r = ''; a < c;) {
if (h = 255 & e.charCodeAt(a++), a == c) {
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4),
r += '==';
break
}
if (o = e.charCodeAt(a++), a == c) {
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
r += base64EncodeChars.charAt((15 & o) << 2),
r += '=';
break
}
t = e.charCodeAt(a++),
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
r += base64EncodeChars.charAt((15 & o) << 2 | (192 & t) >> 6),
r += base64EncodeChars.charAt(63 & t)
}
return r
}

// base64 编码
function base64ToString(e) {
var r, a, c, h, o, t, d;
for (t = e.length, o = 0, d = ''; o < t;) {
do
r = base64DecodeChars[255 & e.charCodeAt(o++)];
while (o < t && r == -1);
if (r == -1)
break;
do
a = base64DecodeChars[255 & e.charCodeAt(o++)];
while (o < t && a == -1);
if (a == -1)
break;
d += String.fromCharCode(r << 2 | (48 & a) >> 4);
do {
if (c = 255 & e.charCodeAt(o++), 61 == c)
return d;
c = base64DecodeChars[c]
} while (o < t && c == -1);
if (c == -1)
break;
d += String.fromCharCode((15 & a) << 4 | (60 & c) >> 2);
do {
if (h = 255 & e.charCodeAt(o++), 61 == h)
return d;
h = base64DecodeChars[h]
} while (o < t && h == -1);
if (h == -1)
break;
d += String.fromCharCode((3 & c) << 6 | h)
}
return d
}

// hex 字符转 base64
function hexToBase64(str) {
return base64Encode(String.fromCharCode.apply(null, str.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" ")));
}

// base64 转 hex
function base64ToHex(str) {
for (var i = 0, bin = base64Decode(str.replace(/[ \r\n]+$/, "")), hex = []; i < bin.length; ++i) {
var tmp = bin.charCodeAt(i).toString(16);
if (tmp.length === 1)
tmp = "0" + tmp;
hex[hex.length] = tmp;
}
return hex.join("");
}


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

function bytesToHex(arr) {
var str = '';
var k, j;
for (var i = 0; i < arr.length; i++) {
k = arr[i];
j = k;
if (k < 0) {
j = k + 256;
}
if (j < 16) {
str += "0";
}
str += j.toString(16);
}
return str;
}

function stringToHex(str) {
var val = "";
for (var i = 0; i < str.length; i++) {
if (val == "")
val = str.charCodeAt(i).toString(16);
else
val += str.charCodeAt(i).toString(16);
}
return val
}

function stringToBytes(str) {
var ch, st, re = [];
for (var i = 0; i < str.length; i++) {
ch = str.charCodeAt(i);
st = [];
do {
st.push(ch & 0xFF);
ch = ch >> 8;
}
while (ch);
re = re.concat(st.reverse());
}
return re;
}

//将byte[]转成String的方法
function bytesToString(arr) {
var str = '';
arr = new Uint8Array(arr);
for (var i in arr) {
str += String.fromCharCode(arr[i]);
}
return str;
}

function bytesToBase64(e) {
var r, a, c, h, o, t;
for (c = e.length, a = 0, r = ''; a < c;) {
if (h = 255 & e[a++], a == c) {
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4),
r += '==';
break
}
if (o = e[a++], a == c) {
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
r += base64EncodeChars.charAt((15 & o) << 2),
r += '=';
break
}
t = e[a++],
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
r += base64EncodeChars.charAt((15 & o) << 2 | (192 & t) >> 6),
r += base64EncodeChars.charAt(63 & t)
}
return r
}

function base64ToBytes(e) {
var r, a, c, h, o, t, d;
for (t = e.length, o = 0, d = []; o < t;) {
do
r = base64DecodeChars[255 & e.charCodeAt(o++)];
while (o < t && r == -1);
if (r == -1)
break;
do
a = base64DecodeChars[255 & e.charCodeAt(o++)];
while (o < t && a == -1);
if (a == -1)
break;
d.push(r << 2 | (48 & a) >> 4);
do {
if (c = 255 & e.charCodeAt(o++), 61 == c)
return d;
c = base64DecodeChars[c]
} while (o < t && c == -1);
if (c == -1)
break;
d.push((15 & a) << 4 | (60 & c) >> 2);
do {
if (h = 255 & e.charCodeAt(o++), 61 == h)
return d;
h = base64DecodeChars[h]
} while (o < t && h == -1);
if (h == -1)
break;
d.push((3 & c) << 6 | h)
}
return d
}

object 数组中嵌套 string 数组

1
2
3
4
5
6
7
8
9
10
11
12
13
var string1 = Java.use("java.lang.String").$new("123");
var string2 = Java.use("java.lang.String").$new("");
// var objarr0 = Java.array("Ljava.lang.String;", [string1, string2]);
var Ref_arr = Java.use('java.lang.reflect.Array')
var stringClass = Java.use("java.lang.String").class
var arg1 = Ref_arr.newInstance(stringClass, 2);
Ref_arr.set(arg1, 0, string1);
Ref_arr.set(arg1, 1, string2);

var objarr1 = Java.use("java.lang.String").$new("24717361");
var objarr2 = Java.use("java.lang.Integer").$new(19);
var objarr3 = Java.use("java.lang.String").$new("");
var objarr = Java.array("Ljava.lang.Object;", [arg1, objarr1, objarr2, objarr3]);

frida 主动加载 dex 并调用其中方法

首先将要使用的 java 类写出来, 例如, 自定义的 base64 码表的 encode 和 decode 方法;

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
class Base64DIY {
private static char[] base64Code = {'i', '5', 'j', 'L', 'W', '7', 'S', '0', 'G', 'X', '6', 'u', 'f', '1', 'c', 'v', '3', 'n', 'y', '4', 'q', '8', 'e', 's', '2', 'Q', '+', 'b', 'd', 'k', 'Y', 'g', 'K', 'O', 'I', 'T', '/', 't', 'A', 'x', 'U', 'r', 'F', 'l', 'V', 'P', 'z', 'h', 'm', 'o', 'w', '9', 'B', 'H', 'C', 'M', 'D', 'p', 'E', 'a', 'J', 'R', 'Z', 'N'};
private static int[] toInt = new int[128];


//存储的是与base64编码对应的索引
static {
for (int i = 0; i < base64Code.length; i++) {
toInt[base64Code[i]] = i;
}
}

//主函数
public static void main(String[] args) {
String str = "0123456789bcdef";
String str2 = toBase64(str.getBytes());
System.out.println(str2);
System.out.println(deBase64(str2));
}

//Base64编码
public static String toBase64(byte[] byteArr){
//判空
if(byteArr == null || byteArr.length == 0){
return null;
}
//确定字符数组的长度
char[] chars = new char[(byteArr.length + 2) / 3 * 4];
int i = 0;
int count = 0;
while(i < byteArr.length){
//获取原始数据的ascii码值
byte b0 = byteArr[i++];
byte b1 = (i < byteArr.length) ? byteArr[i++] : 0;
byte b2 = (i < byteArr.length) ? byteArr[i++] : 0;

//转化为对应的base数
/**
* 这是3位转4位
* 第一位 右移两位高位补0没问题
* 第二位 b0左移到高4位低四位补0 b1 右移到低四位 结合就是b0原本的低四位 + b1的高四位
* 这里会有问题? 我们只要b0的最后两位和b1的高四位, b0左移4位高2位不一定会是00
* “& 0x3f ”的作用就是保证高2位是00
* 第三位第四位同理
* */
chars[count++] = base64Code[(b0 >> 2) & 0x3f];
chars[count++] = base64Code[((b0 << 4) | (b1 >> 4)) & 0x3f];
chars[count++] = base64Code[((b1 << 2) | (b2 >> 6)) & 0x3f];
chars[count++] = base64Code[b2 & 0x3f];
}
//添加'=' case渗透
switch (byteArr.length % 3){
case 1 : chars[--count] = '=';
case 2 : chars[--count] = '=';
}
return new String(chars);
}

//Base64解码
public static String deBase64(String str){
//先判空
if(str == null || str.length() == 0){
return str;
}
int tempNum = str.endsWith("==") ? 2 : str.endsWith("=") ? 1 : 0; //判断字符串结尾有几个'='
byte[] bytes = new byte[str.length() * 3 / 4 - tempNum]; //删除对应个数
int index = 0;
//逆序读出明文
for(int i = 0;i < str.length();i++){
int c0 = toInt[str.charAt(i++)]; //Base64编码对应的索引
int c1 = toInt[str.charAt(i++)];
bytes[index++] = (byte) ((c0 << 2) | (c1 >> 4));
if(index >= bytes.length){
return new String(bytes);
}
int c2 = toInt[str.charAt(i++)];
bytes[index++] = (byte)((c1 << 4) | (c2 >> 2));
if(index >= bytes.length){
return new String(bytes);
}
int c3 = toInt[str.charAt(i)];
bytes[index++] = (byte) ((c2 << 6) | c3);
}
return new String(bytes);

}


}

当前文件最好直接放在 src 文件目录下, 这样在执行命令的时候不会出现无法找到的情况;

  1. 在 idea 中编译运行, 从 .java文件转换成.class文件; 找到对应的Base64DIY.class文件;
  2. .class文件转为.jar文件: jar -cvf ddex.jar Base64DIY.class
  3. .jar文件转为 .dex文件: ~/Library/Android/sdk/build-tools/28.0.3/dx --dex --output=ddex.dex ddex.jar
  4. .dex文件推入手机中 adb push ddex.dex /data/local/tmp/ddex.dex
  5. .dex文件添加权限: chmod 777 ddex.dex

在 frida 脚本中可以 hook 并调用自定义 dex 文件中的方法;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
///<reference path='./index.d.ts'/>

function hook(){
Java.perform(function () {
var ddex = Java.openClassFile("/data/local/tmp/ddex.dex");
ddex.load();
var clazz = Java.use("Base64DIY");
var input = "0123456789bcdef";
var encodeBase64 = clazz.toBase64(Java.use("java.lang.String").$new(input).getBytes());
console.log("encodeBase64", encodeBase64);
})
}

function main(){
hook();
}

setImmediate(main)

检测判断是否是 rpc 调用

1
2
3
looperclazz = (*env)->FindClass(env, &xmmword_39010);// android/os/Looper
myLooper_methodID = (*env)->GetStaticMethodID(env, looperclazz, &qword_39028, &xmmword_39040);// myLooper
if ( !CallStaticObjectMethodV_(env, (__int64)looperclazz, (__int64)myLooper_methodID) )

在 so 中调用android.os.Looper.myLooper(), 如果是正常的调用, 则通过CallStaticObjectMethodV调用的结果为非 0; 如果是通过 frida 的主动调用, 则返回结果为 0; 因为 frida 的主动调用不在主线程中; 可以作为一个主动调用的检测点

frida 注册接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function hook_FridaActivity9(){
Java.perform(function () {
// $接口
var Frida9Interface = Java.use("com.github.lastingyang.androiddemo.Activity.FridaActivity9$Frida9Interface");
console.log("Frida9Interface", Frida9Interface);
var Frida9InterfaceImpl = Java.registerClass({
name: "com.github.lastingyang.androiddemo.Activity.FridaActivity9.FridaInterfaceImpl",
implements: [Frida9Interface],
methods: {
check() {
console.log("FridaInterfaceImpl.check");
return true;
}
}
});
var FridaActivity9 = Java.use("com.github.lastingyang.androiddemo.Activity.FridaActivity9");
FridaActivity9.getInstance.implementation = function () {
console.log("FridaActivity9.getInstance");
return Frida9InterfaceImpl.$new();
}

})
}

frida hook thread 打印调用栈

通过 hook thread 打印出调用栈, 可以配合 r0capture 对发包位置进行回溯

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
37
38
39
40
41
42
43
44
function printJavaStack(tag) {
Java.perform(function () {
console.log(tag + "\n" + Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
});
}



var hook_thread = function(){
Java.perform(function(){
var Thread = Java.use("java.lang.Thread");
Thread.init.implementation = function(arg0, arg1, arg2, arg3){
var res = this.init(arg0, arg1, arg2, arg3);
var threadid = this.getId();

var target = this.target.value;
if (target){
var className = target.$className;
console.log("\nRunnable classname ==>", className, threadid);
printJavaStack("Runnable " + threadid);
}else{
var className = this.$className;
console.log("\nThe Thread classname ==>", className, threadid);
printJavaStack("The Thread " + threadid);
}

return res;
}

Thread.run.implementation = function(){
var threadid = this.getId();
var className = this.$className;
console.log("The Thread run classname ==>", className, threadid);
return this.run();
}
});
}


function main() {
hook_thread();
}

setImmediate(main);