WechatOnCloud/bridge/tools/salt_probe.py
Kris a2201d8be3 feat: 新增woc-bridge服务及配套能力
新增完整的woc-bridge业务服务,实现微信API代理、密钥自动提取、消息队列限流等功能:
1. 新增bridge核心代码与s6服务配置
2. 新增ptrace权限初始化脚本与docker配置
3. 新增M2M鉴权API代理与环境变量配置
4. 新增SQLCipher解密、密钥缓存、二维码截图等工具模块
5. 完善docker构建与实例部署配置
2026-07-07 19:04:13 +08:00

128 lines
4.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""通过 salt 定位快速探测内存中的 SQLCipher enc_key。
策略:
1. 读取加密 DB 的 salt
2. 在微信进程内存中搜索该 salt
3. 对每个 salt 命中点,在 ±2KB 范围内枚举 32 字节候选
4. 用 HMAC-SHA512 验证候选是否为 enc_key
"""
from __future__ import annotations
import hashlib
import hmac
import os
import struct
import sys
from typing import Optional
PAGE_SZ = 4096
KEY_SZ = 32
SALT_SZ = 16
RESERVE_SIZE = 80
IV_SIZE = 16
HMAC_SIZE = 64
MAC_SALT_XOR = 0x3A
def _verify_enc_key(enc_key: bytes, page1: bytes) -> bool:
salt = page1[:SALT_SZ]
mac_salt = bytes(b ^ MAC_SALT_XOR for b in salt)
mac_key = hashlib.pbkdf2_hmac("sha512", enc_key, mac_salt, 2, dklen=KEY_SZ)
hmac_data = page1[SALT_SZ: PAGE_SZ - RESERVE_SIZE + IV_SIZE]
stored_hmac = page1[PAGE_SZ - HMAC_SIZE: PAGE_SZ]
hm = hmac.new(mac_key, hmac_data, hashlib.sha512)
hm.update(struct.pack("<I", 1))
return hm.digest() == stored_hmac
def _get_regions(pid: int) -> list[tuple[int, int, str]]:
regions = []
try:
with open(f"/proc/{pid}/maps", "r", encoding="utf-8", errors="replace") as f:
for line in f:
parts = line.split()
if len(parts) < 2:
continue
perms = parts[1]
if "r" not in perms:
continue
name = parts[5] if len(parts) >= 6 else ""
if name in ("[vdso]", "[vsyscall]", "[vvar]"):
continue
start_s, end_s = parts[0].split("-")
start = int(start_s, 16)
end = int(end_s, 16)
regions.append((start, end - start, name))
except Exception as e:
print(f"读取 maps 失败: {e}")
return regions
def _read_chunk(mem, base: int, offset: int, size: int) -> bytes:
try:
mem.seek(base + offset)
return mem.read(size)
except Exception:
return b""
def probe_pid(pid: int, salt: bytes, page1: bytes, window: int = 2048) -> Optional[bytes]:
regions = _get_regions(pid)
print(f"PID {pid}: {len(regions)} 个可读区域salt={salt.hex()}")
found = 0
with open(f"/proc/{pid}/mem", "rb") as mem:
for base, size, name in regions:
pos = 0
chunk_size = 4 * 1024 * 1024
while pos < size:
to_read = min(chunk_size, size - pos)
data = _read_chunk(mem, base, pos, to_read)
if not data:
break
idx = 0
while True:
hit = data.find(salt, idx)
if hit < 0:
break
abs_addr = base + pos + hit
found += 1
if found % 100 == 0:
print(f" 已找到 {found} 个 salt 命中...")
# 读取 ±window 区域
start = max(0, hit - window)
end = min(len(data), hit + SALT_SZ + window)
region = data[start:end]
# 枚举 32 字节候选16 字节对齐)
for cand_off in range(0, len(region) - KEY_SZ + 1):
cand = region[cand_off:cand_off + KEY_SZ]
if _verify_enc_key(cand, page1):
print(f" !! 找到 enc_key @ PID={pid} addr=0x{abs_addr - window + cand_off:016x} name={name}")
return cand
idx = hit + 1
pos += len(data)
print(f"PID {pid}: salt 命中 {found} 次,未找到 enc_key")
return None
def main():
db_path = sys.argv[1] if len(sys.argv) > 1 else "/config/xwechat_files/wxid_zfj3oc1hn5no22_43c4/db_storage/contact/contact.db"
pid = int(sys.argv[2]) if len(sys.argv) > 2 else 348
with open(db_path, "rb") as f:
page1 = f.read(PAGE_SZ)
salt = page1[:SALT_SZ]
print(f"DB: {db_path}, salt={salt.hex()}")
key = probe_pid(pid, salt, page1)
if key:
print(f"enc_key={key.hex()}")
return 0
print("未找到 enc_key")
return 1
if __name__ == "__main__":
sys.exit(main())