版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/

Frida 反汇编

Frida 提供了反汇编 API,主要用于对目标进程的代码进行动态分析,例如:

  • 获取函数的机器代码并将其反汇编为汇编指令。

  • 分析内存中的指令流。

这些功能在 Frida 的 Instruction 类中实现,基于 Capstone 实现的反汇编功能。

https://frida.re/docs/javascript-api/#instruction

word/media/image1.png

比如,可以通过 Instruction.parse 方法反汇编指定地址的汇编信息

[Remote::AndroidExample]-> Instruction.parse(ptr("0x7e7acdb9a0"));
{
    "address": "0x7e7acdb9a0",
    "groups": [],
    "mnemonic": "sub",
    "next": "0x7e7acdb9a4",
    "opStr": "sp, sp, #0x10",
    "operands": [
        {
            "type": "reg",
            "value": "sp"
        },
        {
            "type": "reg",
            "value": "sp"
        },
        {
            "type": "imm",
            "value": "16"
        }
    ],
    "regsRead": [],
    "regsWritten": [],
    "size": 4
}

Android 示例代码

定义一个 native 函数 add,计算 1+ 1 + 1 并返回结果。

把 add 函数动态注册到 com.cyrus.example.frida.disassemble.FridaDisassembleActivity#add。

#include <jni.h>
#include <android/log.h>

#define LOG_TAG "FridaDisassemble"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

// 实现 add 方法:计算 1 + 1 + 1
jint native_add(JNIEnv *env, jobject obj) {
    return 1 + 1 + 1;
}

// 定义 JNI 方法映射
static const JNINativeMethod methods[] = {
        {"add", "()I", (void *)native_add} // 方法名, 方法签名, C++函数指针
};

// 动态注册 JNI 方法
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;
    if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }

    // 获取 Java 类
    jclass clazz = env->FindClass("com/cyrus/example/frida/disassemble/FridaDisassembleActivity");
    if (clazz == nullptr) {
        LOGD("[-] Failed to find class FridaDisassembleActivity");
        return JNI_ERR;
    }

    // 注册 native 方法
    if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
        LOGD("[-] Failed to register native methods");
        return JNI_ERR;
    }

    LOGD("[+] Successfully registered native methods");
    return JNI_VERSION_1_6;
}

在 kotlin 层调用 add 函数并 toast 结果。

// 调用 native 方法
val result = add()
// 显示 Toast
Toast.makeText(this, "add() returned: $result", Toast.LENGTH_LONG).show()

当点击按钮可以看到返回结果 3。

word/media/image2.png

接下来通过 Frida 实现:

  1. 找到 add 函数动态注册的地址

  2. 反汇编 add 函数机器码

  3. patch add 函数汇编指令修改返回结果

JNI 方法地址跟踪

1. 启动 Frida Server

# 启用超级管理员
adb root

# 进入命令行
adb shell 

# 启动frida-server,并指定端口
/data/local/tmp/fs -l 0.0.0.0:1234

# 端口转发到本地
adb forward tcp:1234 tcp:1234

Frida 的详细使用可以介绍参考这篇文章:使用 Frida Hook Android App

2. hook RegisterNatives 方法

创建 RegisterNatives.js hook RegisterNatives 函数并打印每个动态注册的 jni 方法信息。

// 查找 libart.so 中的 RegisterNatives 地址
function findRegisterNatives() {
    let symbols = Module.enumerateSymbolsSync("libart.so");
    let addrRegisterNatives = null;

    // 遍历所有符号,查找非 CheckJNI 版本的 RegisterNatives
    for (let i = 0; i < symbols.length; i++) {
        let symbol = symbols[i];

        // 确认符合 RegisterNatives 标准的符号(非 CheckJNI 版本)
        if (symbol.name.indexOf("art") >= 0 &&
            symbol.name.indexOf("JNI") >= 0 &&
            symbol.name.indexOf("RegisterNatives") >= 0 &&
            symbol.name.indexOf("CheckJNI") < 0) {
            addrRegisterNatives = symbol.address;
            console.log("[+] Found RegisterNatives symbol: " + symbol.name + " at " + symbol.address);
            break;  // 找到第一个匹配的符号即可
        }
    }

    // 如果没有找到 RegisterNatives 地址,返回 null
    if (addrRegisterNatives === null) {
        console.log("[!] No non-CheckJNI RegisterNatives symbol found!");
    }
    return addrRegisterNatives;
}

