feat: WeChat Linux bot via docker-wechatbot-webhook
- Docker container with auto-restart - systemd webhook receiver on :5804 - Full send/receive loop: WeChat ↔ Docker ↔ Hermes - Fixed login token for persistence - Firewall rules for container-host communication
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
#!/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.")
|
||||
Reference in New Issue
Block a user