Skip to content

Instantly share code, notes, and snippets.

@jimmy947788
Last active December 5, 2025 00:00
Show Gist options
  • Select an option

  • Save jimmy947788/37cce57a889536fafd377e3b4243275e to your computer and use it in GitHub Desktop.

Select an option

Save jimmy947788/37cce57a889536fafd377e3b4243275e to your computer and use it in GitHub Desktop.
frida 脚本大全

脚本大全

frida 快速开始

npm安装

npm i  @types/frida-gum

python安装

pip install frida
pip install frida-tools

执行一段简单的js代码

function main(){
    Java.perform(()=>{
        console.log("成功运行")
    })
}
setImmediate(main)

执行命令

frida -U -f 包名 -l 脚本.js

其中-U是指定设置,-f是以spawn模式启动,也可以

frida -H 192.168.0.100:14725 -F -l 脚本.js

-F是以attach模式启动,-H是连接这个地址,frida-server也需要改掉相关的端口

frida-server -l 0.0.0.0:14725

基本上用的最多的运行命令就是这些

如果不是app,可以这样子

attach

frida -U -p 3173 -l your_script.js

spawn

frida -U -f /path/to/your/executable -l your_script.js

Java层,基础函数

hashmap相关的hook
//由于有些请求头会使用这个添加,可能通过okhttp直接增加
var hashMap = Java.use("java.util.HashMap");
hashMap.put.implementation = function (a, b) {
    console.log("hashMap.put: ", a, b);
    return this.put(a, b);
}

// java.util.concurrent.ConcurrentHashMap
var ConcurrentHashMap = Java.use("java.util.concurrent.ConcurrentHashMap");
ConcurrentHashMap.put.implementation = function (a, b) {
    console.log("ConcurrentHashMap.put: ", a, b);
    return this.put(a, b);
}

// java.util.LinkedHashMap
var LinkedHashMapClass = Java.use("java.util.LinkedHashMap");
LinkedHashMapClass.put.implementation = function (key, value) {
    console.log("LinkedHashMap key:", key, "value:", value);
    return this.put(key, value);
};
URL相关hook
// hook java.net.URL
var URL = Java.use('java.net.URL');
URL.$init.overload('java.lang.String').implementation = function (a) {
    console.log('java.net.URL ' + a)
    this.$init(a)
}

//hook okhttp3 HttpUrl
var Builder = Java.use('okhttp3.Request$Builder');
Builder.url.overload('okhttp3.HttpUrl').implementation = function (a) {
    var res = this.url(a);
    console.log("okhttp3.HttpUrl result: " + res)
    return res;
}
okhttp3的headers的hook
var Builder = Java.use("okhttp3.Request$Builder");
Builder["addHeader"].implementation = function (str, str2) {
    console.log("okhttp3.Request$Builder.addHeader key: " + str)
    console.log("okhttp3.Request$Builder.addHeader val: " + str2)
    var result = this["addHeader"](str, str2);
    console.log("okhttp3.Request$Builder.addHeader result: " + result);
    return result;
};
日志
var log = Java.use("android.util.Log");
log.w.overload('java.lang.String', 'java.lang.String').implementation = function (tag, message) {
    console.log("log.w: ", tag, message);
    return this.w(tag, message);
}
JSON相关,用的非常多

一般来说json都会使用gson这个库

// JSON处理
var jSONObject = Java.use("org.json.JSONObject");
jSONObject.put.overload('java.lang.String', 'java.lang.Object').implementation = function (a, b) {
    console.log("jSONObject.put: ", a, b);
    return this.put(a, b);
}
jSONObject.getString.implementation = function (a) {
    console.log("jSONObject.getString: ", a);
    var result = this.getString(a);
    console.log("jSONObject.getString result: ", result);
    return result;
}
JSONObject['optString'].overload('java.lang.String').implementation = function (str) {
    if(str === "data"){
        console.log('str', str)
        getStackTraceString();
    }
    let result = this['optString'](str);
    return result;
};
弹窗
// 弹窗关键类
var toast = Java.use("android.widget.Toast");
toast.show.implementation = function () {
    console.log("toast.show: ");
    return this.show();
}
base64
var base64 = Java.use("android.util.Base64");
base64.encodeToString.overload('[B', 'int').implementation = function (a, b) {
    console.log("base64.encodeToString: ", JSON.stringify(a));
    var result = this.encodeToString(a, b);
    console.log("base64.encodeToString result: ", result)
    return result;
}
SharedPreferences类相关

sp是存储数据的一个类似于哈希表的一个东西,可以存储数据,提取数据,存储类似于字符串等相关数据

// hook内部存储api,打印出存储的数据
var sp = Java.use("android.app.SharedPreferencesImpl$EditorImpl");
sp.putBoolean.overload('java.lang.String', 'boolean').implementation = function(arg1,arg2){
    console.log("[SharedPreferencesImpl ] putBoolean -> key: "+arg1+" = "+arg2);
    return this.putBoolean(arg1,arg2);
}

sp.putString.overload('java.lang.String', 'java.lang.String').implementation = function(arg1,arg2){
    console.log("[SharedPreferencesImpl] putString -> key: "+arg1+" = "+arg2);
    return this.putString(arg1,arg2);
}

sp.putInt.overload('java.lang.String', 'int').implementation = function(arg1,arg2){
    console.log("[SharedPreferencesImpl] putInt -> key: "+arg1+" = "+arg2);
    return this.putInt(arg1,arg2);
}

sp.putFloat.overload('java.lang.String', 'float').implementation = function(arg1,arg2){
    console.log("[SharedPreferencesImpl] putFloat -> key: "+arg1+" = "+arg2);
    return this.putFloat(arg1,arg2);
}

sp.putLong.overload('java.lang.String', 'long').implementation = function(arg1,arg2){
    console.log("[SharedPreferencesImpl] putLong -> key: "+arg1+" = "+arg2);
    return this.putLong(arg1,arg2);
}

// hook应用程序间数据传递的api,打印出传递数据的uri与具体的字段
var content = Java.use("android.content.ContentResolver");
content.insert.overload("android.net.Uri","android.content.ContentValues").implementation = function(arg1,arg2){
    console.log("[ContentResolver] *insert -> Uri: "+arg1+"  Values: "+arg2);
    return this.insert(arg1,arg2);
}

content.delete.overload("android.net.Uri","java.lang.String","[Ljava.lang.String;").implementation = function(arg1,arg2,arg3){
    console.log("[ContentResolver] *delete -> Uri: "+arg1+"\n  -> arg2: "+arg2+"\n  -> arg3: "+arg3);
    return this.delete(arg1,arg2,arg3);
}

content.update.overload('android.net.Uri','android.content.ContentValues','java.lang.String','[Ljava.lang.String;').implementation = function(arg1,arg2,arg3,arg4){
    console.log("[ContentResolver] *update -> Uri: "+arg1+"\n  -> arg2: "+arg2+"\n  -> arg3: "+arg3+"\n  -> arg4: "+arg4);
    return this.update(arg1,arg2,arg3,arg4);
}

content.query.overload('android.net.Uri', '[Ljava.lang.String;', 'android.os.Bundle', 'android.os.CancellationSignal').implementation = function(arg1,arg2,arg3,arg4){
    console.log("[ContentResolver] *query -> Uri: "+arg1+"\n  -> arg2: "+arg2+"\n  -> arg3: "+arg3+"\n  -> arg4: "+arg4);
    return this.query(arg1,arg2,arg3,arg4);
}

