• frida hook java


    Frida 启动

    attach 启动

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

    BASH
    1
    frida -U com.kevin.android -l hook.js --no-pause

    直接附加到当前应用中

    BASH
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    frida -UF -l hook.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()
    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 启动

    BASH
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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 并开启远程连接

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

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

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

    frida

    frida 远程连接自定义端口

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

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

    python

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

    import frida, sys

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

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

    Frida rpc 远程调用

    python

    PYTHON
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    import frida
    import json
    from flask import Flask, jsonify, request

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

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

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

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

    app = Flask(__name__)

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


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

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

    js

    JSX
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    ///<reference path='/Users/zhangyang/node_modules/@types/frida-gum/index.d.ts'/>

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

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

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

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

    // }
    // setImmediate(main);

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

    压力测试

    tmp.json

    JSON
    1
    {"data": "62feb9a98a01945ab06c0dd7823adc57"}

    命令

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

    nps 进行内网穿透

    1. nps server 启动

      mac: sudo nps start

    2. 新建客户端

      https://kevinspider-1258012111.cos.ap-shanghai.myqcloud.com/2021-01-14-032324.png

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

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

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

      https://kevinspider-1258012111.cos.ap-shanghai.myqcloud.com/2021-01-14-032703.png

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

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

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

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

    Hook 普通方法

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

    JS
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    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 构造方法

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

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

    Hook 动静态成员属性

    JS
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    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 内部类

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

    JS
    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()
    JS
    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()
    JS
    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 类的所有方法及重载

    JS
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    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

    JS
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    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, 可以使用以下代码:

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

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

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

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

    JS
    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

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

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

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

    JS
    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 交互

    CODE
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    frida 传递参数
    function main(){
    Java.perform(function () {
    console.log("enter perform");
    // 获取要hook的类
    var TextViewClass = Java.use("android.widget.TextView");
    // 要hook的方法
    TextViewClass.setText.overload('java.lang.CharSequence').implementation = function (ori_input) {
    console.log('enter', 'java.lang.CharSequence');
    console.log('ori_input',ori_input.toString());

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

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

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

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

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

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

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

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

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

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

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

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

    Hook 打印 char

    JS
    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 数组

    JS
    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

    CODE
    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
    遍历打印
    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()打印

    JS
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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 数组

    CODE
    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 打印调用栈

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

    JS
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    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

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

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

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

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

    JS
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    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

    JS
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    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

    JS
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    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

    JS
    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 绕过 root 检测

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

    Java.perform(function() {

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

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

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

    var RootPropertiesKeys = [];

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

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

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

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

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

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

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

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

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

    var loaded_classes = Java.enumerateLoadedClassesSync();

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

    var useKeyInfo = false;

    var useProcessManager = false;

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

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

    var KeyInfo = null;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    }
    });

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

    }
    });

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

    }
    });

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

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

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

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

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

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

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

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

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

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

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

    });

    Hook frida 强制在主线程运行

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

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

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

    JS
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    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 退出

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

    JS
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    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 打印请求调用栈

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

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

    常用打印转换

    JS
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    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
    //工具相关函数
    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));

    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
    }

    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
    }

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

    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
    }
    文章作者: Kevin
    文章链接: http://example.com/fridahookjava/
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 凡墙总是门
  • 相关阅读:
    react-redux
    Vue中常用的UI框架
    vue中router与route的区别
    H5新增input属性
    H5新增的input类型
    菜鸡对作用域链的理解
    自己对路由的一些理解
    浏览器缓存
    黄瓜的不定期更新面试题
    ajax封装
  • 原文地址:https://www.cnblogs.com/coffee520/p/15516225.html
Copyright © 2020-2023  润新知