// Hook RegisterNatives 函数,打印方法相关信息
function hookRegisterNatives(addrRegisterNatives) {
    if (addrRegisterNatives !== null) {
        // 使用 Interceptor 附加到 RegisterNatives 地址
        Interceptor.attach(addrRegisterNatives, {
            onEnter: function (args) {

                // 获取 java 类和方法列表
                let javaClass = args[1];
                let className = Java.vm.tryGetEnv().getClassName(javaClass); // 获取类名

                // 打印 RegisterNatives 的方法参数
                let methodsPtr = ptr(args[2]);

                let methodCount = args[3].toInt32();
                console.log("[RegisterNatives] method_count:", methodCount);

                // 遍历注册的每个 JNI 方法
                for (let i = 0; i < methodCount; i++) {
                    let methodPtr = methodsPtr.add(i * Process.pointerSize * 3); // 获取每个方法的指针

                    // 读取每个方法的名称、签名和函数指针
                    let namePtr = Memory.readPointer(methodPtr);
                    let sigPtr = Memory.readPointer(methodPtr.add(Process.pointerSize));
                    let fnPtr = Memory.readPointer(methodPtr.add(Process.pointerSize * 2));

                    let name = Memory.readCString(namePtr); // 方法名称
                    let sig = Memory.readCString(sigPtr);   // 方法签名
                    let symbol = DebugSymbol.fromAddress(fnPtr); // 函数符号信息

                    // 打印每个 JNI 方法的详细信息
                    console.log("[RegisterNatives] Class:", className);
                    console.log("  Method: " + name);
                    console.log("  Signature: " + sig);
                    console.log("  Function Pointer: " + fnPtr);
                    console.log("  Function Offset: " + symbol);
                    console.log("  Return Address: " + this.returnAddress);
                }
            }
        });
    } else {
        console.log("[!] Cannot hook RegisterNatives because the address is null.");
    }
}

// 执行查找并 hook RegisterNatives
setImmediate(function() {
    let addrRegisterNatives = findRegisterNatives();
    hookRegisterNatives(addrRegisterNatives);
});

3. 附加到当前设备前台应用

frida -H 127.0.0.1:1234 -l RegisterNatives.js -F

输出如下:

     ____
    / _  |   Frida 14.2.18 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://www.frida.re/docs/home/
[+] Found RegisterNatives symbol: _ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi at 0x7eecdf6d10
[Remote::AndroidExample]-> [RegisterNatives] method_count: 1
[RegisterNatives] Class: com.cyrus.example.frida.disassemble.FridaDisassembleActivity
  Method: add
  Signature: ()I
  Function Pointer: 0x7eddb289a0
  Function Offset: 0x7eddb289a0
  Return Address: 0x7eecd7da74

找到目标方法地址为:0x7eddb289a0

反汇编 JNI 方法

反汇编并打印目标地址开始 10 条汇编指令。

// 定义函数:打印指定地址开始的指定数量的汇编指令
function printAssemblyInstructions(methodAddress, numInstructions) {
    for (var i = 0; i < numInstructions; i++) {
        // 解析当前地址的指令
        var instruction = Instruction.parse(methodAddress);

        // 打印当前指令的地址和汇编指令
        console.log(instruction.address + ": " + instruction.toString());

        // 获取下一条指令的地址,当前地址 + 指令的大小
        methodAddress = methodAddress.add(instruction.size);
    }
}

// 调用函数,传入方法地址和指令数量数量
printAssemblyInstructions(ptr("0x7eddb289a0"), 10);

执行脚本

frida -H 127.0.0.1:1234 -l instructions.js -F

输出如下:

0x7eddb289a0: sub sp, sp, #0x10
0x7eddb289a4: str x0, [sp, #8]
0x7eddb289a8: str x1, [sp]
0x7eddb289ac: mov w0, #3
0x7eddb289b0: add sp, sp, #0x10
0x7eddb289b4: ret
0x7eddb289b8: sub sp, sp, #0x50
0x7eddb289bc: stp x29, x30, [sp, #0x40]
0x7eddb289c0: add x29, sp, #0x40
0x7eddb289c4: mrs x8, tpidr_el0

Patch JNI 方法

把目标地址 0x7eddb289ac 的汇编指令

mov w0, #3

修改为:

mov w0, #2

汇编指令转换机器码:https://armconverter.com/?code=mov+w0,+%232

word/media/image3.png

  • 原始机器码:0x40008052。

  • 小端字节序:反转字节顺序后的机器码是 0x52800040。

编码实现如下:

// 目标地址
var targetAddress = ptr("0x7eddb289ac");

// 获取目标地址所在内存区域的可访问权限,并修改为可读写
Memory.protect(targetAddress, 8, 'rwx');  // 将目标地址所在的内存页面设置为可读写执行

// 构造新的汇编指令:mov w0, #2
var newCode = [0x52800040];  // ARM64 `mov w0, #2` 的机器码

// 将新的机器码写入内存
Memory.writeByteArray(targetAddress, newCode);

patch 完成后,再点击按钮可以看到返回结果变成了 2 。

word/media/image4.png

完整代码

创建 frida 脚本 patch_add_function.js,把上面所有流程连贯实现, 源码如下:

// 查找 libart.so 中的 RegisterNatives 地址
function findRegisterNatives() {
    let symbols = Module.enumerateSymbolsSync("libart.so");
    let addrRegisterNatives = null;

    // 遍历所有符号,查找非 CheckJNI 版本的 RegisterNatives
    for (let i = 0; i < symbols.length; i++) {
        let symbol = symbols[i];

        // 确认符合 RegisterNatives 标准的符号(非 CheckJNI 版本)
        if (symbol.name.indexOf("art") >= 0 &&
            symbol.name.indexOf("JNI") >= 0 &&
            symbol.name.indexOf("RegisterNatives") >= 0 &&
            symbol.name.indexOf("CheckJNI") < 0) {
            addrRegisterNatives = symbol.address;
            console.log("[+] Found RegisterNatives symbol: " + symbol.name + " at " + symbol.address);
            break;  // 找到第一个匹配的符号即可
        }
    }

    // 如果没有找到 RegisterNatives 地址,返回 null
    if (addrRegisterNatives === null) {
        console.log("[!] No non-CheckJNI RegisterNatives symbol found!");
    }
    return addrRegisterNatives;
}

// Hook RegisterNatives 函数,打印方法相关信息并执行自定义操作
function hookRegisterNatives(addrRegisterNatives, className, methodName, methodSig, customAction) {
    if (addrRegisterNatives !== null) {
        // 使用 Interceptor 附加到 RegisterNatives 地址
        Interceptor.attach(addrRegisterNatives, {
            onEnter: function (args) {
                // 获取方法数量
                let methodCount = args[3].toInt32();
                console.log("[RegisterNatives] method_count:", methodCount);

                // 获取 Java 类的类名
                let javaClass = args[1];
                let currentClassName = Java.vm.tryGetEnv().getClassName(javaClass); // 获取类名

                // 打印每个 JNI 方法的详细信息
                let methodsPtr = ptr(args[2]);

                for (let i = 0; i < methodCount; i++) {
                    let methodPtr = methodsPtr.add(i * Process.pointerSize * 3); // 获取每个方法的指针

                    // 读取每个方法的名称、签名和函数指针
                    let namePtr = Memory.readPointer(methodPtr);
                    let sigPtr = Memory.readPointer(methodPtr.add(Process.pointerSize));
                    let fnPtr = Memory.readPointer(methodPtr.add(Process.pointerSize * 2));

                    let name = Memory.readCString(namePtr); // 方法名称
                    let sig = Memory.readCString(sigPtr);   // 方法签名
                    let symbol = DebugSymbol.fromAddress(fnPtr); // 函数符号信息

                    // 打印每个 JNI 方法的详细信息
                    console.log("[RegisterNatives] Class: " + currentClassName);
                    console.log("  Method: " + name);
                    console.log("  Signature: " + sig);
                    console.log("  Function Pointer: " + fnPtr);
                    console.log("  Function Offset: " + symbol);
                    console.log("  Return Address: " + this.returnAddress);

                    // 如果指定了 className 且不匹配,则跳过
                    if (className && currentClassName !== className) {
                        continue;
                    }

                    // 如果指定了 methodName 且不匹配,则跳过
                    if (methodName && name !== methodName) {
                        continue;
                    }

                    // 如果指定了 methodSig 且不匹配,则跳过
                    if (methodSig && sig !== methodSig) {
                        continue;
                    }

                    // 执行自定义操作(传递函数的地址作为参数)
                    if (customAction) {
                        customAction(fnPtr);
                    }
                }
            }
        });
    } else {
        console.log("[!] Cannot hook RegisterNatives because the address is null.");
    }
}

// 定义函数:打印指定地址开始的指定数量的汇编指令
function printAssemblyInstructions(methodAddress, numInstructions) {
    for (var i = 0; i < numInstructions; i++) {
        // 解析当前地址的指令
        var instruction = Instruction.parse(methodAddress);

        // 打印当前指令的地址、助记符和操作数
        console.log(instruction.address + ": " + instruction.toString());

        // 获取下一条指令的地址,4 是指令的大小,ARM64 指令通常是 4 字节
        methodAddress = methodAddress.add(instruction.size);
    }
}


/**
 * 修改指定地址的内存内容为给定的机器码
 * @param {Pointer} targetAddress - 目标函数的内存地址
 * @param {number} size - 目标函数的大小(字节数)
 * @param {Uint8Array} newCode - 要写入的机器码(字节数组)
 */
function patchFunction(targetAddress, size, newCode) {
    try {
        // 获取目标地址所在内存区域的可访问权限,并修改为可读写执行
        Memory.protect(targetAddress, size, 'rwx');  // 设置为可读、可写、可执行

        // 将新的机器码写入内存
        Memory.writeByteArray(targetAddress, newCode);

        console.log("[+] Patched function at address: " + targetAddress);
    } catch (e) {
        console.log("[!] Error patching function at address: " + targetAddress + " - " + e.message);
    }
}


// 执行查找并 hook RegisterNatives
setImmediate(function () {
    // 查找 RegisterNatives 地址
    let addrRegisterNatives = findRegisterNatives();

    // 自定义的操作函数:接收 JNI 方法的地址
    function customAction(jniMethodAddress) {
        // 自定义操作:可以在这里通过 jniMethodAddress 进行特定操作
        console.log("[Custom Action] Executing custom action for JNI method at address: " + jniMethodAddress);

        // 打印汇编指令,传入方法地址和指令数量数量
        printAssemblyInstructions(jniMethodAddress, 10);

        // ARM64 `mov w0, #2` 的机器码
        var newCode = [0x52800040];

         // 计算目标地址:jniMethodAddress 开始第4条指令的地址
        var targetAddress = jniMethodAddress.add(3 * 4); // 每条指令为 4 字节,3 * 4 即跳过前三条指令

        // patch
        patchFunction(targetAddress, 8, newCode);
    }

    // 调用 hookRegisterNatives,传入类名、方法名、方法签名及自定义操作
    hookRegisterNatives(addrRegisterNatives, "com.cyrus.example.frida.disassemble.FridaDisassembleActivity", "add", "()I", customAction);
});

执行脚本

frida -H 127.0.0.1:1234 -l patch_add.js -F

输出如下:

[+] Found RegisterNatives symbol: _ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi at 0x7eecdf6d10
[Remote::AndroidExample]-> [RegisterNatives] method_count: 1
[RegisterNatives] Class: com.cyrus.example.frida.disassemble.FridaDisassembleActivity
  Method: add
  Signature: ()I
  Function Pointer: 0x7edd6169a0
  Function Offset: 0x7edd6169a0
  Return Address: 0x7eecd7da74
[Custom Action] Executing custom action for JNI method at address: 0x7edd6169a0
0x7edd6169a0: sub sp, sp, #0x10
0x7edd6169a4: str x0, [sp, #8]
0x7edd6169a8: str x1, [sp]
0x7edd6169ac: mov w0, #3
0x7edd6169b0: add sp, sp, #0x10
0x7edd6169b4: ret
0x7edd6169b8: sub sp, sp, #0x50
0x7edd6169bc: stp x29, x30, [sp, #0x40]
0x7edd6169c0: add x29, sp, #0x40
0x7edd6169c4: mrs x8, tpidr_el0
[+] Patched function at address: 0x7edd6169ac

调用 printAssemblyInstructions 方法重新反汇编 add 函数地址可以看到汇编指令 mov w0, #3 已经成功被替换为 mov w0, #2

[Remote::AndroidExample]-> printAssemblyInstructions(ptr("0x7edd6169a0"), 10);
0x7edd6169a0: sub sp, sp, #0x10
0x7edd6169a4: str x0, [sp, #8]
0x7edd6169a8: str x1, [sp]
0x7edd6169ac: mov w0, #2
0x7edd6169b0: add sp, sp, #0x10
0x7edd6169b4: ret
0x7edd6169b8: sub sp, sp, #0x50
0x7edd6169bc: stp x29, x30, [sp, #0x40]
0x7edd6169c0: add x29, sp, #0x40
0x7edd6169c4: mrs x8, tpidr_el0

Android 示例程序完整源码:https://github.com/CYRUS-STUDIO/AndroidExample