content.query.overload('android.net.Uri', '[Ljava.lang.String;', 'java.lang.String', '[Ljava.lang.String;', 'java.lang.String').implementation = function(arg1,arg2,arg3,arg4,arg5){
    console.log("[ContentResolver] *query -> Uri: "+arg1+"\n  -> arg2: "+arg2+"\n  -> arg3: "+arg3+"\n  -> arg4: "+arg4+"\n  -> arg5: "+arg5);
    return this.query(arg1,arg2,arg3,arg4,arg5);
}

content.query.overload('android.net.Uri', '[Ljava.lang.String;', 'java.lang.String', '[Ljava.lang.String;', 'java.lang.String', 'android.os.CancellationSignal').implementation = function(arg1,arg2,arg3,arg4,arg5,arg6){
    console.log("[ContentResolver] *query -> Uri: "+arg1+"\n  -> arg2: "+arg2+"\n  -> arg3: "+arg3+"\n  -> arg4: "+arg4+"\n  -> arg5: "+arg5+"\n arg6: "+arg6);
    return this.query(arg1,arg2,arg3,arg4,arg5,arg6);
}
打开页面相关
function WebView() {
    let WebView = Java.use("android.webkit.WebView");
    WebView["postUrl"].implementation = function (str, bArr) {
        var string = java.use('java.lang.String').$new(bArr);
        console.log(`WebView.postUrl is called: str=${str}, string=${string}`);
        this["postUrl"](str, bArr);
    };
    WebView["loadUrl"].overload('java.lang.String').implementation = function (str) {
        console.log(`WebView.loadUrl is called: str=${str}`);
        var s = Java.use('java.lang.String').$new(str);
        var t = Java.use('java.lang.String').$new("https");
        if (s.contains(t)) {
            getStackTraceString();
        }
        this["loadUrl"](str);
    };
    WebView["loadUrl"].overload('java.lang.String', 'java.util.Map').implementation = function (str, map) {
        console.log(`WebView.loadUrl 2is called: str=${str}, map=${map}`);
        this["loadUrl"](str, map);
    };
}
加壳的hook

加壳之后的hook需要使用classLoader,每一个加载的dex都对应有一个classLoader,然后它们之间互相之间的函数调用,也需要使用到对方的classLoader才可以,没有办法直接使用,我们hook也需要使用到这些,因为这样子才能hook到这个java函数的具体地址,然后变成一个native函数再来进行hook

Java.enumerateClassLoadersSync().forEach(classLoader => {
    try {
        if (classLoader.loadClass("ot2.b")) {
            Java.classFactory.loader = classLoader;
            console.log(classLoader)
            let C82252b = Java.use("ot2.b");
            C82252b["getBdOz"].implementation = function (context) {
                console.log(`C82252b.getBdOz is called: context=${context}`);
                let result = this["getBdOz"](context);
                console.log(`C82252b.getBdOz result=${result}`);
                return result;
            };
        }
    } catch (e) {
        // console.log(e)
    }
})

Java层,功能

基础操作
// 获取 Java 类的引用
var ExampleClass = Java.use('com.example.ExampleClass');
// 使用 new 关键字来创建 ExampleClass 的一个实例
var exampleObject = ExampleClass.$new();
// 使用对象进行一些操作,例如调用其方法
exampleObject.someMethod();

// 强制转换,obj转String
var castValue = Java.cast(obj, Java.use("java.lang.String"))

// 获取对象属性内容,得到的这个对象可以使用.value
// 也可以直接执行函数,是a.函数,而不是a.value.函数
obj.a.value
sslpinning
Java.perform(function () {
    console.log("");
    console.log("[.] Cert Pinning Bypass/Re-Pinning");

    var CertificateFactory = Java.use("java.security.cert.CertificateFactory");
    var FileInputStream = Java.use("java.io.FileInputStream");
    var BufferedInputStream = Java.use("java.io.BufferedInputStream");
    var X509Certificate = Java.use("java.security.cert.X509Certificate");
    var KeyStore = Java.use("java.security.KeyStore");
    var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");
    var SSLContext = Java.use("javax.net.ssl.SSLContext");

    // Load CAs from an InputStream
    console.log("[+] Loading our CA...")
    var cf = CertificateFactory.getInstance("X.509");

    // 这里需要ca证书
    try {
        console.log("加载ca 文件")
        var fileInputStream = FileInputStream.$new("/data/local/tmp/cer/desktop.cer");
    } catch (err) {
        console.log("[o] " + err);
        return
    }

    var bufferedInputStream = BufferedInputStream.$new(fileInputStream);
    var ca = cf.generateCertificate(bufferedInputStream);
    bufferedInputStream.close();

    var certInfo = Java.cast(ca, X509Certificate);
    console.log("[o] Our CA Info: " + certInfo.getSubjectDN());

    // Create a KeyStore containing our trusted CAs
    console.log("[+] Creating a KeyStore for our CA...");
    var keyStoreType = KeyStore.getDefaultType();
    var keyStore = KeyStore.getInstance(keyStoreType);
    keyStore.load(null, null);
    keyStore.setCertificateEntry("ca", ca);

    // Create a TrustManager that trusts the CAs in our KeyStore
    console.log("[+] Creating a TrustManager that trusts the CA in our KeyStore...");
    var tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
    var tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
    tmf.init(keyStore);
    console.log("[+] Our TrustManager is ready...");

    console.log("[+] Hijacking SSLContext methods now...")
    console.log("[-] Waiting for the app to invoke SSLContext.init()...")

    SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").implementation = function (a, b, c) {
        console.log("[o] App invoked javax.net.ssl.SSLContext.init...");
        SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").call(this, a, tmf.getTrustManagers(), c);
        console.log("[+] SSLContext initialized with our custom TrustManager!");
    }
});
客户端证书校验

(r0capture的)

if (Java.available) {
    Java.perform(function () {
        function storeP12(pri, p7, p12Path, p12Password) {
            var X509Certificate = Java.use("java.security.cert.X509Certificate")
            var p7X509 = Java.cast(p7, X509Certificate);
            var chain = Java.array("java.security.cert.X509Certificate", [p7X509])
            var ks = Java.use("java.security.KeyStore").getInstance("PKCS12", "BC");
            ks.load(null, null);
            ks.setKeyEntry("client", pri, Java.use('java.lang.String').$new(p12Password).toCharArray(), chain);
            try {
                var out = Java.use("java.io.FileOutputStream").$new(p12Path);
                ks.store(out, Java.use('java.lang.String').$new(p12Password).toCharArray())
            } catch (exp) {
                console.log(exp)
            }
        }

        //在服务器校验客户端的情形下,帮助dump客户端证书,并保存为p12的格式,证书密码为r0ysue
        Java.use("java.security.KeyStore$PrivateKeyEntry").getPrivateKey.implementation = function () {
            var result = this.getPrivateKey()
            var packageName = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext().getPackageName();
            // /data/data/com.paokeji.yiqu/files/cer
            storeP12(this.getPrivateKey(), this.getCertificate(), '/data/data/' + packageName + "/files/" + "base" + '.p12', 'r0ysue');
            console.log("dumpClinetCertificate=>" + '/data/data/' + packageName + "/files/" + "base" + '.p12' + '   pwd: r0ysue');
            // console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
            return result;
        }

        Java.use("java.security.KeyStore$PrivateKeyEntry").getCertificateChain.implementation = function () {
            var result = this.getCertificateChain()
            var packageName = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext().getPackageName();
            storeP12(this.getPrivateKey(), this.getCertificate(), '/data/data/' + packageName + "/files/base.p12", 'r0ysue');
            console.log("dumpClinetCertificate=>" + '/data/data/' + packageName + "/files/base.p12" + '   pwd: r0ysue');
            return result;
        }
        console.log("开始hook")
    })
}
打印对象全部属性
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();
    }
}
字节utf8打印
function utf8Bytes(exampleBytes) {
    let StringClass = Java.use("java.lang.String");
    let CharsetClass = Java.use("java.nio.charset.Charset");
    let utf8Charset = CharsetClass.forName("UTF-8");
    return StringClass.$new(exampleBytes, utf8Charset);
}
map打印
function maptoJson(map){
    var Gson = Java.use('com.google.gson.Gson').$new();
    Gson.toJsonTree(map).getAsJsonObject();
}
打印堆栈
function getStackTraceString() {
    console.log(Java.use('android.util.Log')
        .getStackTraceString(Java.use('java.lang.Throwable')
            .$new()));
}

