验证码服务平台 
谷歌人机 
ReCaptcha(v2/v3 通用版), 直接返回 token
ReCaptcha(v2/v3 企业版), 直接返回 token
ReCaptcha(steam), 直接返回 token
CloudFlare 盾通用版, 返回 cf_clearance、__cf_bm 、正确响应源码 html、验证流程使用的 tls 指纹
Incapsula 盾 reese84 通用版, 返回 reese84 cookie 或 solution 参数
Incapsula 盾 __utmvc 通用版, 服务器直接无感验证 或 __utmvc cookie
Hcaptcha 通用版, 直接返回 generated_pass_UUID
直接返回 _abck
tls 转发接口, 针对校验 ja3、http2 等指纹(akamai/cloudflare)的接口
更多支持后续敬请期待..... 
注册连接:http://c.nxw.so/Aqi 
新用户注册加群即送10000点余额 
新用户注册加群即送10000点余额 
新用户注册加群即送10000点余额

1.png

Frida 启动

  • attach 启动

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

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

直接附加到当前应用中

frida -UF -l hook.js
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 启动

frida -U -f com.kevin.android -l demo1.js --no-pause
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 并开启远程连接

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 远程连接自定义端口

# 连接指定 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

# -*- 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
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

///<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
{"data": "62feb9a98a01945ab06c0dd7823adc57"}

命令

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

nps 进行内网穿透

  • nps server 启动

mac: sudo nps start

  • 新建客户端

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

  • 给客户端添加和 frida server 的端口映射

安卓手机启动 frida-server: ./fs12800 -l 0.0.0.0:6666

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

  • python 脚本更改和 frida-server 的连接

此时就可以将 frida-server 开放到公网了;

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

Hook 普通方法

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 重载方法

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 构造方法

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 对象

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

使用 choose 查找对象

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 保存对象

Java.perform(() => {
  const Activity = Java.use('android.app.Activity');
  let lastActivity = null;
  Activity.onResume.implementation = function () {
    lastActivity = Java.retain(this);
    this.onResume();
  };
});

Hook 动静态成员属性

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 内部类

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 匿名类

// 接口, 抽象类, 不可以被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()
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()
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 类的所有方法及重载

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

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, 可以使用以下代码:

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 主动构造数组

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 强制类型转换

// 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 打印类实现的接口

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 枚举

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

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 主动调用构造方法

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 主动调用静态方法

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 主动调用动态方法

// 非静态方法的主动调用 自定义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 交互

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

// 打印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. 使用 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

遍历打印
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()打印
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
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 打印调用栈

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 打印

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

int ֏(int x) {
        return x + 100;
    }

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

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 内存打印

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

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

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

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

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 跳转

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 检测

// $ 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()

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

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 指定方法中过滤打印

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 退出

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 修改设备参数

// 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 打印请求调用栈

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 注入

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

常用打印转换

//工具相关函数
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 数组

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 方法;

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 文件目录下, 这样在执行命令的时候不会出现无法找到的情况;
3.png

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

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

///<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 调用

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 注册接口

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 对发包位置进行回溯

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

本条博客转载自:凡墙总是门

最后修改:2023 年 07 月 26 日
如果觉得我的文章对你有用,请随意赞赏