frida 安装及操作

安装运行

  1. 电脑端安装
pip3 install frida
pip3 install frida-tools
  1. 下载对应版本server https://github.com/frida/frida/releases
    image.png

传到手机上

adb push frida-server-12.11.12-android-arm64 /data/local/tmp
  1. 运行frida-server
adb shell
cd /data/local/tmp

chmod 755 ./frida-server-12.11.12-android-arm64

./frida-server-12.11.12-android-arm64 &

  1. 转发端口:
adb forward tcp:27042 tcp:27043
  1. 验证(列出正在运行的包名):
frida-ps -U

常用工具函数

  1. 打印某个类的所有成员变量
function dumpAllFieldValue(obj) {
    if (obj === null) {
        return;
    }

    console.log("Dump all fields value for  " + obj.getClass() + " :");

    var cls = obj.getClass();

    while (cls !== null && !cls.equals(Java.use("java.lang.Object").class)) {
        var fields = cls.getDeclaredFields();
        if (fields === null || fields.length === 0) {
            cls = cls.getSuperclass();
            continue;
        }

        if (!cls.equals(obj.getClass())) {
            console.log("Dump super class  " + cls.getName() + " fields:");
        }

        for (var i = 0; i < fields.length; i++) {
            var field = fields[i];
            field.setAccessible(true);
            var name = field.getName();
            var value = field.get(obj);
            var type = field.getType();
            console.log(type + " " + name + "=" + value);
        }

        cls = cls.getSuperclass();
    }
}
  1. 获取成员变量的值
function getFieldValue(obj, fieldName) {
    var cls = obj.getClass();
    var field = cls.getDeclaredField(fieldName);
    field.setAccessible(true);
    var name = field.getName();
    var value = field.get(obj);
    // console.log("field: " + field + "\tname:" + name + "\tvalue:" + value);
    return value;
}
  1. 打印调用堆栈
function printStack() {
    Java.perform(function() {
        var Exception = Java.use("java.lang.Exception");
        var ins = Exception.$new("Exception");
        var straces = ins.getStackTrace();
        if (straces != undefined && straces != null) {
            var strace = straces.toString();
            var replaceStr = strace.replace(/,/g, "\r\n");
            console.log(
                "============================= Stack start ======================="
            );
            console.log(replaceStr);
            console.log(
                "============================= Stack end =======================\r\n"
            );
            Exception.$dispose();
        }
    });
}

堆栈调用顺序为自下而上

java层hook

  1. hook模板
# -*- coding: utf-8 -*-
import frida
import sys

hook_code = """
Java.perform(function(){

    var utils = Java.use("类名路径");
    utils.方法名.implementation = function(a, b){

        return retval;
    }
});
"""
process = frida.get_usb_device().attach('包名')
script = process.create_script(hook_code)
script.load()
sys.stdin.read()
  1. hook方法(非重载方法不用写方法类型)
var utils = Java.use("类名路径");
    utils.方法名.implementation = function(a, b){

    a = 123;
    b = 456;

    var retval = this.方法名(a, b);
    console.log(a, b, retval);

    return retval;
}
  1. hook重载方法
var utils = Java.use("类名路径");
utils.方法名.overload("方法类型").implementation = function(a, b){

    a = 123;
    b = 456;

    var retval = this.方法名(a, b);
    console.log(a, b, retval);

    return retval;
}
  1. hook所有重载方法
var utils = Java.use("类名路径");
    //console.log(utils.方法名.overloads.length);
    for(var i = 0; i < utils.方法名.overloads.length; i++){
        utils.方法名.overloads[i].implementation = function(){
            //console.log(JSON.stringify(arguments));

            if(arguments.length == 0){
                return "调用了没有参数的";
            }else if(arguments.length == 1){
                if(JSON.stringify(arguments).indexOf("Money") != -1){
                    return "调用了Money参数的";
                }else{
                    return "调用了int参数的";
                }
            }

            arguments[0] = 1000;
            return this.方法名.apply(this, arguments);
        }
    }

  1. hook 构造方法