so层,基础函数

so层NewStringUtf8
function hook_NewStringUTF() {
    var artModule = Process.findModuleByName("libart.so");
    var symbols = artModule.enumerateSymbols();
    var newStringUTF = null;
    for (let i = 0; i < symbols.length; i++) {
        let symbol = symbols[i];
        if (symbol.name.indexOf("NewStringUTF") !== -1) {
            console.log(symbol.name);
            newStringUTF = symbol.address;
        }
    }
    if (newStringUTF) {
        Interceptor.attach(newStringUTF, {
            onEnter: function (args) {
                let string = args[1].readCString()
                console.log("[字符串]:", string);
                console.log("[调用栈]:",Thread.backtrace(this.context, Backtracer.FUZZY).map(DebugSymbol.fromAddress).join('\n') + '\n');
            }, onLeave: function (retval) {
            }
        })
    } else {
        console.log("没有这个函数")
    }
}
so层dlsym函数

这个函数是用来查询具体函数地址的,参数为handle(so文件基地址),函数名称,返回为这个函数的地址

function hook_dlsym(){
    // 获取dlsym函数的地址
    let dlsymAddr = Module.findExportByName("libdl.so","dlsym");
    console.log(dlsymAddr);
    // hook dlsym
    Interceptor.attach(dlsymAddr,{
        onEnter:function(args){
            this.args1 = args[1];
        },
        onLeave:function(retval){
            let md= Process.findModuleByAddress(retval);
            if(md==null)return;
            console.log("函数:"+this.args1.readCString(),"模块:"+md.name,"地址:"+retval,"偏移:"+retval.sub(md.base));
        }
    })  
}

我们静态注册的函数,native层和java层联系起来的函数,就会经过这个,这个代码也可以被用来查找相关的静态注册的代码。

so层dlopen函数

第一个

function inline_hook(_module) {
}

//8.0以下所有的so加载都通过dlopen
function hook_dlopen() {
    Interceptor.attach(Module.findExportByName(null, "dlopen"), {
        onEnter: function (args) {
            this.call_hook = false;
            this.modulePath = ptr(args[0]).readCString();
            console.log("[dlopen] call dlopen:",  this.modulePath);
            // if ( this.modulePath.indexOf("libxx.so") >= 0) {
            //     console.log("dlopen:", ptr(args[0]).readCString());
            //     this.call_hook = true;//dlopen函数找到了
            // }

        }, onLeave: function (retval) {
            if (this.call_hook) {//dlopen函数找到了就hook so
                let targetModule = Process.findModuleByName(this.modulePath);
                inline_hook(targetModule);
            }
        }
    });
    // 高版本Android系统使用android_dlopen_ext
	Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function (args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                this.modulePath = ptr(pathptr).readCString();
                console.log("[dlopen] call android_dlopen_ext:",  this.modulePath);
                // if ( this.modulePath.indexOf("libxxx.so") !== -1) {
                //     this.BookReader4Android = true
                // }
            }
        },
        onLeave: function (retval) {
            if (this.BookReader4Android) {
                let targetModule = Process.findModuleByName(this.modulePath);
                inline_hook(targetModule);
            }
        }
    });
}

setImmediate(hook_dlopen);

第二个

function hook_dlopen(so_name) {
    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function (args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                var path = ptr(pathptr).readCString();
                console.log(path)
                if (path.indexOf(so_name) !== -1) {
                    this.match = true
                }
            }
        },
        onLeave: function (retval) {
            if (this.match) {
                console.log(so_name, "加载成功")
            }
        }
    });
}
so层open函数hook
var open_addr = Module.findExportByName(null, "open");
    Interceptor.attach(open_addr, {
        onEnter: function (args) {
            const filename = args[0].readCString()
            console.log(filename)
        },
        onLeave: function (retval){
            if (this.maps_file){
                retval.replace(0)
            }
        }
    })
hook这个exit函数kill函数的

但是基本上没什么用,以为在so里面,可以通过系统调用,即svc 0这种命令,具体不清楚,但是也可以关闭当前进程

function hook_exit() {
    var exit = Module.findExportByName(null, "exit")
    console.log("exit", exit)
    null !== exit && Interceptor.attach(exit,
        {
            onEnter: function (args) {
                var args1 = args[0];
                if (args1 !== undefined && args1 != null) {
                    var status = args1.readInt();
                    console.log("exit " + status);
                }
            }
        }
    );
}

function hook_kill() {
    const killAddr = Module.findExportByName(null, 'kill');
    console.log("kill", killAddr)
    null !== killAddr && Interceptor.attach(killAddr, {
        onEnter: function (args) {
            console.log('kill called with pid: ' + args[0].toInt32() + ', signal: ' + args[1].toInt32());
            // 在这里可以修改 args 来改变行为
        },
        onLeave: function (retval) {
            console.log('kill returned: ' + retval.toInt32());
            // 在这里可以修改返回值
        }
    });
}
pthread_create函数hook

一般来说这个函数是frida检测的时候就创建一个线程,具体hook如下

function hook_pthread_create() {
    var pthread_create_addr = Module.findExportByName(null, "pthread_create");
    Interceptor.attach(pthread_create_addr, {
        onEnter(args) {
            var parg2 = args[2]
            var parg3 = args[3]
            var so_name = Process.findModuleByAddress(parg2).name;
            const baseAddr = Module.findBaseAddress(so_name)
            console.log("pthread_create", so_name, parg3.toString(16), parg2.sub(baseAddr).toString(16))
        }
    })
}
__pthread_start函数hook
// Frida script to hook pthread_start
function hook__pthread_start() {
    // 查找 pthread_start 函数地址
    var __pthread_start = DebugSymbol.fromName("_ZL15__pthread_startPv").address;
    console.log(__pthread_start)
    __pthread_start && Interceptor.attach(__pthread_start, {
        onEnter: function (args) {
            var addr = args[0].add(12 * 8).readPointer();
            var so_name = Process.findModuleByAddress(addr).name;
            var so_base = Module.getBaseAddress(so_name);
            console.log(so_name, so_base, addr.sub(so_base))
        }
    });
}

