#!/usr/bin/env python3 """ GDB startup script for WeChat message hooking. Run with: gdb -x this_script.py --args ./WeChatLinux.AppImage --no-sandbox --disable-gpu GDB disables ASLR by default, so wechat base = 0x555555554000 """ import gdb import os import time # Breakpoint RVA from Ajax's blog (4.1.0.16, confirmed valid for 4.1.7) BP_RVA = 0x4994BEB # Fixed base when GDB starts process (ASLR disabled) FIXED_BASE = 0x555555554000 LOG_FILE = "/home/hmo/projects/AgentsMeeting/gateway/linux/logs/wechat_messages.log" def log(msg): with open(LOG_FILE, "a") as f: f.write(f"{msg}\n") gdb.write(f"{msg}\n") class WechatMessageBreakpoint(gdb.Breakpoint): """Breakpoint that fires on each incoming WeChat message.""" def __init__(self, address): super().__init__(f"*{address}") self.suppress = True def stop(self): try: msg_ptr = int(gdb.parse_and_eval("$rsi")) if msg_ptr == 0: return False raw_type = gdb.selected_inferior().read_memory(msg_ptr + 0x14, 4) msg_type = int.from_bytes(raw_type, byteorder='little', signed=True) raw_svrid = gdb.selected_inferior().read_memory(msg_ptr + 0x50, 8) svrid = int.from_bytes(raw_svrid, byteorder='little') raw_holder = gdb.selected_inferior().read_memory(msg_ptr + 0x20, 8) holder = int.from_bytes(raw_holder, byteorder='little', signed=False) if holder == 0: return False raw_inner = gdb.selected_inferior().read_memory(holder + 0x8, 8) inner = int.from_bytes(raw_inner, byteorder='little', signed=False) if inner == 0: return False # Read content length from inner + 0x10 raw_len = gdb.selected_inferior().read_memory(inner + 0x10, 4) content_len = int.from_bytes(raw_len, byteorder='little', signed=False) if content_len <= 0 or content_len > 100000: return False # Read content pointer from inner + 0x0 raw_cp = gdb.selected_inferior().read_memory(inner + 0x0, 8) content_ptr = int.from_bytes(raw_cp, byteorder='little', signed=False) if content_ptr == 0: return False raw_content = gdb.selected_inferior().read_memory(content_ptr, min(content_len * 2, 100000)) try: content = raw_content.tobytes()[:content_len * 2].decode('utf-16le', errors='replace') except: content = str(raw_content) # Try to read sender info from msg_ptr + 0x38 (talker wxid) try: raw_talker = gdb.selected_inferior().read_memory(msg_ptr + 0x38, 8) talker_ptr = int.from_bytes(raw_talker, byteorder='little', signed=False) if talker_ptr: talker_data = gdb.selected_inferior().read_memory(talker_ptr, 64) talker = talker_data.tobytes().split(b'\x00')[0].decode('utf-8', errors='replace') else: talker = "unknown" except: talker = "unknown" info = f"[WECHAT_MSG] type={msg_type} svrid={hex(svrid)} talker={talker}" log(info) log(f"[WECHAT_MSG] content: {content[:300]}") # Forward to Hermes try: import urllib.request import json payload = json.dumps({ "model": "nova-4", "messages": [ {"role": "user", "content": f"[WeChat from {talker}] {content[:500]}"} ] }).encode() req = urllib.request.Request( "http://192.168.1.246:8642/v1/chat/completions", data=payload, headers={"Content-Type": "application/json", "Authorization": "Bearer hermes123"}, method="POST" ) urllib.request.urlopen(req, timeout=2) except: pass except Exception as e: log(f"[WECHAT_MSG] ERROR: {e}") return False class AutoHookWechat(gdb.Command): """Auto-hook WeChat messages on startup.""" def __init__(self): super().__init__("auto-hook-wechat", gdb.COMMAND_USER) def invoke(self, arg, from_tty): bp_addr = FIXED_BASE + BP_RVA WechatMessageBreakpoint(bp_addr) log(f"[WECHAT_MSG] Breakpoint set at 0x{bp_addr:x}") class OnStart(gdb.Breakpoint): """Breakpoint on _start to set up hooks after process loads.""" def __init__(self): super().__init__("_start") def stop(self): gdb.execute("auto-hook-wechat") return True # Stop so user can continue # Configure GDB gdb.execute("set pagination off") gdb.execute("set confirm off") gdb.execute("handle SIG33 pass nostop noprint") gdb.execute("set follow-fork-mode child") # Register our commands AutoHookWechat() # Set breakpoint at _start so we hook after process loads OnStart() log("[WECHAT_MSG] GDB startup script loaded. Type 'run' to start WeChat.")