mirror of
https://github.com/jackwener/wx-cli.git
synced 2026-10-08 18:55:46 +00:00
fix(daemon,scanner,crypto): harden lifecycle, widen Windows page scan, fix SQLCipher short read (#54)
- daemon: write pid file only after IPC bound; clean sock+pid on normal return - transport: PidFile JSON metadata + identity verification (ps/QueryFullProcessImageNameW); SIGTERM with poll-timeout; backward-compat read for plain-text pid - daemon_cmd: status/stop work with both new JSON and legacy plain-text pid file - config: cwd → exe_dir → ~/.wx-cli config precedence matches `wx init` write order; Windows DB auto-detect picks newest by latest mtime - crypto: full_decrypt uses read_exact for intermediate pages, zero-pads only the final partial page; tests cover short-chunk reads and early EOF - scanner/windows: page protect check covers PAGE_READWRITE / PAGE_WRITECOPY / PAGE_EXECUTE_*WRITE* with modifier-bit stripping Cross-reviewed by @wx-cli-coder. Windows verified via `cargo check --target x86_64-pc-windows-gnu` (no Windows runtime test).
This commit is contained in:
+32
-27
@@ -5,19 +5,19 @@
|
||||
/// - OpenProcess: 获取进程句柄(需要 PROCESS_VM_READ | PROCESS_QUERY_INFORMATION)
|
||||
/// - VirtualQueryEx: 枚举内存区域
|
||||
/// - ReadProcessMemory: 读取内存内容
|
||||
use anyhow::{bail, Context, Result};
|
||||
use anyhow::{Context, Result};
|
||||
use std::path::Path;
|
||||
use windows::Win32::Foundation::{CloseHandle, HANDLE};
|
||||
use windows::Win32::System::Diagnostics::Debug::ReadProcessMemory;
|
||||
use windows::Win32::System::Diagnostics::ToolHelp::{
|
||||
CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS,
|
||||
};
|
||||
use windows::Win32::System::Memory::{
|
||||
VirtualQueryEx, MEMORY_BASIC_INFORMATION, MEM_COMMIT, PAGE_READWRITE,
|
||||
VirtualQueryEx, MEMORY_BASIC_INFORMATION, MEM_COMMIT, PAGE_EXECUTE_READWRITE,
|
||||
PAGE_EXECUTE_WRITECOPY, PAGE_GUARD, PAGE_NOCACHE, PAGE_READWRITE, PAGE_WRITECOMBINE,
|
||||
PAGE_WRITECOPY,
|
||||
};
|
||||
use windows::Win32::System::Threading::{
|
||||
OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ,
|
||||
};
|
||||
use windows::Win32::System::Diagnostics::Debug::ReadProcessMemory;
|
||||
use windows::Win32::System::Threading::{OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ};
|
||||
|
||||
use super::{collect_db_salts, KeyEntry};
|
||||
|
||||
@@ -27,9 +27,7 @@ const CHUNK_SIZE: usize = 2 * 1024 * 1024;
|
||||
/// 查找 Weixin.exe 进程 PID
|
||||
fn find_wechat_pid() -> Option<u32> {
|
||||
// SAFETY: CreateToolhelp32Snapshot 标准 Windows API
|
||||
let snap = unsafe {
|
||||
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0).ok()?
|
||||
};
|
||||
let snap = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0).ok()? };
|
||||
|
||||
let mut entry = PROCESSENTRY32 {
|
||||
dwSize: std::mem::size_of::<PROCESSENTRY32>() as u32,
|
||||
@@ -43,8 +41,8 @@ fn find_wechat_pid() -> Option<u32> {
|
||||
return None;
|
||||
}
|
||||
loop {
|
||||
let name = std::ffi::CStr::from_ptr(entry.szExeFile.as_ptr() as *const i8)
|
||||
.to_string_lossy();
|
||||
let name =
|
||||
std::ffi::CStr::from_ptr(entry.szExeFile.as_ptr() as *const i8).to_string_lossy();
|
||||
if name.eq_ignore_ascii_case("Weixin.exe") {
|
||||
let pid = entry.th32ProcessID;
|
||||
let _ = CloseHandle(snap);
|
||||
@@ -60,8 +58,7 @@ fn find_wechat_pid() -> Option<u32> {
|
||||
}
|
||||
|
||||
pub fn scan_keys(db_dir: &Path) -> Result<Vec<KeyEntry>> {
|
||||
let pid = find_wechat_pid()
|
||||
.context("找不到 Weixin.exe 进程,请确认微信正在运行")?;
|
||||
let pid = find_wechat_pid().context("找不到 Weixin.exe 进程,请确认微信正在运行")?;
|
||||
eprintln!("WeChat PID: {}", pid);
|
||||
|
||||
// SAFETY: OpenProcess 请求读取权限
|
||||
@@ -78,7 +75,9 @@ pub fn scan_keys(db_dir: &Path) -> Result<Vec<KeyEntry>> {
|
||||
eprintln!("找到 {} 个候选密钥", raw_keys.len());
|
||||
|
||||
// SAFETY: 关闭进程句柄
|
||||
unsafe { let _ = CloseHandle(process); }
|
||||
unsafe {
|
||||
let _ = CloseHandle(process);
|
||||
}
|
||||
|
||||
let mut entries = Vec::new();
|
||||
for (key_hex, salt_hex) in &raw_keys {
|
||||
@@ -119,8 +118,9 @@ fn scan_memory(process: HANDLE) -> Result<Vec<(String, String)>> {
|
||||
let region_size = mbi.RegionSize;
|
||||
let base = mbi.BaseAddress as usize;
|
||||
|
||||
// 只扫描已提交的可读写页面
|
||||
if mbi.State == MEM_COMMIT && mbi.Protect == PAGE_READWRITE {
|
||||
// 只扫描已提交的可读可写页面。Windows 的保护位可能带 modifier bits,
|
||||
// 也可能是 WRITECOPY / EXECUTE_READWRITE 这种同样可读可写的保护类型。
|
||||
if mbi.State == MEM_COMMIT && is_writable_readable_page(mbi.Protect.0) {
|
||||
scan_region(process, base, region_size, &mut results);
|
||||
}
|
||||
|
||||
@@ -133,12 +133,18 @@ fn scan_memory(process: HANDLE) -> Result<Vec<(String, String)>> {
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
fn scan_region(
|
||||
process: HANDLE,
|
||||
base: usize,
|
||||
size: usize,
|
||||
results: &mut Vec<(String, String)>,
|
||||
) {
|
||||
fn is_writable_readable_page(protect: u32) -> bool {
|
||||
let base = protect & !(PAGE_GUARD.0 | PAGE_NOCACHE.0 | PAGE_WRITECOMBINE.0);
|
||||
matches!(
|
||||
base,
|
||||
x if x == PAGE_READWRITE.0
|
||||
|| x == PAGE_WRITECOPY.0
|
||||
|| x == PAGE_EXECUTE_READWRITE.0
|
||||
|| x == PAGE_EXECUTE_WRITECOPY.0
|
||||
)
|
||||
}
|
||||
|
||||
fn scan_region(process: HANDLE, base: usize, size: usize, results: &mut Vec<(String, String)>) {
|
||||
let overlap = HEX_PATTERN_LEN + 3;
|
||||
let mut offset = 0usize;
|
||||
|
||||
@@ -159,7 +165,8 @@ fn scan_region(
|
||||
buf.as_mut_ptr() as *mut _,
|
||||
chunk_size,
|
||||
Some(&mut bytes_read),
|
||||
).is_ok()
|
||||
)
|
||||
.is_ok()
|
||||
};
|
||||
|
||||
if ok && bytes_read > 0 {
|
||||
@@ -203,10 +210,8 @@ fn search_pattern(buf: &[u8], results: &mut Vec<(String, String)>) {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
let key_hex = String::from_utf8_lossy(&buf[hex_start..hex_start + 64])
|
||||
.to_lowercase();
|
||||
let salt_hex = String::from_utf8_lossy(&buf[hex_start + 64..hex_start + 96])
|
||||
.to_lowercase();
|
||||
let key_hex = String::from_utf8_lossy(&buf[hex_start..hex_start + 64]).to_lowercase();
|
||||
let salt_hex = String::from_utf8_lossy(&buf[hex_start + 64..hex_start + 96]).to_lowercase();
|
||||
let is_dup = results.iter().any(|(k, s)| k == &key_hex && s == &salt_hex);
|
||||
if !is_dup {
|
||||
results.push((key_hex, salt_hex));
|
||||
|
||||
Reference in New Issue
Block a user