frida_daemon.js 3.78 KB
/**
 * Frida Daemon — 注入微信进程,读取队列文件并发送消息
 *
 * 运行方式(Mac 端):
 *   frida -U -p $(adb shell "su -c 'pgrep -x com.tencent.mm'") -l frida_daemon.js
 *
 * 队列文件:/data/data/com.termux/files/home/hermes-wechat/.frida_queue/queue.jsonl
 * 格式:每行一个 JSON {"talker":"wxid","content":"msg","id":"msg_xxx"}
 */

// 统一放在 Termux 可访问目录,避免 /data/local/tmp 权限问题
var QUEUE_FILE = "/data/data/com.termux/files/home/hermes-wechat/.frida_queue/queue.jsonl";
var LOG_TAG = "[HermesDaemon]";

function log(msg) {
    console.log(LOG_TAG + " " + msg);
}

// ── Send message via WeChat internal API ──
function sendMessage(talker, content) {
    log("Sending to " + talker + ": " + content.substring(0, 40));

    Java.perform(function () {
        Java.scheduleOnMainThread(function () {
            try {
                var r1 = Java.use("u01.r1");
                var builder = r1.a(talker);
                log("Builder class: " + builder.getClass().getName());

                var q1 = Java.use("u01.q1");
                q1.e.call(builder, content);
                q1.h.call(builder, 0);
                log("Content set, type=0");

                var m1 = q1.a.call(builder);
                log("Built m1 class: " + m1.getClass().getName());

                var m1cls = Java.use("u01.m1");
                var ret = m1cls.a.call(m1);
                log("Submit result: " + ret);

            } catch (e) {
                log("Send FAILED: " + e);
            }
        });
    });
}

// ── Read and process queue file ──
function processQueue() {
    try {
        var File = Java.use("java.io.File");
        var FileInputStream = Java.use("java.io.FileInputStream");
        var BufferedReader = Java.use("java.io.BufferedReader");
        var InputStreamReader = Java.use("java.io.InputStreamReader");
        var FileWriter = Java.use("java.io.FileWriter");

        var file = File.$new(QUEUE_FILE);
        if (!file.exists()) {
            return;
        }

        var fis = FileInputStream.$new(file);
        var reader = BufferedReader.$new(InputStreamReader.$new(fis));

        var lines = [];
        var line;
        while ((line = reader.readLine()) !== null) {
            lines.push(line);
        }
        reader.close();
        fis.close();

        if (lines.length === 0) {
            return;
        }

        log("Processing " + lines.length + " queued messages");

        var successCount = 0;
        for (var i = 0; i < lines.length; i++) {
            try {
                var msg = JSON.parse(lines[i]);
                if (msg.talker && msg.content) {
                    sendMessage(msg.talker, msg.content);
                    successCount++;
                }
            } catch (e) {
                log("Parse error: " + e);
            }
        }

        // Clear queue after processing
        var fw = FileWriter.$new(file, false);
        fw.write("");
        fw.close();

        log("Processed " + successCount + "/" + lines.length + " messages, queue cleared");

    } catch (e) {
        log("Queue processing error: " + e);
    }
}

// ── Main loop ──
Java.perform(function () {
    log("Daemon injected into WeChat process");
    log("Watching queue: " + QUEUE_FILE);
    log("Send API: u01.r1.a(talker) -> q1.e(content) -> q1.h(0) -> q1.a() -> m1.a()");

    // Process queue every 500ms
    setInterval(processQueue, 500);

    // Also expose manual trigger via rpc
    rpc.exports = {
        send: function(talker, content) {
            sendMessage(talker, content);
            return "queued";
        },
        process: function() {
            processQueue();
            return "done";
        }
    };

    log("Daemon ready. RPC exports: send(talker, content), process()");
});