feat: add unified entry point and multi-process key extraction

Add main.py as single entry point that auto-detects config, extracts keys, and launches Web UI or decrypts databases in one command.
Refactor find_all_keys to scan all Weixin.exe processes instead of only the largest one, enabling multi=account support.
This commit is contained in:
PeanutSplash
2026-03-03 22:20:12 +08:00
parent bf68409c39
commit 6898a065d7
3 changed files with 197 additions and 121 deletions

View File

@@ -33,20 +33,23 @@ class MBI(ctypes.Structure):
("Protect", wt.DWORD), ("Type", wt.DWORD), ("_pad2", wt.DWORD),
]
def get_pid():
def get_pids():
"""返回所有 Weixin.exe 进程的 (pid, mem_kb) 列表,按内存降序"""
import subprocess
r = subprocess.run(["tasklist","/FI","IMAGENAME eq Weixin.exe","/FO","CSV","/NH"],
capture_output=True, text=True)
best = (0,0)
pids = []
for line in r.stdout.strip().split('\n'):
if not line.strip(): continue
p = line.strip('"').split('","')
if len(p)>=5:
pid=int(p[1]); mem=int(p[4].replace(',','').replace(' K','').strip() or '0')
if mem>best[1]: best=(pid,mem)
if not best[0]: print("[ERROR] Weixin.exe 未运行"); sys.exit(1)
print(f"[+] Weixin.exe PID={best[0]} ({best[1]//1024}MB)")
return best[0]
pids.append((pid, mem))
if not pids: raise RuntimeError("Weixin.exe 未运行")
pids.sort(key=lambda x: x[1], reverse=True)
for pid, mem in pids:
print(f"[+] Weixin.exe PID={pid} ({mem//1024}MB)")
return pids
def read_mem(h, addr, sz):
buf = ctypes.create_string_buffer(sz)
@@ -113,98 +116,97 @@ def main():
for salt_hex, dbs in sorted(salt_to_dbs.items(), key=lambda x: len(x[1]), reverse=True):
print(f" salt {salt_hex}: {', '.join(dbs)}")
# 2. 打开进程
pid = get_pid()
h = kernel32.OpenProcess(0x0010 | 0x0400, False, pid)
if not h:
print("[ERROR] 无法打开进程"); sys.exit(1)
# 2. 打开所有微信进程
pids = get_pids()
regions = enum_regions(h)
total_mb = sum(s for _,s in regions)/1024/1024
print(f"[+] 可读内存: {len(regions)} 区域, {total_mb:.0f}MB")
# 3. 搜索所有 x'<hex>' 模式
print(f"\n搜索 x'<hex>' 缓存密钥...")
hex_re = re.compile(b"x'([0-9a-fA-F]{64,192})'")
# 结果: salt_hex -> enc_key_hex
key_map = {}
key_map = {} # salt_hex -> enc_key_hex
all_hex_matches = 0
t0 = time.time()
for reg_idx, (base, size) in enumerate(regions):
data = read_mem(h, base, size)
if not data: continue
for proc_idx, (pid, mem) in enumerate(pids):
h = kernel32.OpenProcess(0x0010 | 0x0400, False, pid)
if not h:
print(f"[WARN] 无法打开进程 PID={pid},跳过")
continue
for m in hex_re.finditer(data):
hex_str = m.group(1).decode()
addr = base + m.start()
all_hex_matches += 1
hex_len = len(hex_str)
regions = enum_regions(h)
total_mb = sum(s for _,s in regions)/1024/1024
print(f"\n[*] 扫描 PID={pid} ({total_mb:.0f}MB, {len(regions)} 区域)")
if hex_len == 96:
# enc_key(32bytes=64hex) + salt(16bytes=32hex)
enc_key_hex = hex_str[:64]
salt_hex = hex_str[64:]
for reg_idx, (base, size) in enumerate(regions):
data = read_mem(h, base, size)
if not data: continue
if salt_hex in salt_to_dbs and salt_hex not in key_map:
# 验证!
for m in hex_re.finditer(data):
hex_str = m.group(1).decode()
addr = base + m.start()
all_hex_matches += 1
hex_len = len(hex_str)
if hex_len == 96:
enc_key_hex = hex_str[:64]
salt_hex = hex_str[64:]
if salt_hex in salt_to_dbs and salt_hex not in key_map:
enc_key = bytes.fromhex(enc_key_hex)
for rel, path, sz, s, page1 in db_files:
if s == salt_hex:
if verify_key_for_db(enc_key, page1):
key_map[salt_hex] = enc_key_hex
dbs = salt_to_dbs[salt_hex]
print(f"\n [FOUND] salt={salt_hex}")
print(f" enc_key={enc_key_hex}")
print(f" PID={pid} 地址: 0x{addr:016X}")
print(f" 数据库: {', '.join(dbs)}")
break
elif hex_len == 64:
enc_key_hex = hex_str
enc_key = bytes.fromhex(enc_key_hex)
# 找到对应的page1
for rel, path, sz, s, page1 in db_files:
if s == salt_hex:
for rel, path, sz, salt_hex_db, page1 in db_files:
if salt_hex_db not in key_map:
if verify_key_for_db(enc_key, page1):
key_map[salt_hex] = enc_key_hex
dbs = salt_to_dbs[salt_hex]
print(f"\n [FOUND] salt={salt_hex}")
key_map[salt_hex_db] = enc_key_hex
dbs = salt_to_dbs[salt_hex_db]
print(f"\n [FOUND] salt={salt_hex_db}")
print(f" enc_key={enc_key_hex}")
print(f" 地址: 0x{addr:016X}")
print(f" PID={pid} 地址: 0x{addr:016X}")
print(f" 数据库: {', '.join(dbs)}")
break
break
elif hex_len == 64:
# 只有enc_key, 没有salt - 需要逐个DB试
enc_key_hex = hex_str
enc_key = bytes.fromhex(enc_key_hex)
for rel, path, sz, salt_hex_db, page1 in db_files:
if salt_hex_db not in key_map:
if verify_key_for_db(enc_key, page1):
key_map[salt_hex_db] = enc_key_hex
dbs = salt_to_dbs[salt_hex_db]
print(f"\n [FOUND] salt={salt_hex_db}")
print(f" enc_key={enc_key_hex}")
print(f" 地址: 0x{addr:016X}")
print(f" 数据库: {', '.join(dbs)}")
break
elif hex_len > 96 and hex_len % 2 == 0:
enc_key_hex = hex_str[:64]
salt_hex = hex_str[-32:]
elif hex_len > 96 and hex_len % 2 == 0:
# 可能是 enc_key + hmac_key + salt 或其他格式
# 取前64作为enc_key, 后32作为salt
enc_key_hex = hex_str[:64]
salt_hex = hex_str[-32:]
if salt_hex in salt_to_dbs and salt_hex not in key_map:
enc_key = bytes.fromhex(enc_key_hex)
for rel, path, sz, s, page1 in db_files:
if s == salt_hex:
if verify_key_for_db(enc_key, page1):
key_map[salt_hex] = enc_key_hex
dbs = salt_to_dbs[salt_hex]
print(f"\n [FOUND] salt={salt_hex} (long hex {hex_len})")
print(f" enc_key={enc_key_hex}")
print(f" PID={pid} 地址: 0x{addr:016X}")
print(f" 数据库: {', '.join(dbs)}")
break
if salt_hex in salt_to_dbs and salt_hex not in key_map:
enc_key = bytes.fromhex(enc_key_hex)
for rel, path, sz, s, page1 in db_files:
if s == salt_hex:
if verify_key_for_db(enc_key, page1):
key_map[salt_hex] = enc_key_hex
dbs = salt_to_dbs[salt_hex]
print(f"\n [FOUND] salt={salt_hex} (long hex {hex_len})")
print(f" enc_key={enc_key_hex}")
print(f" 地址: 0x{addr:016X}")
print(f" 数据库: {', '.join(dbs)}")
break
if (reg_idx + 1) % 200 == 0:
elapsed = time.time() - t0
progress = sum(s for b,s in regions[:reg_idx+1]) / sum(s for _,s in regions) * 100
print(f" [{progress:.1f}%] {len(key_map)}/{len(salt_to_dbs)} salts matched, "
f"{all_hex_matches} hex patterns, {elapsed:.1f}s")
# 进度
if (reg_idx + 1) % 200 == 0:
elapsed = time.time() - t0
progress = sum(s for b,s in regions[:reg_idx+1]) / sum(s for _,s in regions) * 100
print(f" [{progress:.1f}%] {len(key_map)}/{len(salt_to_dbs)} salts matched, "
f"{all_hex_matches} hex patterns, {elapsed:.1f}s")
kernel32.CloseHandle(h)
# 所有 salt 都找到了就提前退出
if len(key_map) == len(salt_to_dbs):
print(f"\n[+] 所有密钥已找到,跳过剩余进程")
break
elapsed = time.time() - t0
print(f"\n扫描完成: {elapsed:.1f}s, {all_hex_matches} hex模式")
print(f"\n扫描完成: {elapsed:.1f}s, {len(pids)} 个进程, {all_hex_matches} hex模式")
# 4. 如果有未找到的salt用已找到的key做交叉验证
# (WCDB有时对同一passphrase的不同DB用同一enc_key如果salt相同)
@@ -248,7 +250,6 @@ def main():
for rel in missing:
print(f" {rel}")
kernel32.CloseHandle(h)
if __name__ == '__main__':