Java.perform(function hookTest4(){
    var money = Java.use("类名路径");
    money.$init.overload('重载的参数1', '重载的参数2').implementation = function(str, num){
        console.log(str, num);
        str = "欧元";
        num = 2000;
        this.$init(str, num);
    }
});

  1. 对象实例化
Java.perform(function hookTest2(){
    var utils = Java.use("utils类路径");
    var money = Java.use("money类路径");
    utils.方法名.overload('重载参数').implementation = function(a){
        a = 888;
        var retval = this.方法名(money.$new("日元", 100000));//对象实例化
        console.log(a, retval);
        return retval;
    }
});
  1. 修改类的字段
Java.perform(function(){
    //静态字段的修改
    var money = Java.use("money类路径");
    //console.log(JSON.stringify(money.字段名));
    money.字段名.value = "xxxxx";
    console.log(money.flag.value);

    //非静态字段的修改
    Java.choose("money类路径", {
        onMatch: function(obj){
            obj._name.value = "ouyuan"; //字段名与函数名相同,前面加个下划线
            obj.name.value = "ouyuan"; //字段名与函数名不同
        },
        onComplete: function(){
        }
    });
});

也可以通过java的反射方式修改

function setFieldValue(obj, fieldName, fieldValue) {
    var cls = obj.getClass();
    var field = cls.getDeclaredField(fieldName);
    field.setAccessible(true);
    field.set(obj, fieldValue);
}
  1. hook内部类与匿名类
hook_code = """
Java.perform(function hookTest6(){
    Java.perform(function(){
        var innerClass = Java.use("money类路径$内部类名");
        console.log(innerClass);
        innerClass.$init.implementation = function(a, b){
            a = "nb";
            b = 888;
            return this.$init(a, b);
        }

        var xxx = Java.use("xxx类路径$smali中查看匿名类数字编号");
        console.log(xxx);
        xxx.getInfo.implementation = function(){
            return "匿名类被Hook了"
        }
    });
});
"""
  1. hook类的所有方法
hook_code = """
Java.perform(function hookTest8(){
    Java.perform(function(){
        var md5 = Java.use("md5类路径");
        var methods = md5.class.getDeclaredMethods();
        for(var j = 0; j < methods.length; j++){
            var methodName = methods[j].getName();
            console.log(methodName);

            for(var k = 0; k < md5[methodName].overloads.length; k++){

                md5[methodName].overloads[k].implementation = function(){
                    for(var i = 0; i < arguments.length; i++){
                        console.log(arguments[i]);
                    }
                    return this[methodName].apply(this, arguments);
                }
            }
        }
    });
});
"""
  1. Hook动态加载的dex(Android 7以上)
hook_code = """
Java.perform(function () {
    Java.enumerateClassLoaders({
        onMatch: function (loader) {
            try {
                if (loader.loadClass("com.xiaojianbang.app.Dynamic")) {
                    Java.classFactory.loader = loader;
                    var Dynamic = Java.use("com.xiaojianbang.app.Dynamic");
                    console.log(Dynamic);
                    Dynamic.sayHello.implementation = function () {
                        return "9999999";
                    }
                }
            } catch (error) {
            }
        },
        onComplete: function () {
        }
    });
});
"""

  1. Java特殊类型的遍历与修改(Map举例)
hook_code = """
Java.perform(function () {
    var ShufferMap = Java.use("com.xiaojianbang.app.ShufferMap");
    console.log(ShufferMap);
    ShufferMap.show.implementation = function (map) {
        console.log(JSON.stringify(map));
        //Java map的遍历
        var key = map.keySet();
        var it = key.iterator();
        var result = "";
        while(it.hasNext()){
            var keystr = it.next();
            var valuestr = map.get(keystr);
            result += valuestr;
        }
        console.log(result);
        // return result;

        //Java map的修改
        map.put("pass", "zygx8");
        map.put("guanwang", "www.zygx8.com");

        var retval = this.show(map);
        console.log(retval);
        return retval;
    }
});
"""
  1. 打印HashMap

console.log(JSON.stringify(arguments))
var Map = Java.use('java.util.HashMap');
var args_map = Java.cast(arguments[1], Map);
console.log(args_map.toString());
  1. Java层主动调用