hook__pthread_start()
动态加载的一些so进行hook
function waitForModule(moduleName) {
    return new Promise((resolve, reject) => {
        const checkInterval = setInterval(() => {
            const baseAddr = Module.findBaseAddress(moduleName);
            if (baseAddr !== null) {
                clearInterval(checkInterval);
                resolve(baseAddr);
            }
        }, 1000); // 检查频率为每秒一次
    });
}
初始化调用函数hook

对于一些在.init_xxx执行的代码,是在dlopen运行中执行的代码,就必须要找到在最开始使用的系统函数中找到系统调用然后进行hook,类似于这个是在open的某个函数下hook的

var open_addr = Module.findExportByName(null, "open");
    Interceptor.attach(open_addr, {
        onEnter: function (args) {
            const filename = args[0].readCString()
            if (filename.indexOf("maps") !== -1) {
                console.log(filename)
                console.log('RegisterNatives called from:\n' + Thread.backtrace(this.context, Backtracer.FUZZY).map(DebugSymbol.fromAddress).join('\n') + '\n');
                // Thread.sleep(40)
                this.maps_file = true
            }
            if (!is_run) {
                locate_init()
            }
            is_run = true
        },
        onLeave: function (retval){
            if (this.maps_file){
                retval.replace(0)
            }
        }
    })
RegisterNatives的hook

动态注册的函数

function hook_RegisterNatives() {
    var RegisterNatives_addr = null;
    var symbols = Process.findModuleByName("libart.so").enumerateSymbols();
    for (var i = 0; i < symbols.length; i++) {
        var symbol = symbols[i].name;
        if ((symbol.indexOf("CheckJNI") == -1) && (symbol.indexOf("JNI") >= 0)) {
            if (symbol.indexOf("RegisterNatives") >= 0) {
                RegisterNatives_addr = symbols[i].address;
                console.log("RegisterNatives_addr: ", RegisterNatives_addr);
            }
        }
    }
    Interceptor.attach(RegisterNatives_addr, {
        onEnter: function (args) {
            var env = args[0];
            var jclass = args[1];
            var class_name = Java.vm.tryGetEnv().getClassName(jclass);
            var methods_ptr = ptr(args[2]);
            var method_count = args[3].toInt32();
            console.log("RegisterNatives method counts: ", method_count);
            for (var i = 0; i < method_count; i++) {
                var name = methods_ptr.add(i * Process.pointerSize * 3).readPointer().readCString();
                var sig = methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize).readPointer().readCString();
                var fnPtr_ptr = methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2).readPointer();
                var find_module = Process.findModuleByAddress(fnPtr_ptr);
                console.log("类: ", class_name, "方法: ", name, "签名: ", sig, "函数地址: ", fnPtr_ptr, "模块名: ", find_module.name, "函数偏移: ", ptr(fnPtr_ptr).sub(find_module.base));
            }
        },
        onLeave: function (retval) {}
    });
}
hook_RegisterNatives()
memcpy函数hook
 Interceptor.attach(Module.findExportByName("libc.so", "memcpy"), {
    onEnter: function (args) {
        this.dst = args[0];
        this.src = args[1];
        this.size = args[2].toInt32();
    },
    onLeave: function (retval) {
        // ARM64指令是4字节
        if (this.size === 4) {
            // 检查地址是否落在代码段
            if (textRange && (this.src.compare(textRange.base) >= 0 && this.src.compare(textRange.base.add(textRange.size)) < 0)) {
                console.log("可能是ARM64指令的memcpy:");

                console.log("复制地址: " + this.dst);
                console.log("源地址: " + this.src);
                console.log("复制位数大小: " + this.size);
                console.log("字节打印:")
                console.log(hexdump(this.dst, { offset: 0, length: this.size }));

                // 可以根据常见的ARM64指令模式进一步确认,比如分析前几个bit
                var instruction = Memory.readU32(this.dst);
                console.log("Instruction (hex): " + instruction.toString(16));
            }
        }
    }
});
memcmp函数hook
const l = Module.getExportByName("libc.so", "pthread_create")
// 地址对齐,其实默认就是对齐的
const i = l.and(-2)
// 如果这个是成功的
const r = Interceptor.attach(Module.getExportByName("libc.so", "memcmp"), {
    onEnter: function (e) {
        this.match = 0 === e[0].compare(i) || 0 === e[1].compare(i)
    }, onLeave: function (e) {
        // 销毁掉
        this.match && (e.replace(new NativePointer(0)), r.detach())
    }
});
str相关函数

strstr

function hook_strstr() {
    var strstr = Module.findExportByName(null, "strstr");
    console.log("strstr", strstr)
    null !== strstr && Interceptor.attach(strstr, {
        onEnter: function (e) {
            var haystack = e[0].readCString();
            var needle = e[1].readCString();
            // 上面两个都是字符串
        }, onLeave: function (e) {
            // 返回值0就是没有检测到
        }
    })
}

strcmp

function hook_strcmp() {
    Interceptor.attach(Module.findExportByName(null, 'strcmp'), {
        onEnter: function (args) {
            // 获取 strcmp 的参数(两个字符串)
            var str1 = args[0].readUtf8String();
            var str2 = args[1].readUtf8String();
            // 输出调用 strcmp 时传入的两个字符串
            console.log('strcmp called with:');
            console.log('  str1: ' + str1);
            console.log('  str2: ' + str2);
        },
        onLeave: function (retval) {
            // 输出 strcmp 返回值
            console.log('strcmp returned: ' + retval.toInt32());
        }
    });
}
init_array函数hook

hook这个call_constructors,这个call_constructors就是用来执行so初始化函数的,它会去执行so的初始化函数

在dlopen开始所执行的位置。

function hook_init_array() {
    let linker = null;
    if (Process.pointerSize === 4) {
        linker = Process.findModuleByName("linker");
    } else {
        linker = Process.findModuleByName("linker64");
    }
    let call_constructors_addr, get_soname
    let symbols = linker.enumerateSymbols();
    for (let index = 0; index < symbols.length; index++) {
        let symbol = symbols[index];
        if (symbol.name === "__dl__ZN6soinfo17call_constructorsEv") {
            call_constructors_addr = symbol.address;
        } else if (symbol.name === "__dl__ZNK6soinfo10get_sonameEv") {
            get_soname = new NativeFunction(symbol.address, "pointer", ["pointer"]);
        }
    }
    console.log(call_constructors_addr)
    Interceptor.attach(call_constructors_addr, {
        onEnter: function (args) {
            let soinfo = args[0];
            let soname = get_soname(soinfo).readCString();
            // 此时还没有执行完成初始化函数
            // 这个时候就可以加载so了,可以在这个位置nop掉其他的东西
        },
        onLeave: function (retval) {
            if (this.match) {
                console.log("libDexHelper.so", "加载成功")
                const module = Process.findModuleByName("libDexHelper.so");
                // 执行结束初始化函数了,接下来会跑dlopen
            }
        }
    });
}

so层,功能

