pull/82/merge
Yanmi3560 2026-05-19 16:56:20 +08:00 committed by GitHub
commit 34d84d16c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 74 additions and 39 deletions

View File

@ -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<libc::pid_t> {
// 使用 pgrep -x WeChat 查找(与 C 版本一致)
/// 查找所有 WeChat 相关进程的 PID
/// 微信 3.8+ 将数据库操作分离到 WeChatAppEx Helper 进程,
/// 需要扫描主 WeChat 进程和所有 WeChatAppEx 进程才能找到密钥。
fn find_wechat_pids() -> Option<Vec<libc::pid_t>> {
let output = std::process::Command::new("pgrep")
.args(["-x", "WeChat"])
.output()
@ -88,7 +92,15 @@ fn find_wechat_pid() -> Option<libc::pid_t> {
return None;
}
let s = String::from_utf8_lossy(&output.stdout);
s.trim().parse().ok()
let pids: Vec<libc::pid_t> = 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<Vec<KeyEntry>> {
// 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. 获取 task port
// SAFETY: task_for_pid 是标准 Mach API参数合法
let task = unsafe {
// 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)
}
/// 获取单个进程的 task port
fn get_task_port(pid: libc::pid_t) -> Result<mach_port_t> {
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<Vec<KeyEntry>> {
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<Vec<(String, String)>> {
let task = get_task_port(pid)?;
eprintln!(" Got task port: {}", task);
scan_memory(task)
}
/// 扫描进程内存,返回 (key_hex, salt_hex) 列表