hook_code = """
Java.perform(function () {
    //静态方法的主动调用
    var rsa = Java.use("com.xiaojianbang.app.RSA");
    var str = Java.use("java.lang.String");
    var base64 = Java.use("android.util.Base64");
    var bytes = str.$new("xiaojianbang").getBytes();
    console.log(JSON.stringify(bytes));
    var retval = rsa.encrypt(bytes);
    var result = base64.encodeToString(retval, 0);
    console.log(result);
    //非静态方法的主动调用1 (新建一个对象去调用)
    var res = Java.use("com.xiaojianbang.app.Money").$new("日元", 300000).getInfo();
    console.log(res);
    var utils = Java.use("com.xiaojianbang.app.Utils");
    res = utils.$new().myPrint(["xiaojianbang", "is very good", " ", "zygx8", "is very good"]);
    console.log(res);
    //非静态方法的主动调用2 (获取已有的对象调用)
    Java.choose("com.xiaojianbang.app.Money", {
        onMatch: function (obj) {
            if (obj._name.value == "美元") {
                res = obj.getInfo();
                console.log(res);
            }
        },
        onComplete: function () {
        }
    });
});
"""

  1. 删除对象引用
$.dispose
  1. 获取参数类型
xxx.class.getType()
  1. 用frida注入dex文件
hook_code = """
Java.perform(function () {
    Java.openClassFile("/data/local/tmp/xiaojianbang.dex").load();
    var xiaojianbang = Java.use("com.xiaojianbang.test.xiaojianbang");

    var ShufferMap = Java.use("com.xiaojianbang.app.ShufferMap");
    ShufferMap.show.implementation = function (map) {
        var retval = xiaojianbang.sayHello(map);
        console.log(retval);
        return retval;
    }

});
"""
  1. 端口检测解决方案
./data/local/tmp/frida_server_arm64 -l 127.0.0.1:9999

adb forward tcp:9999 tcp:9999
  1. frida启动前注入
frida -H 127.0.0.1:9999 -f com.xjb.cpp -l hook.js --no-pause

so 层hook

枚举导入导出表(ELF即so文件)

  1. 枚举导入表
var imports = Module.enumerateImports("libxiaojianbang.so");
for(var i = 0; i < imports.length; i++){
    if(imports[i].name == "strncat"){
        console.log(JSON.stringify(imports[i]));
        console.log(imports[i].address);
    }
}
  1. 枚举导出表
var exports = Module.enumerateExports("libxiaojianbang.so");
for(var i = 0; i < exports.length; i++){
    //if(exports[i].name == "strncat"){
        console.log(JSON.stringify(exports[i]));
    //}
}
  1. hook导出函数
hook_code = """
Java.perform(function hookTest2() {
    var helloAddr = Module.findExportByName("libxiaojianbang.so", "Java_com_xiaojianbang_app_NativeHelper_add");
    console.log(helloAddr);
    if (helloAddr != null) {
        Interceptor.attach(helloAddr, {
            onEnter: function (args) {
                //args参数数组
                console.log(args[0]);
                console.log(args[1]);
                console.log(args[2]);
                console.log(args[3]);
                console.log(args[4].toInt32());
            },
            onLeave: function (retval) {
                //retval函数返回值
                console.log(retval);
                console.log("retval", retval.toInt32());
            }
        });
    }
});
"""
  1. 函数地址计算
function hookTest14(){
    var soAddr = Module.findBaseAddress("libxiaojianbang.so");
    console.log(soAddr);
    var funcAddr = soAddr.add(0x23F4);
    console.log(funcAddr);
}
  1. Hook未导出函数
function hookTest14(){
    var soAddr = Module.findBaseAddress("libxiaojianbang.so");
    console.log(soAddr);
    var funcAddr = soAddr.add(0x23F4);
    console.log(funcAddr);

    if(funcAddr != null){
        Interceptor.attach(funcAddr,{
            onEnter: function(args){

            },
            onLeave: function(retval){
                console.log(hexdump(retval));
            }
        });
     }
}
  1. 获取指针参数返回值