hook strstr解决frida检测anti问题
function antifrida() {
    var strstr = Module.findExportByName(null, "strstr");
    console.log("strstr", strstr)
    null !== strstr && Interceptor.attach(strstr, {
        onEnter: function (e) {
            this.frida = Boolean(0), this.haystack = e[0], this.needle = e[1],
            null !== this.haystack.readCString() && null !== this.needle.readCString() &&
            (-1 === this.haystack.readCString().indexOf("frida") &&
                -1 === this.needle.readCString().indexOf("frida") &&
                -1 === this.haystack.readCString().indexOf("gum-js-loop") &&
                -1 === this.needle.readCString().indexOf("gum-js-loop") &&
                -1 === this.haystack.readCString().indexOf("gmain") &&
                -1 === this.needle.readCString().indexOf("gmain") &&
                -1 === this.haystack.readCString().indexOf("linjector") &&
                -1 === this.needle.readCString().indexOf("linjector") ||
                (this.frida = Boolean(1)))
        }, onLeave: function (e) {
            if (this.frida) {
                if (this.haystack.readCString().indexOf(this.needle.readCString()) !== -1) {
                    // console.log("[haystack] =>", this.haystack.readCString())
                    // console.log("[needle]   =>", this.needle.readCString())
                }
                e.replace(0);
            }
        }
    })
}

但是对于一些不使用strstr或者使用其他的就没有办法进行检测了

枚举全部so文件
function hook_native(){
    var modules = Process.enumerateModules();
    for (var i in modules){
        var module = modules[i];
        console.log(module.name);
        console.log(module.size);
        console.log(module.base);
        console.log(module.path);
    }
}
打印全部导出的函数
const hooks = Module.load('/data/app/com.example.hookdemo-xMdClW3H4V5El06BIcGN0A==/lib/arm64/libhookdemo.so');
var Imports = hooks.enumerateImports();
for(var i = 0; i < Imports.length; i++) {
    //函数类型
    console.log("type:",Imports[i].type);
    //函数名称
    console.log("name:",Imports[i].name);
    //属于的模块
    console.log("module:",Imports[i].module);
    //函数地址
    console.log("address:",Imports[i].address);
 }
内存操作
// 创建一个字符串
var r = Memory.allocUtf8String("muyang");
console.log(Memory.readUtf8String(r))

// 创建一个r内存
var newaddress = Memory.alloc(10);
// 赋值过去
Memory.copy(newaddress,r,10);
console.log(hexdump(newaddress));
console.log(hexdump(r,{
    offset:0,
    length:10,
    header:true,
    ansi:true
}))

var arr = [ 0x6d,0x75,0x79,0x61,0x6e,0x67];
//申请一个新的内存空间 返回指针 大小是arr.length
const r = Memory.alloc(arr.length);
//将arr数组写入R地址中
Memory.writeByteArray(r,arr);
//输出
console.log(hexdump(r, {
    offset: 0,
    length: arr.length,
    header: true,
    ansi: false
}));

var buffer = Memory.readByteArray(r,arr.length);
console.log(buffer)

具体的内存操作可以让gpt写

pthread_create函数替换
function create_pthread_create() {
    const pthread_create_addr = Module.findExportByName(null, "pthread_create")
    const pthread_create = new NativeFunction(pthread_create_addr, "int", ["pointer", "pointer", "pointer", "pointer"]);
    return new NativeCallback((parg0, parg1, parg2, parg3) => {
        const module = Process.findModuleByAddress(parg2);
        const so_name = module.name;
        const baseAddr = module.base
        //console.log("pthread_create", so_name, "0x" + parg2.sub(baseAddr).toString(16), "0x" + parg3.toString(16))
        if(so_name === TARGET_SO_NAME)
        {
            // 找出來那些thread_create是在TARGET_SO_NAME中建立的
            console.log("pthread_create", so_name, "0x" + parg2.sub(baseAddr).toString(16), "0x" + parg3.toString(16))

            // 一個一個阻止建立看看哪些是檢查frida的
            // pthread_create libpbb_qr_biz_release.so 0x5599b0 0x0
            // pthread_create libpbb_qr_biz_release.so 0x3ac820 0xb40000712cece450
            // pthread_create libpbb_qr_biz_release.so 0x1c831c 0xb40000713cf2cfc0
            // pthread_create libpbb_qr_biz_release.so 0x19bf50 0xb40000712cecca10
            // pthread_create libpbb_qr_biz_release.so 0x3999fc 0xb40000712cef0ff0
            // pthread_create libpbb_qr_biz_release.so 0x3999fc 0xb40000712cf07d10
            
            //return 0;
        }
        // 成功的返回值是0
        return pthread_create(parg0, parg1, parg2, parg3)
    }, "int", ["pointer", "pointer", "pointer", "pointer"])
}

//libpbb_qr_biz_release.so 0x7039c29000 0x5599b0
function replace_thread() {
    var new_pthread_create = create_pthread_create()
    var pthread_create_addr = Module.findExportByName(null, "pthread_create")
    // 函数替换
    Interceptor.replace(pthread_create_addr, new_pthread_create);
}
replace_thread();
打印堆栈
console.log('RegisterNatives called from:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');

console.log('RegisterNatives called from:\n' + Thread.backtrace(this.context, Backtracer.FUZZY).map(DebugSymbol.fromAddress).join('\n') + '\n');

Thread.currentContext();
重新写入指令
Memory.protect(ptr(地址), 4, 'rwx');
// 使用 Arm64Writer 写入 'ret' 指令
var writer = new Arm64Writer(parg2);
writer.putRet();
writer.flush();
writer.dispose();
console.log("ret success");

// 写入nop指令
var writer = new Arm64Writer(parg2);
writer.putNop(); // 写入 nop 指令
writer.flush();  // 刷新写入到内存
console.log("ret success");

frida-tools命令

frida-trace

frida-trace | Frida • A world-class dynamic instrumentation toolkit

hook libc.so下面全部函数

会在handlers目录下生成关于libc.so全部的hook函数

frida-trace -U -f com.lucky.luckyclient -a "libc.so!"

Process详解

获取全部已经加载的so文件

Process.enumerateModules()

获取当前线程的id

Process.getCurrentThreadId();

查找模块

Process.findModuleByName("libc.so")

[MI 6::com.chehejia.oc.m01 ]-> Process.findModuleByName("libc.so")
{
    "base": "0x7b38cc8000",
    "name": "libc.so",
    "path": "/system/lib64/libc.so",
    "size": 1163264
}

获取模块基础地址

const baseAddress = Process.getBaseAddress("libc.so")
baseAddress.add(0x111)
...

16进制地址转换为frida指针

ptr("0x111")

找到堆地址

var heap = Process.getHeap();
var startAddress = heap.start; // 假设我们从堆的起始地址开始
var size = heap.size;
var endAddress = startAddress.add(size);

Memory详解

Frida的Memory对象是用于访问和操作进程内存的核心组件。以下是Memory对象常用的方法和属性的详细解释:

内存读取和写入
  • Memory.readByteArray(address, size): 从指定地址读取字节数组。

    var data = Memory.readByteArray(ptr("0x12345678"), 16);
  • Memory.readUtf8String(address): 从指定地址读取UTF-8字符串。

    var str = Memory.readUtf8String(ptr("0x12345678"));
  • Memory.writeByteArray(address, data): 将字节数组写入指定地址。

    Memory.writeByteArray(ptr("0x12345678"), new Uint8Array([0x01, 0x02, 0x03]));
  • Memory.writeUtf8String(address, string): 将UTF-8字符串写入指定地址。

    Memory.writeUtf8String(ptr("0x12345678"), "Hello, world!");
