Frida使用手册

安装Frida

环境准备

  1. adb环境

SDK Platform-Tools

  1. Python与pip

官方下载地址

主机安装Frida服务

Frida项目地址

打开frida项目,去Release下载对应cpu架构和版本的frida-server

查看手机架构:

1
adb shell getprop ro.product.cpu.abi

这里我们的主机可以使用手机或者模拟器,前提是必须能够获得root权限

  1. 启动服务:
1
2
3
4
adb root
adb push frida-server /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/frida-server*"
adb shell "/data/local/tmp/frida-server* &"
  1. 端口映射:
1
2
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
  1. 查看进程
1
frida-ps -U

客户端安装

确定安装好Python及pip

  1. 打开命令行窗口
  2. 安装frida-tools
1
pip install frida-tools
  1. 等待安装完成

确认:

1
frida-ps --version

如果安装成功,则输出frida版本号

常用命令

1
2
3
4
5
6
7
8
9
10
11
# 通过USB将Frida连接到iPad并列出正在运行的进程
$ frida-ps -U

# 列出正在运行的应用程序
$ frida-ps -Ua

# 列出已安装的应用程序
$ frida-ps -Uai

# 将Frida连接到特定设备
$ frida-ps -D 0216027d1d6d3a03
参数 含义
-U 使用 USB 设备连接
-l 指定js脚本的路径名字
-F app 最前显示的应用
-f 指定包名,自动启动 app 用%resume 恢复主线程
–no-pause 启动后,自动运行主线程 可省略%resume
–debug 附加到 Node.js 进行调试
-o 将输入的日志文件保存到指定路径
-p 指定进程ID
-n 指定包名附加

使用Frida脱壳

查壳

关于壳

  1. 壳的功能:壳最本质的功能就是实现加载器,壳是指在一个程序的外面再包裹上另外一段代码,保护里面的代码不被非法修改或反编译的程序。它们一般都是先于程序运行,拿到控制权,然后完成它们保护软件的任务,深入点就是在apk外面再套一层壳,在运行的时候这层壳会把真正的apk的dalvik指令集释放出来,为apk加壳是目前主流的防护方案,真正的源码是隐藏在壳之下的,要想拿到源码研究其逻辑就要先想办法将壳脱掉,所以我们拿到一个apk要逆向的第一步就是用查壳工具看下这个apk文件是否加壳,以及加了什么壳,然后想办法把壳脱掉,拿到dex文件再去分析。
    注意:壳绝对不能改变原来代码的执行流程;加壳程序也不能对源程序有任何的影响。

  2. apk的组成原理:apk壳和pc端的都差不多,只是在处理不同的文件格式dex和exe中会有差别;导致不同的加壳;Android上的应用安装文件是apk格式的,这个apk文件其实就是个归档文件压缩包,把应用相关的源码、资源文件、配置文件等等都归档打包,直接解压之后就能得到app的dalvik指令集dex文件,然后再反编译为smali,还可以再反编译为Java,这样就几乎等同于拿到了app就能拿到可读性还蛮清晰的源码。

  3. Android Dex文件加壳原理:Android APK加壳过程中,牵扯到三个角色:加壳程序(加密源程序为解壳数据、组装解壳程序和解壳数据)、解壳程序(解密解壳数据,并运行时通过DexClassLoader动态加载)、(源程序)需要加壳处理的被保护代码

APKSCAN-PKID 查壳工具

Java版下载地址:百度网盘 提取密码:8888

解压后,在Java1.8环境下运行:

1
java -jar ApkScan-PKID.jar

脱壳

检测是否安装了frida-dexdump,若没安装则去安装:

1
pip install frida-dexdump

确认脱壳应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(venv) PS D:\haha\Python> frida-ps -Ua
PID Name Identifier
----- ---------------------- ---------------------------------------
28227 Calendar com.google.android.calendar
27477 Clash Meta for Android com.github.metacubex.clash.meta
26269 Clock com.google.android.deskclock
28400 Contacts com.google.android.contacts
28845 Copilot com.microsoft.copilot
25108 Digital Wellbeing com.google.android.apps.wellbeing
27254 GitHub com.github.android
25173 Google com.google.android.googlequicksearchbox
25212 Messages com.google.android.apps.messaging
25052 Phone com.google.android.dialer
26497 Photos com.google.android.apps.photos
27037 QQ com.tencent.mobileqq
3656 Setting p.ktxo.wr
24569 Settings com.android.settings
26910 Telegram X org.thunderdog.challegram
28952 WeChat com.tencent.mm
25732 v2rayNG com.v2ray.ang