function hookTest5(){
    var soAddr = Module.findBaseAddress("libxiaojianbang.so");
    console.log(soAddr);
    var sub_930 = soAddr.add(0x930); //函数地址计算 thumb+1 ARM不加
    console.log(sub_930);

     var sub_208C = soAddr.add(0x208C); //函数地址计算 thumb+1 ARM不加
     console.log(sub_208C);
     if(sub_208C != null){
        Interceptor.attach(sub_208C,{
            onEnter: function(args){
                this.args1 = args[1];
            },
            onLeave: function(retval){
                console.log(hexdump(this.args1));
            }
        });
     }
}
  1. Hook_dlopen
function hookTest6(){
    var dlopen = Module.findExportByName(null, "dlopen");
    console.log(dlopen);
    if(dlopen != null){
        Interceptor.attach(dlopen,{
            onEnter: function(args){
                var soName = args[0].readCString();
                console.log(soName);
                if(soName.indexOf("libxiaojianbang.so") != -1){
                    this.hook = true;
                }
            },
            onLeave: function(retval){
                if(this.hook) { hookTest5() };
            }
        });
    }

    var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
    console.log(android_dlopen_ext);
    if(android_dlopen_ext != null){
        Interceptor.attach(android_dlopen_ext,{
            onEnter: function(args){
                var soName = args[0].readCString();
                console.log(soName);
                if(soName.indexOf("libxiaojianbang.so") != -1){
                    this.hook = true;
                }
            },
            onLeave: function(retval){
                if(this.hook) { hookTest5() };
            }
        });
    }

}
  1. 内存读写
function hookTest7(){
    var soAddr = Module.findBaseAddress("libxiaojianbang.so");
    console.log(soAddr);
    if(soAddr != null){
        //console.log(soAddr.add(0x2C00).readCString());
        //console.log(hexdump(soAddr.add(0x2C00)));  //读取指定地址的字符串

        //var strByte = soAddr.add(0x2C00).readByteArray(16); //读内存
        //console.log(strByte);

        //soAddr.add(0x2C00).writeByteArray(stringToBytes("xiaojianbang")); //写内存
        //console.log(hexdump(soAddr.add(0x2C00)));  //dump指定内存

        //var bytes = Module.readByteArray(soAddr.add(0x2C00), 16);
        //console.log(bytes);

    }
}
  1. 主动调用JNI函数
function hookTest8(){
    var funcAddr = Module.findExportByName("libxiaojianbang.so", "Java_com_xiaojianbang_app_NativeHelper_helloFromC");
    console.log(funcAddr);
    if(funcAddr != null){
        Interceptor.attach(funcAddr,{
            onEnter: function(args){

            },
            onLeave: function(retval){
                var env = Java.vm.tryGetEnv();
                var jstr = env.newStringUtf("www.zygx8.com");  //主动调用jni函数 cstr转jstr
                retval.replace(jstr);
                var cstr = env.getStringUtfChars(jstr); //主动调用 jstr转cstr
                console.log(cstr.readCString());
                console.log(hexdump(cstr));
            }
        });
    }
}
  1. jni函数Hook(计算地址方式)
function hookTest9(){
    Java.perform(function(){
        //console.log(JSON.stringify(Java.vm.tryGetEnv()));
        var envAddr = ptr(Java.vm.tryGetEnv().handle).readPointer();
        var newStringUtfAddr = envAddr.add(0x538).readPointer();
        var registerNativesAddr = envAddr.add(1720).readPointer();
        console.log("newStringUtfAddr", newStringUtfAddr);
        console.log("registerNativesAddr", registerNativesAddr)
        if(newStringUtfAddr != null){
            Interceptor.attach(newStringUtfAddr,{
                onEnter: function(args){
                    console.log(args[1].readCString());
                    //args[1] = "xiaojianbang is very good!";
                },
                onLeave: function(retval){

                }
            });
        }
        if(registerNativesAddr != null){     //Hook registerNatives获取动态注册的函数地址
            Interceptor.attach(registerNativesAddr,{
                onEnter: function(args){
                    console.log(args[2].readPointer().readCString());
                    console.log(args[2].add(Process.pointerSize).readPointer().readCString());
                    console.log(args[2].add(Process.pointerSize * 2).readPointer());
                    console.log(hexdump(args[2]));
                    console.log("sub_289C", Module.findBaseAddress("libxiaojianbang.so").add(0x289C));
                },
                onLeave: function(retval){

                }
            });
        }

    });
}
  1. jni函数Hook(libart.so)
