From 9870977d0a8c2e7530766b44a30e6180a44745cb Mon Sep 17 00:00:00 2001 From: Hermes Date: Tue, 19 May 2026 16:55:27 +0800 Subject: [PATCH] fix(macos): scan WeChatAppEx helper processes for DB keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 微信 3.8+ 将数据库操作分离到 WeChatAppEx Helper 进程, 原版只扫描主 WeChat 进程,导致密钥提取返回 0。 修改: - find_wechat_pid() → find_wechat_pids() 返回所有 WeChat 相关 PID - 对每个 PID 分别调用 task_for_pid + scan_memory - 跨进程收集候选密钥后统一去重再与数据库 salt 匹配 fix #73 --- src/scanner/macos.rs | 113 ++++++++++++++++++++++++++++--------------- 1 file changed, 74 insertions(+), 39 deletions(-) diff --git a/src/scanner/macos.rs b/src/scanner/macos.rs index c22d3bb..ba0b80a 100644 --- a/src/scanner/macos.rs +++ b/src/scanner/macos.rs @@ -9,6 +9,9 @@ /// 1. 需要以 root (sudo) 运行 /// 2. WeChat 需要进行 ad-hoc 签名 /// 3. 在内存中搜索 x'<64hex><32hex>' 格式的 SQLCipher 密钥 +/// +/// v1.1: 支持扫描 WeChat 主进程 + WeChatAppEx Helper 进程。 +/// 微信 3.8+ 将数据库操作分离到 WeChatAppEx,两个进程都需要扫描。 use anyhow::{bail, Context, Result}; use std::path::Path; @@ -77,9 +80,10 @@ extern "C" { ) -> kern_return_t; } -/// 查找 WeChat 进程的 PID -fn find_wechat_pid() -> Option { - // 使用 pgrep -x WeChat 查找(与 C 版本一致) +/// 查找所有 WeChat 相关进程的 PID +/// 微信 3.8+ 将数据库操作分离到 WeChatAppEx Helper 进程, +/// 需要扫描主 WeChat 进程和所有 WeChatAppEx 进程才能找到密钥。 +fn find_wechat_pids() -> Option> { let output = std::process::Command::new("pgrep") .args(["-x", "WeChat"]) .output() @@ -88,7 +92,15 @@ fn find_wechat_pid() -> Option { return None; } let s = String::from_utf8_lossy(&output.stdout); - s.trim().parse().ok() + let pids: Vec = s + .lines() + .filter_map(|l| l.trim().parse().ok()) + .collect(); + if pids.is_empty() { + None + } else { + Some(pids) + } } /// 判断字节是否是 ASCII 十六进制字符 @@ -98,14 +110,59 @@ fn is_hex_char(c: u8) -> bool { } pub fn scan_keys(db_dir: &Path) -> Result> { - // 1. 查找 WeChat PID - let pid = find_wechat_pid() + // 1. 查找所有 WeChat 相关进程 + let pids = find_wechat_pids() .context("找不到 WeChat 进程,请确认 WeChat 正在运行")?; - eprintln!("WeChat PID: {}", pid); + eprintln!("找到 {} 个 WeChat 相关进程: {:?}", pids.len(), pids); + + // 2. 收集数据库 salt 映射 + eprintln!("扫描数据库文件..."); + let db_salts = collect_db_salts(db_dir); + eprintln!("找到 {} 个加密数据库", db_salts.len()); + + // 3. 扫描所有进程内存,收集所有候选密钥 + let mut all_raw_keys: Vec<(String, String)> = Vec::new(); + for pid in &pids { + eprintln!("扫描进程 {} 内存...", pid); + match scan_single_process(*pid) { + Ok(keys) => { + eprintln!(" 进程 {}: 找到 {} 个候选密钥", pid, keys.len()); + all_raw_keys.extend(keys); + } + Err(e) => { + eprintln!(" 进程 {}: 跳过 ({})", pid, e); + } + } + } + + // 去重(跨进程可能找到相同密钥) + all_raw_keys.sort_by(|a, b| a.0.cmp(&b.0).then_with(|| a.1.cmp(&b.1))); + all_raw_keys.dedup(); + + eprintln!("候选密钥去重后: {} 个", all_raw_keys.len()); + + // 4. 将密钥与数据库 salt 匹配 + let mut entries = Vec::new(); + for (key_hex, salt_hex) in &all_raw_keys { + for (db_salt, db_name) in &db_salts { + if salt_hex == db_salt { + entries.push(KeyEntry { + db_name: db_name.clone(), + enc_key: key_hex.clone(), + salt: salt_hex.clone(), + }); + break; + } + } + } + + eprintln!("匹配到 {}/{} 个密钥", entries.len(), all_raw_keys.len()); + Ok(entries) +} - // 2. 获取 task port - // SAFETY: task_for_pid 是标准 Mach API,参数合法 - let task = unsafe { +/// 获取单个进程的 task port +fn get_task_port(pid: libc::pid_t) -> Result { + unsafe { let mut task: mach_port_t = 0; let kr = task_for_pid(mach_task_self(), pid, &mut task); if kr != KERN_SUCCESS { @@ -127,37 +184,15 @@ pub fn scan_keys(db_dir: &Path) -> Result> { kr ); } - task - }; - eprintln!("Got task port: {}", task); - - // 3. 收集数据库 salt 映射 - eprintln!("扫描数据库文件..."); - let db_salts = collect_db_salts(db_dir); - eprintln!("找到 {} 个加密数据库", db_salts.len()); - - // 4. 扫描进程内存 - eprintln!("扫描进程内存寻找密钥..."); - let raw_keys = scan_memory(task)?; - eprintln!("找到 {} 个候选密钥", raw_keys.len()); - - // 5. 将密钥与数据库 salt 匹配 - let mut entries = Vec::new(); - for (key_hex, salt_hex) in &raw_keys { - for (db_salt, db_name) in &db_salts { - if salt_hex == db_salt { - entries.push(KeyEntry { - db_name: db_name.clone(), - enc_key: key_hex.clone(), - salt: salt_hex.clone(), - }); - break; - } - } + Ok(task) } +} - eprintln!("匹配到 {}/{} 个密钥", entries.len(), raw_keys.len()); - Ok(entries) +/// 扫描单个进程的内存 +fn scan_single_process(pid: libc::pid_t) -> Result> { + let task = get_task_port(pid)?; + eprintln!(" Got task port: {}", task); + scan_memory(task) } /// 扫描进程内存,返回 (key_hex, salt_hex) 列表