wechat_earlybird.py 4.84 KB
"""
WeChat early-bird key capture:
1. Kill WeChat
2. Poll until new WeChat process appears
3. Attach Frida immediately
4. Capture CCCryptorCreate keys during login / DB open
"""
import sys, os, time, subprocess, threading
sys.path.insert(0, '/Users/yibo/Library/Python/3.9/lib/python/site-packages')
import frida

SQLCIPHER = '/opt/homebrew/bin/sqlcipher'
DB_BASE   = '/Users/yibo/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/yibooo_ac07/db_storage'

JS = r"""
var seen = {};

function uniqueByteCount(arr) {
    var map = {};
    var n = 0;
    for (var i = 0; i < arr.length; i++) {
        if (!map[arr[i]]) { map[arr[i]] = true; n++; }
    }
    return n;
}

function tryCapture(keyPtr, keyLen, label) {
    try {
        if (keyLen !== 32) return;
        var raw = keyPtr.readByteArray(32);
        if (!raw) return;
        var bytes = new Uint8Array(raw);
        if (uniqueByteCount(bytes) < 8) return;
        var hex = '';
        for (var i = 0; i < bytes.length; i++) {
            hex += ('0' + bytes[i].toString(16)).slice(-2);
        }
        if (!seen[hex]) {
            seen[hex] = true;
            send({type:'key', hex:hex, len:keyLen, via:label});
        }
    } catch(e) {
        send({type:'err', msg: e.toString(), label: label});
    }
}

var found = {};

var addr1 = Module.findGlobalExportByName('CCCryptorCreate');
found['CCCryptorCreate'] = !!addr1;
if (addr1) {
    Interceptor.attach(addr1, { onEnter: function(args) {
        if (args[1].toInt32() === 0) tryCapture(args[3], args[4].toInt32(), 'CCCryptorCreate');
    }});
}

var addr2 = Module.findGlobalExportByName('CCCryptorCreateWithMode');
found['CCCryptorCreateWithMode'] = !!addr2;
if (addr2) {
    Interceptor.attach(addr2, { onEnter: function(args) {
        if (args[2].toInt32() === 0) tryCapture(args[5], args[6].toInt32(), 'CCCryptorCreateWithMode');
    }});
}

var addr3 = Module.findGlobalExportByName('CCCrypt');
found['CCCrypt'] = !!addr3;
if (addr3) {
    Interceptor.attach(addr3, { onEnter: function(args) {
        if (args[1].toInt32() === 0) tryCapture(args[3], args[4].toInt32(), 'CCCrypt');
    }});
}

send({type:'ready', found:found});
"""

def verify_key(key_hex, rel):
    path = os.path.join(DB_BASE, rel)
    if not os.path.exists(path): return False
    r = subprocess.run([SQLCIPHER, path],
        input=f"PRAGMA key=\"x'{key_hex}'\";\nSELECT count(*) FROM sqlite_master;\n",
        capture_output=True, text=True, timeout=10)
    lines = [l.strip() for l in r.stdout.split('\n') if l.strip() and l.strip() != 'ok']
    return bool(lines) and lines[0].isdigit()

TEST_DBS = [
    'contact/contact.db',
    'message/message_0.db',
    'session/session.db',
    'general/general.db',
    'message/message_1.db',
]

confirmed = {}  # key_hex -> [dbs]

def on_key(key_hex, via, keylen):
    print(f'\n  [key] len={keylen} via={via}  {key_hex[:24]}...')
    matched = []
    for rel in TEST_DBS:
        try:
            if verify_key(key_hex, rel):
                matched.append(rel)
                print(f'    ✓ matches {rel}')
        except Exception as e:
            pass
    if not matched:
        print(f'    (no match in test set)')
    else:
        confirmed[key_hex] = matched

# ── main ──────────────────────────────────────────────────────────────────────
print('[1] Killing WeChat...')
subprocess.run(['killall', 'WeChat'], capture_output=True)
time.sleep(2)

print('[2] Waiting for WeChat to start (open 微信.app now)...')
print('    → 请现在手动打开微信.app')

pid = None
for _ in range(120):   # wait up to 2 min
    out = subprocess.run(['pgrep','-x','WeChat'], capture_output=True, text=True).stdout.strip()
    if out:
        pid = int(out.split('\n')[0])
        break
    time.sleep(0.5)

if not pid:
    print('[!] WeChat did not start within 2 minutes')
    sys.exit(1)

print(f'[3] WeChat PID={pid} detected, attaching Frida NOW...')
session = frida.attach(pid)
script  = session.create_script(JS)

def on_msg(msg, data):
    if msg['type'] == 'error':
        print(f'[JS ERROR] {msg["description"]}')
        return
    if msg['type'] != 'send': return
    p = msg['payload']
    if p['type'] == 'ready':
        print(f'[4] Hooks: {p["found"]}')
        print('    → 请登录微信,然后慢慢浏览各个聊天窗口\n')
    elif p['type'] == 'key':
        on_key(p['hex'], p['via'], p['len'])
    elif p['type'] == 'err':
        print(f'  [js-err] {p["label"]}: {p["msg"]}')

script.on('message', on_msg)
script.load()
print('[*] Script loaded. Monitoring... (Ctrl+C to stop)\n')

try:
    while True:
        time.sleep(1)
except KeyboardInterrupt:
    pass

print(f'\n[结果] 捕获到 {len(confirmed)} 个已验证 key:')
for k, dbs in confirmed.items():
    print(f'  {k}  →  {dbs}')
session.detach()