1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
| import { promises } from "node:fs"; import { EventEmitter } from "node:events"; import path from "node:path"; import * as frida from "frida"; import WebSocket, { WebSocketServer } from "ws";
const codex = require("./third-party/RemoteDebugCodex.js"); const messageProto = require("./third-party/WARemoteDebugProtobuf.js");
class DebugMessageEmitter extends EventEmitter {};
const DEBUG_PORT = 9421;
const CDP_PORT = 62000;
const DEBUG = false;
const debugMessageEmitter = new DebugMessageEmitter();
const **bufferToHexString** = (_buffer_: ArrayBuffer) => { return Array.from(new Uint8Array(_buffer_)).map(_byte_ => _byte_.toString(16).padStart(2, '0')).join(""); }
const **debug_server** = () => { const wss = new WebSocketServer({ port: DEBUG_PORT, host: '0.0.0.0' }); console.log(`[server] debug server running on ws://0.0.0.0:${DEBUG_PORT}`);
let messageCounter = 0;
const **onMessage** = (_message_: ArrayBuffer) => { DEBUG && console.log(`[client] received raw message (hex): ${bufferToHexString(_message_)}`); let unwrappedData: any = null; try { const decodedData = messageProto.mmbizwxadevremote.WARemoteDebug_DebugMessage.decode(_message_); unwrappedData = codex.unwrapDebugMessageData(decodedData); DEBUG && console.log(`[client] [DEBUG] decoded data:`); DEBUG && console.dir(unwrappedData) } catch (e) { console.error(`[client] err: ${e}`); }
if (unwrappedData === null) { return; }
if (unwrappedData.category === "chromeDevtoolsResult") { _ debugMessageEmitter.emit("cdpmessage", unwrappedData.data.payload); } }
wss.on("connection", (_ws_: WebSocket) => { console.log("[conn] miniapp client connected"); _ws_.on("message", onMessage); _ws_.on("error", (_err_) => {console.error("[client] err:", _err_)}); _ws_.on("close", () => {console.log("[client] client disconnected")}); });
debugMessageEmitter.on("proxymessage", (_message_: string) => { wss && wss.clients.forEach(_client_ => { if (_client_.readyState === WebSocket.OPEN) { _ _ const rawPayload = { jscontext_id: "", op_id: Math.round(100 * Math.random()), payload: _message_.toString() }; DEBUG && console.log(rawPayload); const wrappedData = codex.wrapDebugMessageData(rawPayload, "chromeDevtools", 0); const outData = { seq: ++messageCounter, category: "chromeDevtools", data: wrappedData.buffer, compressAlgo: 0, originalSize: wrappedData.originalSize } const encodedData = messageProto.mmbizwxadevremote.WARemoteDebug_DebugMessage.encode(outData).finish(); _client_.send(encodedData, { binary: true }); } }); }); }
const **proxy_server** = () => { const wss = new WebSocketServer({ port: CDP_PORT, host: '0.0.0.0' }); console.log(`[server] proxy server running on ws://0.0.0.0:${CDP_PORT}`);
const **onMessage** = (_message_: string) => { debugMessageEmitter.emit("proxymessage", _message_); }
wss.on("connection", (_ws_: WebSocket) => { console.log("[conn] CDP client connected"); _ws_.on("message", onMessage); _ws_.on("error", (_err_) => {console.error("[client] CDP err:", _err_)}); _ws_.on("close", () => {console.log("[client] CDP client disconnected")}); });
debugMessageEmitter.on("cdpmessage", (_message_: string) => { wss && wss.clients.forEach(_client_ => { if (_client_.readyState === WebSocket.OPEN) { _ _client_.send(_message_); } }); }); }
const **frida_server** = async () => { const localDevice = await frida.getLocalDevice(); const processes = await localDevice.enumerateProcesses({scope: frida.Scope.Metadata}); const wmpfProcesses = processes.filter(_process_ => _process_.name === "WeChatAppEx.exe"); const wmpfPids = wmpfProcesses.map(_p_ => _p_.parameters.ppid ? _p_.parameters.ppid : 0);
_ const wmpfPid = wmpfPids.sort((_a_, _b_) => wmpfPids.filter(_v_ => _v_ === _a_).length - wmpfPids.filter(_v_ => _v_ === _b_).length).pop(); if (wmpfPid === undefined) { throw new Error("[frida] WeChatAppEx.exe process not found"); return; } const wmpfProcess = processes.filter(_process_ => _process_.pid === wmpfPid)[0]; const wmpfVersionMatch = wmpfProcess.parameters.path ? wmpfProcess.parameters.path.match(/\d+/g) : ""; const wmpfVersion = wmpfVersionMatch ? new Number(wmpfVersionMatch.pop()) : 0; if (wmpfVersion === 0) { throw new Error("[frida] error in find wmpf version"); return; }
_ const session = await localDevice.attach(wmpfPid);
_ const projectRoot = path.join(path.dirname(require.main && require.main.filename || process.mainModule && process.mainModule.filename || process.cwd()), ".."); let scriptContent: string | null = null; try { scriptContent = (await promises.readFile(path.join(projectRoot, "frida/hook.js"))).toString(); } catch (e) { throw new Error("[frida] hook script not found"); return; }
let configContent: string | null = null; try { configContent = (await promises.readFile(path.join(projectRoot, "frida/config", `addresses.${wmpfVersion}.json`))).toString(); configContent = JSON.stringify(JSON.parse(configContent)); } catch(e) { throw new Error(`[frida] version config not found: ${wmpfVersion}`); }
if (scriptContent === null || configContent === null) { throw new Error("[frida] unable to find hook script"); return; }
_ const script = await session.createScript(scriptContent.replace("@@CONFIG@@", configContent)); script.message.connect(_message_ => { console.log("[frida client]", _message_); }); await script.load(); console.log(`[frida] script loaded, WMPF version: ${wmpfVersion}, pid: ${wmpfPid}`); }
const **main** = async () => { debug_server(); proxy_server(); frida_server(); }
(async () => { await main(); })();
|