内存权限
  • Memory.protect(address, size, permissions): 设置指定地址范围的内存权限。常用的权限包括'r'(只读)、'w'(可写)和'x'(可执行)。

    Memory.protect(ptr("0x12345678"), 4096, 'rwx');
内存分配和释放
  • Memory.alloc(size): 分配指定大小的内存并返回起始地址。

    var ptr = Memory.alloc(1024); // 分配1KB
  • Memory.allocUtf8String(string): 分配内存并存储指定的UTF-8字符串。

    var ptr = Memory.allocUtf8String("Hello, Frida!");
内存扫描
  • Memory.scan(address, size, pattern, options): 在指定地址范围内搜索特定模式。 扩展使用,支持使用正则表达式搜索字节模式。

    // 进行内存搜索
    // frida的hex
    const searchValue = "66 72 69 64 61"
    const startAddress = ptr(0x7583c0c000)
    const endAddress = ptr(0x7583d1e000)
    Memory.scan(startAddress, endAddress.sub(startAddress).toInt32(), searchValue, {
        onMatch: function(address, size) {
            console.log("找到数据!地址:" + address);
            // 你可以在这里修改数据,例如:
        },
        onComplete: function() {
            console.log("搜索完成。");
        }
    });
    
    // 特殊匹配手法
    Memory.scan(ptr("0x10000000"), 0x10000, "00 01 02 03 ?? ?? 06 07 08 09 ?? ?? ?? ?? 0e 0f", {
        onMatch: function(address) {
            console.log("找到数据!地址:" + address);
        },
        onComplete: function() {
            console.log("搜索完成。");
        }
    });
内存管理
  • Memory.readPointer(address): 从指定地址读取指针值。

    var ptrValue = Memory.readPointer(ptr("0x12345678"));
  • Memory.writePointer(address, value): 将指针值写入指定地址。

    Memory.writePointer(ptr("0x12345678"), ptr("0x87654321"));
读取和写入特定数据类型
  • Memory.readInt(address): 从指定地址读取32位整数。

    var intValue = Memory.readInt(ptr("0x12345678"));
  • Memory.writeInt(address, value): 将32位整数写入指定地址。

    Memory.writeInt(ptr("0x12345678"), 42);
  • Memory.readFloat(address): 读取32位浮点数。

    var floatValue = Memory.readFloat(ptr("0x12345678"));
  • Memory.writeFloat(address, value): 写入32位浮点数。

    Memory.writeFloat(ptr("0x12345678"), 3.14);
结构体操作
  • Memory.readStruct(address, layout): 读取一个结构体,结构体布局由一个对象定义。

    var layout = {
        field1: 'int',
        field2: 'pointer'
    };
    var struct = Memory.readStruct(ptr("0x12345678"), layout);
  • Memory.writeStruct(address, layout, struct): 将结构体写入指定地址。

    Memory.writeStruct(ptr("0x12345678"), layout, { field1: 100, field2: ptr("0x87654321") });
管理内存分配
  • Memory.free(ptr): 释放之前分配的内存。注意并不是所有的分配都可以手动释放,特别是如果使用了系统的分配方法。
内存跟踪
  • Memory.protect(address, size, permissions): 允许动态更改内存权限,这对于监控和调试非常有用。
patchCode
Memory.patchCode(parg2, 4, function (code) {
    var retBytes = new Uint8Array([0xC0, 0x03, 0x5F, 0xD6]);
    code.writeByteArray(retBytes);
});
Memory.patchCode(address, 4, function (code) {
    // 创建一个字节数组,包含 NOP 指令的机器码
    code.writeByteArray([0x1F, 0x20, 0x03, 0xD5]);  // 将字节数组写入目标内存
});
读取DCB数据
const address = module.base.add(0xE8E60);
// 使用 Memory.readU64 读取 64 位数据
const value = Memory.readU64(address);
// 打印模块和相关符号
const func_module = Process.findModuleByAddress(ptr(value))
console.log(func_module.name)
console.log(func_module.base)
const symbol = DebugSymbol.fromAddress(ptr(value));
console.log(symbol.name)
内存antifrida
function fridaReplace(startAddress, endAddress) {
    const searchValue = "6c 69 62 66 72 69 64 61 2d 61 67 65 6e 74"; // "libfrida-agent" 的十六进制表示
    let matchedAddresses = []; // 用于存储匹配的地址
    Memory.scan(startAddress, endAddress.sub(startAddress).toInt32(), searchValue, {
        onMatch: function (address, size) {
            console.log("找到数据!地址:" + address);
            matchedAddresses.push(address); // 将匹配的地址存储到数组中
        },
        onComplete: function () {
            console.log("搜索完成,共找到 " + matchedAddresses.length + " 个匹配项。");
            // 遍历所有匹配的地址并进行修改
            matchedAddresses.forEach(function (address) {
                try {
                    // 计算所在的页地址,并将权限设置为可写
                    const size = searchValue.split(" ").length
                    Memory.protect(address, size, 'rwx');
                    // 修改 "frida" 为 "wzdnb"
                    const replacement = "6c 69 62 77 7a 64 6e 62 2d 61 67 65 6e 74"; // "libwzdnb-agent" 的十六进制表示
                    const bytes = replacement.split(" ").map(byte => parseInt(byte, 16));
                    Memory.writeByteArray(address, bytes);
                    console.log("已将 'frida' 替换为 'wzdnb',地址:" + address);
                    // 恢复原始内存权限(可根据需要设置合适的权限)
                    Memory.protect(address, size, 'r--');
                } catch (error) {
                    // console.error("修改地址 " + address + " 时出错:" + error);
                }
            });
        }
    });
}

function readFile(fileName) {
    console.log("> Reading file: ", fileName);
    const JString = Java.use("java.lang.String");
    const Files = Java.use("java.nio.file.Files");
    const Paths = Java.use("java.nio.file.Paths");
    const URI = Java.use("java.net.URI");

    const pathName = "file://" + fileName;
    const path = Paths.get(URI.create(pathName));
    const fileBytes = Files.readAllBytes(path);
    return JString.$new(fileBytes);
}

function replaceFrida() {
    const data = readFile("/proc/self/maps");
    // 马上去除全部hook
    Interceptor.detachAll();
    // 解析出来我们想要的数据位置
    let num = 0;
    let startAddress, endAddress;
    data.split("\n").forEach(line => {
        if (line.indexOf("/memfd") !== -1) {
            console.log(line);
            const match = line.match(/^([0-9a-f]+)-([0-9a-f]+)/);
            if (num === 0) {
                startAddress = ptr(parseInt(match[1], 16));
                endAddress = ptr(parseInt(match[2], 16));
            }
            num += 1;
        }
    })
    if (startAddress && endAddress) {
        fridaReplace(startAddress, endAddress);
        console.log("检测成功");
    }
}

replaceFrida();

stalker详解

stalker原理

原理把原本的汇编代码复制过来,在每一行汇编代码下面膨胀代码,使得代码变大了很多,已实现回调函数。

在一开始的时候终止线程运行,然后通过pc知道下一个命令在哪获取到第一个基本块,创建到一个内存区域里面去,把原本的代码修改为跳转过去,执行内存块的代码,执行之后在块的结尾,也就是b或者bl之前,跳转到stalker创建的块里面去,然后在里面拿到跳转的位置再创建一个新的代码块,判断需要跳转到哪个代码块里面去。