function hookTest10(){
    var artSym = Module.enumerateSymbols("libart.so");
    var NewStringUTFAddr = null;
    for(var i = 0; i < artSym.length; i++){
        if(artSym[i].name.indexOf("CheckJNI") == -1 && artSym[i].name.indexOf("NewStringUTF") != -1){
            console.log(JSON.stringify(artSym[i]));
            NewStringUTFAddr = artSym[i].address;
        }
    };

    if(NewStringUTFAddr != null){
        Interceptor.attach(NewStringUTFAddr,{
            onEnter: function(args){
                console.log(args[1].readCString());
            },
            onLeave: function(retval){

            }
        });
    }

}
  1. so层函数主动调用
function hookTest11(){
    Java.perform(function(){
        var funcAddr = Module.findBaseAddress("libxiaojianbang.so").add(0x23F4);
        var func = new NativeFunction(funcAddr, "pointer", ['pointer', 'pointer']);
        var env = Java.vm.tryGetEnv();
        console.log("env: ", JSON.stringify(env));
        if(env != null){
            var jstr = env.newStringUtf("xiaojianbang is very good!!!");
            //console.log("jstr: ", hexdump(jstr));
            var cstr = func(env, jstr);
            console.log(cstr.readCString());
            console.log(hexdump(cstr));
        }
    });
}
  1. frida读写文件
//frida API 读写文件
function hookTest12(){
    var ios = new File("/sdcard/xiaojianbang.txt", "w");
    ios.write("xiaojianbang is very good!!!\n");
    ios.flush();
    ios.close();
}
//Hook libc 读写文件
function hookTest13() {

    var addr_fopen = Module.findExportByName("libc.so", "fopen");
    var addr_fputs = Module.findExportByName("libc.so", "fputs");
    var addr_fclose = Module.findExportByName("libc.so", "fclose");

    console.log("addr_fopen:", addr_fopen, "addr_fputs:", addr_fputs, "addr_fclose:", addr_fclose);
    var fopen = new NativeFunction(addr_fopen, "pointer", ["pointer", "pointer"]);
    var fputs = new NativeFunction(addr_fputs, "int", ["pointer", "pointer"]);
    var fclose = new NativeFunction(addr_fclose, "int", ["pointer"]);

    var filename = Memory.allocUtf8String("/sdcard/xiaojianbang.txt");
    var open_mode = Memory.allocUtf8String("w");
    var file = fopen(filename, open_mode);
    console.log("fopen:", file);

    var buffer = Memory.allocUtf8String("zygxb\n");
    var retval = fputs(buffer, file);
    console.log("fputs:", retval);

    fclose(file);

}

RPC

import frida
import sys

rdev = frida.get_usb_device()
session = rdev.attach("com.yuanrenxue.onlinejudge2020")  # 包名

js_code = """
rpc.exports = {
    getsign: function(i) {
        Java.perform(function() {
            console.log("get_sign");
            var my_class1 = Java.use("com.yuanrenxue.onlinejudge2020.OnlineJudgeApp");
            var reslut = my_class1.getSign1(i);
            console.log(reslut);
            send({ "sign": reslut, "num": i })
            return reslut;
        });
    },
};

"""

script = session.create_script(js_code)


def on_message(message, data):
    sign = message.get("payload").get("sign")
    num = message.get("payload").get("num")


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

script.exports.getsign(1) # 调用的函数

sys.stdin.read()

通过wifiadb实现群控

import frida
import os
import time

app = "com.xxx.xxx";

device_ids = [];
devices = frida.enumerate_devices();
for device in devices :
    # print(device)
    ## 枚举所有通过wifiadb 连的机器
    if device.id.find(":") > 0:
        #print(device.id)
        device_ids.append(device.id.replace("5555", "9999"))