假设我们脱壳的应用是Telegram X,我们可以执行以下任一一条命令进行脱壳:

  • 指定App的应用名称:
1
frida-dexdump -U -n 'Telegram X'
  • 指定App的应用进程ID:
1
frida-dexdump -U -p 26910

` 指定App的应用包名:

1
frida-dexdump -U -f org.thunderdog.challegram

脱壳

使用jadx反编译

jadx项目地址

反编译dex

打开

若遇到dex文件打开错误的问题,jadx.plugins.input.dex.DexException: Bad checksum

文件->首选项->插件->dex-input,verify dex file checksum before load,取消勾选,即关闭校验

error

使用frida hook

以下内容来自:

frida 使用

frida hook java

Frida javascript hook 检测设备信息获取等

frida hook so

一篇文章带你领悟Frida的精髓(基于安卓8.1)

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 以目标对象作为参数的方法, 此时该参数就是对象;

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
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);
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
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 ([email protected]):
// - 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
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 文件目录下, 这样在执行命令的时候不会出现无法找到的情况;

202

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

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
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
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
/**
* 如何使用:
* 1、确保设备启动 frida-service
* adb shell
* su 需要 root 设备
* cd data/local/tmp/ firda-service 可执行文件存放位置
* ./frida-service*** & 后台运行
* frida -U -f 【包名】 -l 【脚本路径】 注入脚本启动应用
* */


//全局配置
var runConfig = {
"permission": false,
"startActivity": false,
"deviceId": true,
"file": false,
"ipAddress": false,
"location": false,
"other": false,
"systemProperties": false,
"packageList": false,
"enablePrintStackTrace": false,
}


Java.perform(function x() {
console.log(" --------- 启动检测 ----------");

if (runConfig.permission) {
checkPermission();
}

if (runConfig.startActivity) {
checkStartActivity();
}

if (runConfig.deviceId) {
checkAndroidId();
checkIMEI();
checkOtherId()
}

if (runConfig.file) {
checkExternalFileRW();
}

if (runConfig.ipAddress) {
checkIPAddress();
}

if (runConfig.location) {
checkLocation();
}

if (runConfig.other) {
checkOther();
}

if (runConfig.systemProperties) {
checkSystemProperties();
}

if (runConfig.packageList) {
checkPackageList();
}


console.log(" --------- 结束检测 ----------");
})



///

function log() {
if (runConfig.enablePrintStackTrace) {
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
}
}




function checkPackageList() {
console.log("----------- 应用安装列表检查 -----------")

var ApplicationPackageManager = Java.use("android.app.ApplicationPackageManager")
ApplicationPackageManager.getInstalledPackages.implementation = function (flags) {
var list = this.getInstalledPackages(flags)

console.log("ApplicationPackageManager 获取安装列表 " + list)
log();
return list;
}

ApplicationPackageManager.getPackageInfo
.overload('java.lang.String', 'int')
.implementation = function (pkg, flags) {
var info = this.getPackageInfo(pkg, flags)

console.log("ApplicationPackageManager 获取包名信息 " + info)
log();
return info;
}

ApplicationPackageManager.getPackageInfo
.overload('android.content.pm.VersionedPackage', 'int')
.implementation = function (pkg, flags) {
var info = this.getPackageInfo(pkg, flags)

console.log("ApplicationPackageManager 获取包名信息 " + info)
log();
return info;
}


ApplicationPackageManager.getLaunchIntentForPackage.implementation = function (pkg) {
var intent = this.getLaunchIntentForPackage(pkg)

console.log("ApplicationPackageManager 获取启动 intent: " + intent)
log();
return intent;
}
}


function checkSystemProperties() {
console.log("----------- 系统属性检查 -----------")
var SystemProperties = Java.use("android.os.SystemProperties")
SystemProperties.get
.overload('java.lang.String').implementation = function (key) {
var val = this.get(key)

console.log("SystemProperties 获取系统属性 " + key + " -> " + val)
log();
return val;
}

SystemProperties.get
.overload('java.lang.String', 'java.lang.String').implementation = function (key, def) {
var val = this.get(key, def)

console.log("SystemProperties 获取系统属性 " + key + " -> " + val + " " + def)
log();
return val;
}
}



function checkOther() {
console.log("----------- 剪切板检查 -----------")

var ClipboardManager = Java.use("android.content.ClipboardManager")
ClipboardManager.getPrimaryClip.implementation = function () {
var val = this.getPrimaryClip()

console.log("ClipboardManager 1 获取短信 " + val)
log();
return val;
}

ClipboardManager.getPrimaryClipDescription.implementation = function () {
var val = this.getPrimaryClipDescription()

console.log("ClipboardManager 1 获取短信 " + val)
log();
return val;
}

console.log("----------- 网络信息检查 -----------")

var ConnectivityManager = Java.use("android.net.ConnectivityManager")
ConnectivityManager.getActiveNetworkInfo.implementation = function () {
var val = this.getActiveNetworkInfo()

console.log("ConnectivityManager 获取网络信息 " + val)
log();
return val;
}
}

/**
* 未完善,需要解析 content 判断属于哪一种类型
*/
function checkContentResolve() {

var ContentResolver = Java.use("android.content.ContentResolver")
ContentResolver.query
.overload('android.net.Uri', '[Ljava.lang.String;', 'android.os.Bundle', 'android.os.CancellationSignal')
.implementation = function (uri, strs, bundle, signal) {
var val = this.query(uri, strs, bundle, signal)

console.log("ContentResolver 1 获取短信 " + val)
log();
return val;
}

ContentResolver.query
.overload('android.net.Uri', '[Ljava.lang.String;', 'java.lang.String', '[Ljava.lang.String;', 'java.lang.String')
.implementation = function (uri, strs, str1, strs2, str3) {
var val = this.query(uri, strs, str1, strs2, str3)

console.log("ContentResolver 2 获取短信 " + val)
log();
return val;
}

ContentResolver.query
.overload('android.net.Uri', '[Ljava.lang.String;', 'java.lang.String', '[Ljava.lang.String;', 'java.lang.String', 'android.os.CancellationSignal')
.implementation = function (uri, strs1, str2, strs3, str4, signal) {
var val = this.query(uri, strs1, str2, strs3, str4, signal)

console.log("ContentResolver 3 获取短信 " + val)
log();
return val;
}
}



function checkLocation() {
console.log("----------- 定位检查 -----------")

var LocationManager = Java.use("android.location.LocationManager")
LocationManager.getLastLocation.implementation = function () {
var location = this.getLastLocation()

console.log("LocationManager 获取定位 " + location)
log();
return location;
}

LocationManager.getLastKnownLocation.implementation = function () {
var location = this.getLastKnownLocation()

console.log("LocationManager 获取定位 " + location)
log();
return location;
}
}

function checkIPAddress() {
console.log("----------- IP 地址检查 -----------")

var NetworkInterface = Java.use("java.net.NetworkInterface")
NetworkInterface.getInterfaceAddresses.implementation = function () {
var addessList = this.getInterfaceAddresses()

console.log("NetworkInterface 获取 IP 地址 " + addessList)
log();
return addessList;
}

var Inet4Address = Java.use("java.net.Inet4Address")
Inet4Address.getHostAddress.implementation = function () {
var address = this.getHostAddress()

console.log("Inet4Address 获取主机地址 " + address)
log();
return address;
}

var Inet6Address = Java.use("java.net.Inet6Address")
Inet6Address.getHostAddress.implementation = function () {
var address = this.getHostAddress()

console.log("Inet6Address 获取主机地址 " + address)
log();
return address;
}
}

function checkExternalFileRW() {
console.log("----------- 外部文件读写检查 -----------")

var ContextImpl = Java.use("android.app.ContextImpl")
ContextImpl.getExternalFilesDirs.implementation = function (type) {
var files = this.getExternalFilesDirs(type)

console.log("ContextImpl 获取外部文件目录 " + type)
log();
return files;
}

ContextImpl.getExternalMediaDirs.implementation = function () {
var files = this.getExternalMediaDirs()

console.log("ContextImpl 获取媒体文件目录")
log();
return files;
}

ContextImpl.getExternalCacheDirs.implementation = function () {
var files = this.getExternalCacheDirs()

console.log("ContextImpl 获取缓存目录")
log();
return files;
}



var Environment = Java.use("android.os.Environment")
Environment.getExternalStorageDirectory.implementation = function () {
var file = this.getExternalStorageDirectory()

console.log("ContextImpl 获取外部存储目录")
log();
return file;
}

}


function checkIMEI() {

console.log("----------- imei 检查 -----------")

var TelephonyManager = Java.use("android.telephony.TelephonyManager")

//getDeviceId
TelephonyManager.getDeviceId.overload("int").implementation = function (slotIndex) {
var iemi = this.getDeviceId(slotIndex)
console.log("TelephonyManager 获取 IMEI getDeviceId slotIndex = " + slotIndex + " iemi = " + iemi)
log();
return iemi;
}
TelephonyManager.getDeviceId.overload().implementation = function () {
var iemi = this.getDeviceId()
console.log("TelephonyManager 获取 getDeviceId IMEI = " + iemi)
log();
return iemi;
}

//getMeid
TelephonyManager.getMeid.overload("int").implementation = function (slotIndex) {
var iemi = this.getMeid(slotIndex)
console.log("TelephonyManager 获取 IMEI getMeid slotIndex = " + slotIndex + " iemi = " + iemi)
log();
return iemi;
}
TelephonyManager.getMeid.overload().implementation = function () {
var iemi = this.getMeid()
console.log("TelephonyManager 获取 getMeid IMEI = " + iemi)
log();
return iemi;
}


//getImei
TelephonyManager.getImei.overload("int").implementation = function (slotIndex) {
var iemi = this.getImei(slotIndex)
console.log("TelephonyManager 获取 IMEI getImei slotIndex = " + slotIndex + " iemi = " + iemi)
log();
return iemi;
}

TelephonyManager.getImei.overload().implementation = function () {
var iemi = this.getImei()
console.log("TelephonyManager 获取 getImei IMEI = " + iemi)
log();
return iemi;
}

}


function checkOtherId() {


console.log("----------- mac 检查 -----------")

var NetworkInterface = Java.use("java.net.NetworkInterface")
NetworkInterface.getHardwareAddress.implementation = function () {
var mac = this.getHardwareAddress()
console.log("NetworkInterface 获取 MAC = " + mac)
log();
return mac;
}

var WifiInfo = Java.use("android.net.wifi.WifiInfo")
WifiInfo.getMacAddress.implementation = function () {
var mac = this.getMacAddress()
console.log("WifiInfo 获取 MAC = " + mac)
log();
return mac;
}

console.log("----------- SSID 检查 -----------")
WifiInfo.getSSID.implementation = function () {
var ssid = this.getSSID()
console.log("WifiInfo 获取 ssid = " + ssid)
log();
return ssid;
}


console.log("----------- oaid 检查 -----------")

var OAID_LIST = ["com.bun.supplier.IdSupplier",
"com.bun.miitmdid.provider.DefaultProvider",
"com.bun.miitmdid.supplier.IdSupplier",
"com.bun.miitmdid.interfaces.IdSupplier"]

for (let index in OAID_LIST) {
try {
var oaid = Java.use(OAID_LIST[index])
oaid.getOAID.implementation = function () {
var result = this.getOAID()

console.log('获取 oaid = ' + result);
log();
return result
}

} catch (e) {

}
}

console.log("----------- IMSI 检查 -----------")
var TelephonyManager = Java.use("android.telephony.TelephonyManager")
TelephonyManager.getSubscriberId.overload().implementation = function () {
var imsi = this.getSubscriberId()
console.log("TelephonyManager 获取 imsi = " + imsi)
log();
return imsi;
}

TelephonyManager.getSubscriberId.overload('int').implementation = function (index) {
var imsi = this.getSubscriberId(index)
console.log("TelephonyManager 获取 1 imsi = " + imsi)
log();
return imsi;
}

console.log("----------- SN 检查 -----------")
var Build = Java.use("android.os.Build")
Build.getSerial.implementation = function () {
var sn = this.getSerial()
console.log("TelephonyManager 获取 sn = " + sn)
log();
return sn;
}
}

function checkAndroidId() {
console.log("----------- android id检查 -----------")

var ANDROID_ID = "android_id"
var Secure = Java.use("android.provider.Settings$Secure")
Secure.getString.implementation = function (resolver, name) {
var result = this.getString(resolver, name);
console.log("getString name = " + name + " val =" + result)
if (ANDROID_ID == name) {
console.log("getString 获取 androidID")
log();
}
return result;
}

Secure.getStringForUser.implementation = function (resolver, name, userHandle) {
var result = this.getStringForUser(resolver, name, userHandle);
console.log("getStringForUser name = " + name + " val =" + result)
if (ANDROID_ID == name) {
console.log("Secure getStringForUser 获取 androidID")
log();
}
return result;
}

var SecureSystem = Java.use("android.provider.Settings$System")
SecureSystem.getStringForUser.implementation = function (resolver, name, userHandle) {
var result = this.getStringForUser(resolver, name, userHandle);
console.log("System getStringForUser name = " + name + " val =" + result)
if (ANDROID_ID == name) {
console.log("System getStringForUser 获取 androidID")
log();
}

return result;
}
}

function checkPermission() {
console.log("----------- 权限检查 -----------")

var ActivityCompat = Java.use("android.app.Activity")
ActivityCompat.requestPermissions.overload("[Ljava.lang.String;", "int")
.implementation = function (permissions, requestCode) {
console.log("requestPermissions 2 requestCode = " + requestCode + " permissions = " + permissions)

log();
this.requestPermissions(permissions, requestCode)
}

var Fragment = Java.use("android.app.Fragment")
Fragment.requestPermissions.implementation = function (permissions, code) {
console.log('权限申请 android permissions = ' + permissions + " code = " + code);
log();

this.requestPermissions(permissions, code)
}

var Fragmentx = Java.use("androidx.fragment.app.Fragment")
Fragmentx.requestPermissions.implementation = function (permissions, code) {
console.log('权限申请 androidx permissions = ' + permissions + " code = " + code);
log();

this.requestPermissions(permissions, code)
}
}

function checkStartActivity() {
console.log("----------- startActivity 检查 -----------")


var Instrumentation = Java.use('android.app.Instrumentation');
Instrumentation.execStartActivity
.overload(
'android.content.Context',
'android.os.IBinder',
'android.os.IBinder',
'android.app.Activity',
'android.content.Intent',
'int',
'android.os.Bundle')
.implementation =
function (
who, contextThread, token, target, intent, requestCode, options) {
console.log(
'【当前应用 1 Instrumentation】 启动 execStartActivity intent = ' +
intent);
var pkg = intent.getPackage()
console.log('pkg = ' + pkg)
if (pkg != undefined && pkg != NULL && pkg == 'com.xiaomi.market') {
intent.setPackage('com.heytap.market')
}

log();

return this.execStartActivity(
who, contextThread, token, target, intent, requestCode, options);
}

Instrumentation.execStartActivity
.overload(
'android.content.Context',
'android.os.IBinder',
'android.os.IBinder',
"java.lang.String",
'android.content.Intent',
'int',
'android.os.Bundle')
.implementation =
function (
who, contextThread, token, target, intent, requestCode, options) {
console.log(
'【当前应用 2 Instrumentation】 启动 execStartActivity intent = ' +
intent);
var pkg = intent.getPackage()
console.log('pkg = ' + pkg)
if (pkg != undefined && pkg != NULL && pkg == 'com.xiaomi.market') {
intent.setPackage('com.heytap.market')
}

log();

return this.execStartActivity(
who, contextThread, token, target, intent, requestCode, options);
}

Instrumentation.execStartActivity
.overload(
'android.content.Context',
'android.os.IBinder',
'android.os.IBinder',
"java.lang.String",
'android.content.Intent',
'int',
'android.os.Bundle',
"android.os.UserHandle"
)
.implementation =
function (
who, contextThread, token, resultWho, intent, requestCode, options, user) {
console.log(
'【当前应用 3 Instrumentation】 启动 execStartActivity intent = ' +
intent);
var pkg = intent.getPackage()
console.log('pkg = ' + pkg)
if (pkg != undefined && pkg != NULL && pkg == 'com.xiaomi.market') {
intent.setPackage('com.heytap.market')
}

log();

return this.execStartActivity(who, contextThread, token, resultWho, intent, requestCode, options, user)
}

Instrumentation.checkStartActivityResult.implementation = function (res, intent) {
console.log('【checkStartActivityResult 启动 intent = ' + intent);

log();

return this.checkStartActivityResult(res, intent)
}
}