打开 App 并滑动页面,新的请求会出现在左边。+
diff --git a/capture/live.py b/capture/live.py new file mode 100644 index 0000000..e892f76 --- /dev/null +++ b/capture/live.py @@ -0,0 +1,195 @@ +"""把 Dart 发出的明文 HTTP 显示在本机页面。 + +Charles 看不到这些请求:它们不走系统代理,在进系统套接字之前就已经加密。 +这里直接从进程内存里取出加密前的报文。 +""" +import json +import threading +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer + +import frida + +DEVICE = "emulator-5556" +PORT = 8791 +APP_NAMES = ("RaptDrama",) + +JS = r""" +function extract(addr) { + var back = addr; + for (var i = 0; i < 160; i++) { + var b = 0; + try { b = back.sub(1).readU8(); } catch (e) { break; } + if (b !== 0x0a && b !== 0x0d && (b < 0x20 || b > 0x7e)) break; + back = back.sub(1); + } + var s = ""; + for (var j = 0; j < 2500; j++) { + var c = 0; + try { c = back.add(j).readU8(); } catch (e) { break; } + if (c === 0x0d) continue; + if (c === 0x0a) { s += "\n"; continue; } + if (c < 0x20 || c > 0x7e) break; + s += String.fromCharCode(c); + } + return s; +} + +var seen = {}; +var scanning = false; +var lastScan = 0; + +function scan() { + var now = Date.now(); + if (scanning || now - lastScan < 1200) return; + scanning = true; + lastScan = now; + var ranges = Process.enumerateRanges("rw-"); + var fresh = []; + for (var i = 0; i < ranges.length; i++) { + var r = ranges[i]; + if (r.size > 24 * 1024 * 1024) continue; + var matches; + try { matches = Memory.scanSync(r.base, r.size, "48 54 54 50 2f 31 2e 31"); } + catch (e) { continue; } + for (var j = 0; j < matches.length; j++) { + var text = extract(matches[j].address); + if (!text || text.length < 16 || seen[text]) continue; + if (text.indexOf("package:") === 0) continue; + seen[text] = true; + fresh.push(text); + } + } + if (fresh.length) send(JSON.stringify(fresh)); + scanning = false; +} + +var writePtr = Process.getModuleByName("libc.so").getExportByName("write"); +Interceptor.attach(writePtr, { + onEnter: function (args) { + try { + if (args[1].readU8() === 0x17) scan(); + } catch (e) {} + } +}); +scan(); +send("ready"); +""" + +FLOWS = [] +LOCK = threading.Lock() + + +def on_message(message, _data): + if message.get("type") != "send": + print(message, flush=True) + return + payload = message["payload"] + if payload == "ready": + print("已挂上进程,等待请求", flush=True) + return + items = json.loads(payload) + with LOCK: + for text in items: + first = text.strip().split("\n", 1)[0][:180] + FLOWS.append({"id": len(FLOWS) + 1, "summary": first, "text": text}) + print(first, flush=True) + + +def attach(): + device = frida.get_device(DEVICE) + pid = None + for proc in device.enumerate_processes(): + if proc.name in APP_NAMES or "Rapt" in proc.name: + pid = proc.pid + break + if pid is None: + raise SystemExit("RaptDrama 没在运行。先打开 App,再启动这个页面。") + print(f"attach {pid}", flush=True) + session = device.attach(pid) + script = session.create_script(JS) + script.on("message", on_message) + script.load() + return session, script + + +PAGE = """ + +
+ +打开 App 并滑动页面,新的请求会出现在左边。+