for id in device_ids :
    print(id)
    device = frida.get_device_manager().add_remote_device(id)
    print(device)
    pid = device.spawn([app])
    print(pid)
    device.resume(pid)
    time.sleep(1)
    session = device.attach(pid)
    with open("load_hook.js") as f:
        script = session.create_script(f.read())
        script.load()
input()

常用命令

  1. 列出正在运行的进程:
frida-ps -U
  1. 列出安装的程序
frida-ps -Uai
  1. 列出运行中的程序(查看包名很方便)
frida-ps -Ua
  1. 连接frida到一个指定的设备上
frida-ps -D 设备id

另外还有四个分别是:frida-trace, frida-discover, frida-ls-devices, frida-kill

objection 使用

Frida只是提供了各种API供我们调用,在此基础之上可以实现具体的功能,比如禁用证书绑定之类的脚本,就是使用Frida的各种API来组合编写而成。于是有大佬将各种常见、常用的功能整合进一个工具,供我们直接在命令行中使用,这个工具便是objection。

  1. 安装
pip3 install objection
  1. 连接app
objection -g 包名 explore
  1. Memory 指令
memory list modules  // 查看内存中加载的库
memory list exports libssl.so  // 查看库的导出函数
memory list exports libart.so --json /root/libart.json //将结果保存到json文件中
memory search --string --offsets-only //搜索内存
memory search "64 65 78 0a 30 35 00"
  1. root
android root disable //尝试关闭app的root检测
android root simulate //尝试模拟root环境

  1. activities
android hooking list activities // 可以列出app具有的所有avtivity
  1. 内存漫游
//列出内存中所有的类
android hooking list classes

//在内存中所有已加载的类中搜索包含特定关键词的类
android hooking search classes [search_name] 

//在内存中所有已加载的方法中搜索包含特定关键词的方法
android hooking search methods [search_name] 

//直接生成hook代码
android hooking generate simple [class_name]

// 查看类的全部广法
android hooking list class_methods [class_name]
  1. hook 方式

hook指定方法, 如果有重载会hook所有重载,如果有疑问可以看
--dump-args : 打印参数
--dump-backtrace : 打印调用栈
--dump-return : 打印返回值

// 查看方法的参数、返回值和调用栈
android hooking watch class_method com.xxx.xxx.methodName --dump-args --dump-backtrace --dump-return

//获取全部tostring的返回来值
android hooking watch class_method java.lang.StringBuilder.toString --dump-return

//弹窗
android hooking watch class_method android.app.Dialog.show --dump-args --dump-backtrace --dump-return

//hook指定类, 会打印该类下的所有调用 
android hooking watch class com.xxx.xxx 

//设置返回值(只支持bool类型) 
android hooking set return_value com.xxx.xxx.methodName false
  1. 关闭app的ssl校验
android sslpinning disable
  1. Spawn方式Hook
    从Objection的使用操作中我们可以发现,Obejction采用Attach附加模式进行Hook,这可能会让我们错过较早的Hook时机,可以通过如下的代码启动Objection,引号中的objection命令会在启动时就注入App。
objection -g packageName explore --startup-command 'android hooking watch xxx' 

ARIDA

管理PRC脚本,自动生成http接口的工具

  1. 安装 下载
git clone git@github.com:lateautumn4lin/arida.git

使用conda安装

conda create -n arida python==3.8
conda install --yes --file requirements.txt

使用pip安装

virtualenv venv
source venv/bin/activate

// 下载pip安装格式的requirements.txt https://github.com/Boris-code/arida/blob/master/requirements.txt

pip install -r requirements.txt
  1. 运行
uvicorn main:app --reload
watch 127.0.0.1:8000/docs

image.png
3. 开发

Config文件中写入自己的App信息
apps目录写开发相应的Frida-Js脚本,可参考其他两个文件

参考

  1. Objection:https://www.cnblogs.com/qiaorui/p/13455420.html

  2. Firda:https://juejin.cn/post/6844904127705661448#heading-9

  3. ARIDA: https://github.com/lateautumn4lin/arida

posted @ 2020-12-25 15:18  wzqwer  阅读(448)  评论(0编辑  收藏  举报