使用stalker之后相关的块基本上bl都不是执行的原本的bl了,而是我们stalker修改了之后的bl,跳转到了stalker的相关位置去,而不是原本的直接执行了。

基础使用,打印每一个块的汇编代码,方便知道哪些块被调用了,被调用的顺序。

常规hook代码
Interceptor.attach(JNI_OnLoad, {
    onEnter: function (args) {
        var curTid = Process.getCurrentThreadId();
        Stalker.follow(curTid, {
            transform: function (iterator) {
                let instruction = iterator.next();
                const baseFirstAddress = instruction.address;
                const isModuleCode = baseFirstAddress.compare(startBase) >= 0 &&
                    baseFirstAddress.compare(startBase.add(size)) <= 0;
                const module = Process.findModuleByAddress(baseFirstAddress);
                if (isModuleCode) {
                    if (module) {
                        const name = module.name;
                        const offset = baseFirstAddress.sub(module.base);
                        const base = module.base;
                        console.log(`[transform] start: ${baseFirstAddress} name:${name} offset: ${offset} base: ${base}`);
                    } else {
                        console.log(`[transform] start: ${baseFirstAddress}`);
                    }
                }
                do {
                    const curRealAddr = instruction.address;
                    const curOffset = curRealAddr.sub(baseFirstAddress);
                    const curOffsetInt = curOffset.toInt32()
                    const instructionStr = instruction.toString()
                    if (isModuleCode){
                        console.log("\t" + curRealAddr + " <+" + curOffsetInt + ">: " + instructionStr);
                    }
                    iterator.keep();
                } while ((instruction = iterator.next()) !== null);
                if (isModuleCode){
                    console.log()
                }
            }
        })
    },
    onLeave: function (retval) {
        console.log("结束");
        Stalker.unfollow();
        Stalker.garbageCollect();
    }
}
)

测试是否会卡死的代码

const module = Process.findModuleByName("libDexHelper.so");
Interceptor.attach(module.base.add(0x4b614), {
        onEnter: function (args) {
            var curTid = Process.getCurrentThreadId();
            Stalker.follow(curTid, {
                // 直接创建block块,什么都不做
                transform: function (iterator) {
                    while (true){
                        if (iterator.next() === null){
                            break;
                        }
                        iterator.keep();
                    }
                }
            })
        },
        onLeave: function (retval) {
            console.log("结束");
            Stalker.unfollow();
            Stalker.garbageCollect();
        }
    }
)

如果log有问题的情况下,使用缓存的方式

class LogBuffer {
    constructor() {
        this.queue = [];
        this.lock = false;
    }

    // 添加日志信息
    push(message) {
        this.queue.push(message);
    }

    // 获取并清空日志信息
    flush() {
        const messages = this.queue.slice();
        this.queue = [];
        return messages;
    }

    // 检查队列是否有内容
    hasLogs() {
        return this.queue.length > 0;
    }
}

// 创建一个日志缓冲区实例
const logBuffer = new LogBuffer();

如果出现js变量问题,可以采用如下方式

transform: function (iterator) {
    let curOffset = 0;
    while (true) {
        const instruction = iterator.next();
        if (instruction === null) {
            break;
        }
        const address = instruction.address;
        const isModuleCode = address.compare(base) >= 0 &&
            address.compare(base.add(size)) <= 0;
        if (isModuleCode) {
            const curRealAddr = instruction.address;
            const curOffsetInt = curOffset;
            const instructionStr = instruction.toString();
            if (curOffset) {
                logBuffer.push("\t" + curRealAddr + " <+" + curOffsetInt + ">: " + instructionStr);
            } else {
                const offset = curRealAddr.sub(base);
                const name = "libDexHelper.so";
                logBuffer.push(`[transform] start: ${curRealAddr} name:${name} offset: ${offset} base: ${base}`);
                logBuffer.push("\t" + curRealAddr + " <+" + curOffsetInt + ">: " + instructionStr);
            }
            curOffset += 4;
        }
        iterator.keep();
    }
    if (curOffset) {
        logBuffer.push()
    }
}
常用api

排除这个区域

Stalker.exclude({base,size});

停止对某个基本块的跟踪

Stalker.unfollow();

清理 Stalker 使用的内存和资源

Stalker.garbageCollect();

强制 Stalker立即清空这个缓冲区

Stalker.flush()
回调函数使用

事件回调

const cm = new CModule(`
#include <gum/gumstalker.h>
#include <stdio.h>

void onEvent(const GumEvent *event, GumCpuContext *cpu_context, gpointer user_data) {
  printf("成功");
}
`);

Stalker.follow(curTid, {
    events: {
        call: true,
    },
    onEvent: cm.onEvent,
    transform: function (iterator) {
        while (true) {
            if (iterator.next() === null) {
                break;
            }
            iterator.keep();
        }
    }
})

示例代码

function excludeLibs(libs) {
  new ModuleMap().values().map(m => {
      const { name, base, size } = m;
      if (libs.includes(name)) return;
      console.log('exclude', JSON.stringify(m));
      Stalker.exclude({base,size});
  });
}

function main() {
  excludeLibs(['frida-agent-64.so', 'libc-2.31.so']);
  const onReceive = blob => {
    const events = Stalker.parse(blob, { annotate: true, stringify: true });
    console.log('onReceive', events.length, events);
  };
  const cm = new CModule(`
    #include <gum/gumstalker.h>
    #include <stdio.h>
    void onEvent(const GumEvent *event, GumCpuContext *cpu_context, gpointer user_data) {
      printf("type %d\n", event->type);
    }
  `);
  Stalker.follow(Process.id, {
    events: {
      call: true, // CALL instructions: yes please
      ret: true, // RET instructions
      exec: false, // all instructions
      block: true, // block executed: coarse execution trace
      compile: false // block compiled: useful for coverage
    },
    // onReceive,
    onEvent: cm.onEvent
  });
}
setTimeout(main, 100);
打印汇编指令

一般来说正常arm64都是4个字节为一条指令

 Instruction.parse(ptr(地址))

直接看hex也可以

console.log(hexdump(ptr(地址), {
    length: 4,
    header: true,
    ansi: true
}))

ptr把地址转换为指针

ptr(0x4cb54 + 0x700c113000)

注意Thmub指令需要and(-2)才可以正常使用hexdump还有readArray这些函数,但是Instruction不需要and(-2)

可以判读是否在当前模块

const isModuleCode = baseFirstAddress.compare(base) >= 0 &&
baseFirstAddress.compare(base.add(size)) < 0;
iterator.putCallout回调

iterator.putCallout可以在指令之后设置回调,注意这个和keep是配合的,如图

iterator.putCallout((context) => {
    console.log("\t" + curRealAddr + " <+" + curOffsetInt + ">: " + instructionStr,
        "x8="+ context.x8);
})
修改寄存器的值
context.x8 = 0x12345678;  // 修改 x8 寄存器
context.x0 = 0x9abcdef0;  // 修改 x0 寄存器
context.pc = context.pc.add(4);  // 修改 pc 寄存器,跳过当前指令(示例)
patchCode代码
function nopFunc(parg2) {
    // 修改内存保护,使其可写
    Memory.protect(parg2, 4, 'rwx');
    // 使用 Arm64Writer 写入 'ret' 指令
    var writer = new Arm64Writer(parg2);
    writer.putRet();
    writer.flush();
    writer.dispose();
    console.log("nop " + parg2 + " success");
}

