新增完整的woc-bridge业务服务,实现微信API代理、密钥自动提取、消息队列限流等功能: 1. 新增bridge核心代码与s6服务配置 2. 新增ptrace权限初始化脚本与docker配置 3. 新增M2M鉴权API代理与环境变量配置 4. 新增SQLCipher解密、密钥缓存、二维码截图等工具模块 5. 完善docker构建与实例部署配置
128 lines
4.1 KiB
Python
128 lines
4.1 KiB
Python
"""通过 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())
|