mcpField
R
rev-frida

rev-frida

github.com/p4nda0s安全2.3k2.2k 次安装25 周前更新
代码仓库

关于 rev-frida

面向动态分析人员的 Frida 脚本生成指南,用于运行时插桩、Hook 和检查。内容涵盖现代 Frida API,并强调按模块加载时机安装 Hook,以及谨慎处理初始化阶段。

主要功能

  1. 01生成原生导出函数 Hook 脚本
  2. 02覆盖 Java 与 ObjC 方法 Hook
  3. 03捕获参数、返回值和运行轨迹
  4. 04读取与写入内存并转储数据
  5. 05根据模块加载事件安装 Hook
  6. 06提供初始化阶段 Hook 风险指引

适用场景

  1. 01检查应用运行时的函数调用与数据
  2. 02在目标原生模块加载后安装 Hook
  3. 03分析 Java 或 ObjC 方法行为
  4. 04排查构造函数阶段的反调试行为

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/p4nda0s/reverse-skills --skill rev-frida

安装后在 Claude Code 中输入/rev-frida确认已加载。

详细介绍

rev-frida - Frida 脚本生成器

生成用于动态分析、Hook 和运行时检查的 Frida 插桩脚本。

概览

使用 Frida 实现:

  • 原生导出函数 Hook
  • Java 或 ObjC 方法 Hook
  • 运行时跟踪
  • 参数或返回值捕获
  • 内存转储
  • 感知加载器的原生插桩

重要:现代 Frida CLI

现代 Frida CLI 不使用 --no-pause。进程启动后会在脚本加载完毕时恢复运行。

# Spawn and hook
frida -U -f com.example.app -l hook.js

# Attach to running process
frida -U com.example.app -l hook.js

# Attach by PID
frida -U -p 1234 -l hook.js

现代 API 参考

模块和符号查找
const mod = Process.getModuleByName("libssl.so");

mod.name;
mod.base;
mod.size;
mod.path;

const ptr = mod.getExportByName("SSL_read");

Process.enumerateModules();
mod.enumerateExports();
mod.enumerateImports();

const addr = Module.getExportByName(null, "open");
Interceptor
Interceptor.attach(ptr, {
    onEnter(args) {
        console.log("arg0:", args[0].toInt32());
        console.log("arg1 str:", args[1].readUtf8String());
    },
    onLeave(retval) {
        console.log("ret:", retval.toInt32());
    }
});

Interceptor.replace(ptr, new NativeCallback(function (a0, a1) {
    console.log("replaced");
    return 0;
}, "int", ["pointer", "int"]));
NativeFunction 和 NativeCallback
const open = new NativeFunction(
    Module.getExportByName(null, "open"),
    "int",
    ["pointer", "int"]
);

const fd = open(Memory.allocUtf8String("/etc/hosts"), 0);

const cb = new NativeCallback(function (arg) {
    console.log("called with:", arg);
    return 0;
}, "int", ["int"]);
内存操作
ptr(addr).readByteArray(size);
ptr(addr).readUtf8String();
ptr(addr).readU32();
ptr(addr).readPointer();

ptr(addr).writeByteArray(bytes);
ptr(addr).writeUtf8String("hello");
ptr(addr).writeU32(0x41414141);

const buf = Memory.alloc(256);
const str = Memory.allocUtf8String("hello");

Memory.scan(mod.base, mod.size, "48 89 5C 24 ?? 48 89 6C", {
    onMatch(address, size) {
        console.log("found at:", address);
    },
    onComplete() {}
});
ObjC
if (ObjC.available) {
    const hook = ObjC.classes.ClassName["- methodName:"];
    Interceptor.attach(hook.implementation, {
        onEnter(args) {
            const selfObj = new ObjC.Object(args[0]);
            const param = new ObjC.Object(args[2]);
            console.log(selfObj.toString());
            console.log(param.toString());
        }
    });
}
Java
if (Java.available) {
    Java.perform(function () {
        const Activity = Java.use("android.app.Activity");
        Activity.onCreate.implementation = function (bundle) {
            console.log("onCreate called");
            return this.onCreate(bundle);
        };
    });
}

脚本生成指南

生成 Frida 脚本时:

  1. 始终使用现代 API,例如 Process.getModuleByName() 和 mod.getExportByName()。
  2. 不要使用 --no-pause。
  3. 优先采用由加载事件驱动的原生 Hook,而不是轮询。
  4. 以易读的形式打印指针和缓冲区。
  5. 对有风险的 Hook 使用 try/catch 包装。
  6. 使用 hexdump() 检查二进制数据。
处理原生模块加载时序

不要假设目标 .so 已经加载。

首选顺序:

  1. Hook android_dlopen_ext 或 dlopen,并在目标库加载时安装 Hook。
  2. 立即使用 Process.findModuleByName() 检查模块是否已加载。
  3. 仅在后备情况下使用轮询。

默认使用此辅助函数:

function hookModuleLoad(moduleName, callback) {
    const dlopen = Module.findGlobalExportByName("android_dlopen_ext")
        || Module.findGlobalExportByName("dlopen");

    if (!dlopen) {
        throw new Error("dlopen/android_dlopen_ext not found");
    }

    const hooked = new Set();

    Interceptor.attach(dlopen, {
        onEnter(args) {
            this.path = args[0].isNull() ? null : args[0].readCString();
            this.shouldHook = this.path && this.path.indexOf(moduleName) !== -1;
        },
        onLeave(retval) {
            if (!this.shouldHook || retval.isNull()) {
                return;
            }

            const mod = Process.findModuleByName(moduleName);
            if (!mod) {
                return;
            }

            const key = mod.base.toString();
            if (hooked.has(key)) {
                return;
            }
            hooked.add(key);

            callback(mod);
        }
    });
}

