#!/usr/bin/env python3 """ analyze_wechathook.py — 从 Tinker patch DEX 分析 WeChat 8.0.74 消息发送路径 在 Termux 上运行: python3 /sdcard/wechathook/analyze_wechathook.py 目标: 1. 解码 w11.r0 构造器字节码,看它调了什么 2. 解码 gm0.j1.i() 字节码,确认它检查的是字段 m 3. 检查 gm0.j1.m 在运行时是否 null(通过分析 gm0.j1.g() 是否被调用) """ import struct, os, subprocess # Tinker patch DEX (需要有 root 才能访问) TINKER_APK = "/data/user/0/com.tencent.mm/tinker/patch-cf87f687/dex/tinker_classN.apk" WORK_DIR = "/data/local/tmp/wh_dex_analysis" def ensure_dex(): os.makedirs(WORK_DIR, exist_ok=True) dex = f"{WORK_DIR}/classes12.dex" if not os.path.exists(dex): # 需要 root 拷贝 r = subprocess.run(["su", "-c", f"cp {TINKER_APK} /data/local/tmp/_tinker.apk && chmod 644 /data/local/tmp/_tinker.apk"], capture_output=True, text=True) if r.returncode != 0: print("需要 root 权限:", r.stderr) return None import zipfile with zipfile.ZipFile("/data/local/tmp/_tinker.apk") as z: z.extract("classes12.dex", WORK_DIR) print(f"Extracted classes12.dex ({os.path.getsize(dex)} bytes)") return dex def uleb128(data, off): r, s = 0, 0 while True: b = data[off]; off += 1 r |= (b & 0x7F) << s if not (b & 0x80): break s += 7 return r, off def parse_dex(path): data = open(path, "rb").read() h = lambda o: struct.unpack_from('= ss: return f"?s{i}" off = h(so + i*4); _, o2 = uleb128(data, off) return data[o2:data.index(b'\x00', o2)].decode('utf-8', errors='replace') def gt(i): if i >= ts: return f"?t{i}" return gs(h(to + i*4)) def gp(pi): if pi >= ps: return "?proto" pb = po + pi*12 ret = gt(h(pb+4)); poff = h(pb+8); params = [] if poff: n = h(poff) for k in range(n): params.append(gt(struct.unpack_from('= ts: continue name = gt(ci) cdo = h(base+24) if cdo == 0: continue off = cdo sf, off = uleb128(data, off) inf, off = uleb128(data, off) dm, off = uleb128(data, off) vm, off = uleb128(data, off) fields, methods = [], [] fi = 0 for _ in range(sf+inf): d, off = uleb128(data, off); fi += d fl, off = uleb128(data, off) if fi < fs: fb = fo+fi*8 ft = struct.unpack_from('> 4) & 0xf i += 2 if op in range(0x02, 0x0e) or op in range(0x1a, 0x22) or op >= 0x26 else 1 return '\n'.join(lines) if lines else " (no invoke/field ops)" def main(): dex = ensure_dex() if not dex: return print("Parsing DEX...") classes, data, to, ts, fo, fs, mo, ms, po, ps, so, ss = parse_dex(dex) # ── 1. gm0.j1 structure ────────────────────────────────────────── print("\n" + "="*60) print("1. gm0.j1 class") print("="*60) j1 = classes.get("Lgm0/j1;") if j1: print("Static fields:") for name, typ, flags in j1['fields']: print(f" {name}: {typ} flags=0x{flags:x}") print("\nMethods with signatures:") for name, proto, flags, co2 in j1['methods']: flag_s = '+'.join(f for f, b in [('public',1),('static',8),('sync',0x20),('ctor',0x10000)] if flags&b) print(f" {name}: {proto} [{flag_s}]") else: print("NOT FOUND") # ── 2. w11.r0 structure ────────────────────────────────────────── print("\n" + "="*60) print("2. w11.r0 class") print("="*60) r0 = classes.get("Lw11/r0;") if r0: print("Static fields:") for name, typ, flags in r0['fields']: print(f" {name}: {typ}") print(f"\nConstructors (looking for (String,String,int,int,long)):") for name, proto, flags, co2 in r0['methods']: if name == "": print(f" {proto} code_off=0x{co2:x}") else: print("NOT FOUND") # ── 3. Decode gm0.j1.i() bytecode ─────────────────────────────── print("\n" + "="*60) print("3. gm0.j1.i() bytecode (key ops)") print("="*60) if j1: for name, proto, flags, co2 in j1['methods']: if name == "i" and "()Lgm0/j1;" in proto: print(f"gm0.j1.i() code_off=0x{co2:x}") print(dump_bytecode(data, co2)) break # ── 4. Decode w11.r0 (String,String,int,int,long) ctor ────────── print("\n" + "="*60) print("4. w11.r0 constructor bytecode (key invoke ops)") print("="*60) if r0: for name, proto, flags, co2 in r0['methods']: if name == "" and "Ljava/lang/String;Ljava/lang/String;IIJ" in proto: print(f"(String,String,int,int,long) code_off=0x{co2:x}") print(dump_bytecode(data, co2, max_insns=40)) break # ── 5. Who initializes gm0.j1.m? Search for sput-object on field m ─ print("\n" + "="*60) print("5. Searching gm0.j1.g() bytecode") print("="*60) if j1: for name, proto, flags, co2 in j1['methods']: if name == "g": print(f"gm0.j1.g{proto} code_off=0x{co2:x}") print(dump_bytecode(data, co2)) break print("\n== Done ==") if __name__ == "__main__": main()