Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 74 additions & 39 deletions src/scanner/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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()
Expand All @@ -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 十六进制字符
Expand All @@ -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. 收集数据库 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<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 {
Expand All @@ -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) 列表
Expand Down