对于函数可以ret

function nopFunc(parg2) {
    // 修改内存保护,使其可写
    Memory.protect(parg2, 4, 'rwx');
    // 使用 Arm64Writer 写入 'nop' 指令
    var writer = new Arm64Writer(parg2);
    writer.putNop();
    writer.flush();
    writer.dispose();
    console.log("nop " + parg2 + " success");
}
CModule详解

简单用法

const cm = new CModule(`
#include <gum/gumstalker.h>
#include <stdio.h>

void test(){
  console.log("成功");
}
`);
// 使用 NativeFunction 来调用该函数指针
const testFunc = new NativeFunction(cm.test, "void", []);
// 调用 test 函数
testFunc()

自吐脚本

加密算法自吐
const config = {
    showStacks: false,
    showDivider: true,
  }
  Java.perform(function () {
    // console.log('frida 已启动');
    function showStacks(name = '') {
      if (config.showStacks) {
        console.log(Java.use('android.util.Log').getStackTraceString(Java.use('java.lang.Throwable').$new(name)))
      }
    }
  
    function showDivider(name = '') {
      if (config.showDivider) {
        console.log(`==============================${name}==============================`)
      }
    }
  
    function showArguments() {
      console.log('arguments: ', ...arguments)
    }
  
    const ByteString = Java.use('com.android.okhttp.okio.ByteString')
    const Encode = {
      toBase64(tag, data) {
        console.log(tag + ' Base64: ', ByteString.of(data).base64())
        // console.log(tag + ' Base64: ', bytesToBase64(data));
      },
      toHex(tag, data) {
        console.log(tag + ' Hex: ', ByteString.of(data).hex())
        // console.log(tag + ' Hex: ', bytesToHex(data));
      },
      toUtf8(tag, data) {
        console.log(tag + ' Utf8: ', ByteString.of(data).utf8())
        // console.log(tag + ' Utf8: ', bytesToString(data));
      },
      toAll(tag, data) {
        Encode.toUtf8(tag, data)
        Encode.toHex(tag, data)
        Encode.toBase64(tag, data)
      },
      toResult(tag, data) {
        Encode.toHex(tag, data)
        Encode.toBase64(tag, data)
      },
    }
  
    const MessageDigest = Java.use('java.security.MessageDigest')
    {
      let overloads_update = MessageDigest.update.overloads
      for (const overload of overloads_update) {
        overload.implementation = function () {
          const algorithm = this.getAlgorithm()
          showDivider(algorithm)
          showStacks(algorithm)
          Encode.toAll(`${algorithm} update data`, arguments[0])
          return this.update(...arguments)
        }
      }
  
      let overloads_digest = MessageDigest.digest.overloads
      for (const overload of overloads_digest) {
        overload.implementation = function () {
          const algorithm = this.getAlgorithm()
          showDivider(algorithm)
          showStacks(algorithm)
          const result = this.digest(...arguments)
          if (arguments.length === 1) {
            Encode.toAll(`${algorithm} update data`, arguments[0])
          } else if (arguments.length === 3) {
            Encode.toAll(`${algorithm} update data`, arguments[0])
          }
  
          Encode.toResult(`${algorithm} digest result`, result)
          return result
        }
      }
    }
  
    const Mac = Java.use('javax.crypto.Mac')
    {
      Mac.init.overload('java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function (key, AlgorithmParameterSpec) {
        return this.init(key, AlgorithmParameterSpec)
      }
      Mac.init.overload('java.security.Key').implementation = function (key) {
        const algorithm = this.getAlgorithm()
        showDivider(algorithm)
        showStacks(algorithm)
        const keyBytes = key.getEncoded()
        Encode.toAll(`${algorithm} init Key`, keyBytes)
        return this.init(...arguments)
      }
  
  
      let overloads_doFinal = Mac.doFinal.overloads
      for (const overload of overloads_doFinal) {
        overload.implementation = function () {
          const algorithm = this.getAlgorithm()
          showDivider(algorithm)
          showStacks(algorithm)
          const result = this.doFinal(...arguments)
          if (arguments.length === 1) {
            Encode.toAll(`${algorithm} update data`, arguments[0])
          } else if (arguments.length === 3) {
            Encode.toAll(`${algorithm} update data`, arguments[0])
          }
  
          Encode.toResult(`${algorithm} doFinal result`, result)
          return result
        }
      }
    }
  
    const Cipher = Java.use('javax.crypto.Cipher')
    {
      let overloads_init = Cipher.init.overloads
      for (const overload of overloads_init) {
        overload.implementation = function () {
          const algorithm = this.getAlgorithm()
          showDivider(algorithm)
          showStacks(algorithm)
  
          if (arguments[0]) {
            const mode = arguments[0]
            console.log(`${algorithm} init mode`, mode)
          }
  
          if (arguments[1]) {
            const className = JSON.stringify(arguments[1])
            // 安卓10以上私钥是有可能输出不了的
            if (className.includes('OpenSSLRSAPrivateKey')) {
              // const keyBytes = arguments[1];
              // console.log(`${algorithm} init key`, keyBytes);
            } else {
              const keyBytes = arguments[1].getEncoded()
              Encode.toAll(`${algorithm} init key`, keyBytes)
            }
          }
  
          if (arguments[2]) {
            const className = JSON.stringify(arguments[2])
            if (className.includes('javax.crypto.spec.IvParameterSpec')) {
              const iv = Java.cast(arguments[2], Java.use('javax.crypto.spec.IvParameterSpec'))
              const ivBytes = iv.getIV()
              Encode.toAll(`${algorithm} init iv`, ivBytes)
            } else if (className.includes('java.security.SecureRandom')) {
            }
          }
  
          return this.init(...arguments)
        }
      }
  
      let overloads_doFinal = Cipher.doFinal.overloads
      for (const overload of overloads_doFinal) {
        overload.implementation = function () {
          const algorithm = this.getAlgorithm()
          showDivider(algorithm)
          showStacks(algorithm)
          const result = this.doFinal(...arguments)
          if (arguments.length === 1) {
            Encode.toAll(`${algorithm} update data`, arguments[0])
          } else if (arguments.length === 3) {
            Encode.toAll(`${algorithm} update data`, arguments[0])
          }
  
          Encode.toResult(`${algorithm} doFinal result`, result)
          return result
        }
      }
    }
  
    const Signature = Java.use('java.security.Signature')
    {
      let overloads_update = Signature.update.overloads
      for (const overload of overloads_update) {
        overload.implementation = function () {
          const algorithm = this.getAlgorithm()
          showDivider(algorithm)
          showStacks(algorithm)
          Encode.toAll(`${algorithm} update data`, arguments[0])
          return this.update(...arguments)
        }
      }
  
      let overloads_sign = Signature.sign.overloads
      for (const overload of overloads_sign) {
        overload.implementation = function () {
          const algorithm = this.getAlgorithm()
          showDivider(algorithm)
          showStacks(algorithm)
          const result = this.sign()
          Encode.toResult(`${algorithm} sign result`, result)
          return this.sign(...arguments)
        }
      }
    }
  })
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment