#!/usr/bin/env python3 """ mitmproxy 插件:自动保存 TNG Money Packet 含 receiverList 的 API 响应。 用法: mitmdump -s debug-server/tng_mmp_mitm_addon.py -p 8888 或 mitmweb -s debug-server/tng_mmp_mitm_addon.py -p 8888 手机 WiFi 代理 -> PC_IP:8888,安装 mitmproxy CA 后打开 TNG 红包 Leaderboard。 命中响应会打印到终端,并写入 reverse/dumps/mitm_mmp/ """ from __future__ import annotations import json import os import re from datetime import datetime from mitmproxy import ctx, http OUTPUT_DIR = os.path.join( os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "reverse", "dumps", "mitm_mmp", ) MMP_HINTS = ( "receiverlist", "claimedamount", "mmpreceiver", "moneypacket", "merchantmoneypacket", ) HOST_HINTS = ( "ebuckler.com", "tngdigital.com", "alipaydev.com", ) def _ensure_dir() -> None: os.makedirs(OUTPUT_DIR, exist_ok=True) def _looks_like_mmp(body: str) -> bool: lower = body.lower() if any(h in lower for h in MMP_HINTS): return True return "mmp" in lower and "amount" in lower def _extract_receiver_list(obj): """递归找 receiverList 并格式化为 [(nickname, amount), ...]""" rows = [] def walk(node): if isinstance(node, dict): if "receiverList" in node and isinstance(node["receiverList"], list): for item in node["receiverList"]: if not isinstance(item, dict): continue name = ( item.get("nickName") or item.get("displayName") or item.get("userName") or item.get("receiverName") or item.get("name") ) amount = ( item.get("claimedAmount") or item.get("receiveAmount") or item.get("amount") ) if name and amount is not None: rows.append((str(name), str(amount))) for v in node.values(): walk(v) elif isinstance(node, list): for v in node: walk(v) walk(obj) return rows class TngMmpCapture: def __init__(self) -> None: _ensure_dir() self.count = 0 ctx.log.info(f"TNG MMP capture -> {OUTPUT_DIR}") def response(self, flow: http.HTTPFlow) -> None: if flow.response is None or not flow.response.content: return host = (flow.request.host or "").lower() if not any(h in host for h in HOST_HINTS): return try: text = flow.response.get_text(strict=False) except Exception: return if not text or not _looks_like_mmp(text): return self.count += 1 ts = datetime.now().strftime("%Y%m%d_%H%M%S") safe_host = re.sub(r"[^\w.-]", "_", host)[:40] path = os.path.join(OUTPUT_DIR, f"mmp_{ts}_{self.count}_{safe_host}.json") summary_lines = [ f"[TNG-MMP #{self.count}] {flow.request.method} {flow.request.url}", ] try: data = json.loads(text) with open(path, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) rows = _extract_receiver_list(data) if rows: summary_lines.append(f" receiverList ({len(rows)} 条):") for name, amount in rows: summary_lines.append(f" {name} -> {amount}") else: summary_lines.append(" (JSON 已保存,未解析到 receiverList)") except json.JSONDecodeError: with open(path, "w", encoding="utf-8") as f: f.write(text) summary_lines.append(" (非 JSON,已保存原文)") summary_lines.append(f" saved: {path}") ctx.log.info("\n".join(summary_lines)) addons = [TngMmpCapture()]