百趣云 百趣云的博客

Frida 脚本工程化:从一次性脚本到可复用武器库

一次性脚本的痛点

逆向工程师的电脑里都有一堆 test1.jshook_final.jshook_final_真的最终版.js——一次性脚本越攒越多,换个 App 又要重写 80%。Frida 脚本需要工程化:模块化、可配置、可复用

模块化:按功能拆分

把常用能力拆成独立模块,用 require 组合:

// lib/utils.js
function bytesToHex(bytes) {
    return Array.from(new Uint8Array(bytes))
        .map(b => b.toString(16).padStart(2, '0')).join('');
}
function bytesToStr(bytes) {
    return String.fromCharCode.apply(null, new Uint8Array(bytes));
}
module.exports = { bytesToHex, bytesToStr };

// lib/okhttp.js  —— OkHttp 抓包模块
var { bytesToStr } = require('./utils.js');
function hookOkHttp(onRequest) {
    Java.perform(function() {
        var Chain = Java.use('okhttp3.internal.http.RealInterceptorChain');
        Chain.proceed.overload('okhttp3.Request').implementation = function(req) {
            onRequest(req.url().toString(), req.method());
            return this.proceed(req);
        };
    });
}
module.exports = { hookOkHttp };

// main.js —— 具体项目的组装
var { hookOkHttp } = require('./lib/okhttp.js');
hookOkHttp(function(url, method) {
    if (url.includes('api.target.com')) {
        console.log(method + ' ' + url);
    }
});

Frida 17+ 原生支持 require(frida-compile 打包),老版本用 frida-compile 工具链。

可配置:目标信息外置

把 App 相关的信息抽成配置,脚本主体不变:

// config.js
module.exports = {
    packageName: 'com.target.app',
    signClass: 'com.target.app.SignUtils',
    signMethod: 'getSign',
    apiFilter: 'api.target.com',
    logFile: '/sdcard/target_hook.log',
};

// main.js
var config = require('./config.js');
// 所有硬编码换成 config.xxx

换目标 App 时只改 config.js——脚本主体零改动。

RPC 服务化:签名能力的标准封装

逆向项目的终极产物大多是"签名服务"。标准封装模式:

// sign_server.js
Java.perform(function() {
    var SignUtils = Java.use(config.signClass);
    rpc.exports = {
        sign: function(params) {
            return SignUtils[config.signMethod](params);
        },
        // 健康检查接口,监控必备
        ping: function() { return 'pong'; },
        // 版本信息,排查 App 更新导致的问题
        appVersion: function() {
            var ctx = Java.use('android.app.ActivityThread')
                .currentApplication().getApplicationContext();
            var pm = ctx.getPackageManager();
            return pm.getPackageInfo(ctx.getPackageName(), 0).versionName.getInt();
        }
    };
});

Python 侧的标准客户端:

import frida

class SignService:
    def __init__(self, device_id=None):
        self.device = frida.get_device(device_id) if device_id else frida.get_usb_device()
        self.session = self.device.attach(config['packageName'])
        self.script = self.session.create_script(open('sign_server.js').read())
        self.script.load()

    def sign(self, params):
        return self.script.exports.sign(params)

    def health(self):
        try:
            return self.script.exports.ping() == 'pong'
        except:
            return False

精髓:三个工程实践

  1. 日志分级与落盘。hook 脚本里 console.log 在 App 高频调用时会刷屏卡死。分级:ERROR 必打、INFO 关键节点、DEBUG 调试期。生产环境日志写文件(frida 的 File API 或 send 到 Python 侧落盘),别刷终端
  2. 异常隔离。每个 hook 点的回调里包 try-catch——一个 hook 点抛异常不能搞挂整个脚本(Frida 的默认行为是异常后该 hook 失效,静默失效最难排查):
try {
    // hook 逻辑
} catch(e) {
    console.log('[ERROR] hook点名称: ' + e);
}
  1. 版本适配层。App 更新后类名/方法名可能变,把"定位目标类"的逻辑做成特征搜索而非硬编码:
function findSignClass() {
    // 按特征找:有 getSign 方法且引用了特定字符串
    var classes = Java.enumerateLoadedClassesSync();
    for (var name of classes) {
        try {
            var cls = Java.use(name);
            if (cls.class.getDeclaredMethods().some(m => m.getName() === 'getSign')) {
                return cls;
            }
        } catch(e) {}
    }
}

总结

  1. 模块化:utils/抓包/签名拆库,require 组合
  2. 可配置:App 信息外置 config,换目标零改动
  3. RPC 服务化:sign/ping/version 标准三接口
  4. 日志分级、异常隔离、特征定位,生产三件套

交流微信:run1255

By 百趣云 阅读量:2 On