像这样使用:

hookModuleLoad("libtarget.so", function (mod) {
    const target = mod.getExportByName("target_export");
    Interceptor.attach(target, {
        onEnter(args) {
            console.log("target_export called");
        }
    });
});

如果目标可能已经加载,请将立即检查与加载 Hook 结合使用:

function hookNowOrOnLoad(moduleName, callback) {
    const mod = Process.findModuleByName(moduleName);
    if (mod) {
        callback(mod);
        return;
    }
    hookModuleLoad(moduleName, callback);
}

仅在后备情况下使用轮询:

function hookWhenReady(moduleName, exportName, callbacks) {
    const mod = Process.findModuleByName(moduleName);
    if (mod) {
        Interceptor.attach(mod.getExportByName(exportName), callbacks);
        return;
    }

    const timer = setInterval(function () {
        const loaded = Process.findModuleByName(moduleName);
        if (!loaded) {
            return;
        }
        clearInterval(timer);
        Interceptor.attach(loaded.getExportByName(exportName), callbacks);
    }, 100);
}

说明:

  • 在 Android 上,优先使用 android_dlopen_ext,然后再考虑 dlopen。
  • 根据模块基址去重,而不只是根据路径。
  • 通常在 dlopen/android_dlopen_ext 的 onLeave 中安装 Hook 最合适,此时构造函数已运行完毕。
  • 如果原生加载由 Java 驱动,还应考虑 System.loadLibrary、Runtime.loadLibrary0、dlsym 或 RegisterNatives。
不要盲目 Hook init 系列函数

不要建议用户盲目 Hook .init、.init_array、构造函数或 JNI_OnLoad。

这些位置很脆弱:

  • 许多库会在这些位置执行一次性设置
  • 不当的 Hook 可能会在真正的目标逻辑运行前导致进程崩溃
  • 过早的 Hook 可能改变时序,并掩盖用户想要研究的行为
  • 构造函数代码通常会调用许多不相关的辅助函数

建议在初始化阶段 Hook 之前:

  1. 确定为何必须过早 Hook
  2. 确定具体模块和确切的初始化例程
  3. 确认目标符号或行为是否仅在常规导出函数可用之前存在
  4. 如果较晚的稳定 Hook 能提供同样的可见性,则优先使用它

优先顺序:

  1. 在模块加载后 Hook 稳定的导出函数
  2. Hook RegisterNatives、dlsym 或第一个实际业务函数
  3. 仅当原生注册或反调试设置发生在 JNI_OnLoad 中时,才 Hook JNI_OnLoad
  4. 仅在有充分证据表明关键逻辑位于构造函数或 .init_array 中时,才 Hook 它们

如果建议使用早期初始化 Hook,请说明:

  • 常规导出函数 Hook 为何不够
  • 应 Hook 的确切函数或地址
  • 预期会出现何种故障
  • 如何验证该 Hook 没有破坏初始化

错误建议:

// do not suggest this without a reason
Interceptor.attach(Module.findBaseAddress("libtarget.so").add(0x1234), ...);

更好的建议:

  • 等待模块加载
  • 通过符号、交叉引用、字符串或跟踪证据确认构造函数目标
  • 识别出确切的初始化函数后再附加 Hook
  • 使用初始化阶段 Hook 之前,向用户说明风险
优先使用构造函数分发器,而不是盲目使用 init 钩子

如果怀疑构造函数调用链中存在反调试逻辑,优先观察或钩取分发器,而不是直接盲目附加到原始 .init_array 条目。

可考虑的较高层级目标包括:

  • call_constructors
  • call_array
  • 链接器侧的构造函数遍历器
  • 遍历构造函数表的应用侧包装函数

这样更安全的原因:

  • 一个钩子就能揭示完整的构造函数执行顺序
  • 可以看到哪个构造函数在进程终止或反调试设置前立即运行
  • 可减少对无关初始化代码的盲目修补
  • 可以先记录构造函数目标,再只修补有问题的目标

推荐的工作流:

  1. 钩取模块加载
  2. 确认进程是否在执行构造函数期间终止
  3. 如果是,在可用时钩取 call_constructors 或 call_array
  4. 记录每个被分发的构造函数目标
  5. 找出执行反调试检查的确切构造函数
  6. 修补或钩取该构造函数,或者修补其中具体的反调试分支

仅在以下情况下建议使用 call_constructors 或 call_array:

  • 目标平台或链接器构建版本提供这些函数
  • 符号、跟踪记录或反汇编表明实际使用了这些函数
  • 用户需要查看构造函数执行期间的反调试行为

请提醒用户注意以下限制:

  • 这些函数属于加载器或链接器内部函数,其名称可能因 Android 版本、厂商构建版本或平台而异
  • 它们可能未导出,可能需要恢复符号或通过偏移量附加
  • 过早钩取它们可能影响每次库加载,因此应谨慎限定日志范围
  • 应先用它们进行发现,而不是将其作为默认的永久绕过方法

好的建议:

  • “进程在原生库初始化期间退出。首先,在存在 call_constructors 或 call_array 时钩取相应的构造函数分发器,记录构造函数目标,然后转向确切的有问题的构造函数。”

不好的建议:

  • “钩取每个 .init_array 条目并逐个修补,直到不再崩溃。”
指针和缓冲区日志记录
console.log(args[0]);
console.log(args[0].toString());
console.log(hexdump(args[0], {
    offset: 0,
    length: 64,
    header: true,
    ansi: false
}));

评论

支持 Markdown