diff --git a/src/daemon/cache.rs b/src/daemon/cache.rs index 561df51..d0086da 100644 --- a/src/daemon/cache.rs +++ b/src/daemon/cache.rs @@ -200,11 +200,6 @@ impl DbCache { } pub async fn get_with_mode(&self, rel_key: &str) -> Result> { - let enc_key_hex = match self.all_keys.get(rel_key) { - Some(k) => k.clone(), - None => return Ok(None), - }; - let db_path = self.db_dir.join( rel_key .replace('\\', std::path::MAIN_SEPARATOR_STR) @@ -214,6 +209,21 @@ impl DbCache { return Ok(None); } + let enc_key_hex = match self.all_keys.get(rel_key) { + Some(k) => k.clone(), + None => { + // Fallback: look up by DB salt for keys saved as "_by_salt/" + // when the DB file wasn't accessible during `wx init`. + let salt = crate::scanner::read_db_salt(&db_path); + match salt.and_then(|s| { + self.all_keys.get(&format!("_by_salt/{}", s)).cloned() + }) { + Some(k) => k, + None => return Ok(None), + } + } + }; + let wal_path = wal_path_for(&db_path); let db_mt = mtime_nanos(&db_path); let wal_mt = if wal_path.exists() { diff --git a/src/scanner/linux.rs b/src/scanner/linux.rs index d6f4ee9..8c55d63 100644 --- a/src/scanner/linux.rs +++ b/src/scanner/linux.rs @@ -4,6 +4,7 @@ /// 通过 /proc//mem 读取内存内容, /// 搜索 x'<64hex><32hex>' 格式的 SQLCipher 密钥 use anyhow::{Context, Result}; +use std::collections::HashMap; use std::io::{Read, Seek, SeekFrom}; use std::path::Path; @@ -89,21 +90,33 @@ pub fn scan_keys(db_dir: &Path) -> Result> { } eprintln!("找到 {} 个候选密钥", raw_keys.len()); + // 无法匹配 DB 文件的密钥用 _by_salt/ 保存,daemon 会做 salt 回退查找, + // 确保所有在内存中找到的密钥都不会因 DB 文件权限或路径问题而丢失。 + let db_salt_map: HashMap<&str, &str> = db_salts + .iter() + .map(|(salt, name)| (salt.as_str(), name.as_str())) + .collect(); + 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; - } - } + let db_name = db_salt_map + .get(salt_hex.as_str()) + .map(|s| s.to_string()) + .unwrap_or_else(|| format!("_by_salt/{}", salt_hex)); + entries.push(KeyEntry { + db_name, + enc_key: key_hex.clone(), + salt: salt_hex.clone(), + }); } - eprintln!("匹配到 {}/{} 个密钥", entries.len(), raw_keys.len()); + let matched = entries.iter().filter(|e| !e.db_name.starts_with("_by_salt/")).count(); + eprintln!( + "匹配到 {}/{} 个密钥(另有 {} 个按 salt 保存)", + matched, + raw_keys.len(), + entries.len() - matched + ); Ok(entries) } diff --git a/src/scanner/macos.rs b/src/scanner/macos.rs index c22d3bb..6cb9f6b 100644 --- a/src/scanner/macos.rs +++ b/src/scanner/macos.rs @@ -10,6 +10,7 @@ /// 2. WeChat 需要进行 ad-hoc 签名 /// 3. 在内存中搜索 x'<64hex><32hex>' 格式的 SQLCipher 密钥 use anyhow::{bail, Context, Result}; +use std::collections::HashMap; use std::path::Path; use super::{collect_db_salts, KeyEntry}; @@ -141,22 +142,33 @@ pub fn scan_keys(db_dir: &Path) -> Result> { let raw_keys = scan_memory(task)?; eprintln!("找到 {} 个候选密钥", raw_keys.len()); - // 5. 将密钥与数据库 salt 匹配 + // 5. 将密钥与数据库 salt 匹配,无法匹配的用 _by_salt/ 保存, + // 确保所有在内存中找到的密钥都不会因 DB 文件权限或路径问题而丢失。 + let db_salt_map: HashMap<&str, &str> = db_salts + .iter() + .map(|(salt, name)| (salt.as_str(), name.as_str())) + .collect(); + 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; - } - } + let db_name = db_salt_map + .get(salt_hex.as_str()) + .map(|s| s.to_string()) + .unwrap_or_else(|| format!("_by_salt/{}", salt_hex)); + entries.push(KeyEntry { + db_name, + enc_key: key_hex.clone(), + salt: salt_hex.clone(), + }); } - eprintln!("匹配到 {}/{} 个密钥", entries.len(), raw_keys.len()); + let matched = entries.iter().filter(|e| !e.db_name.starts_with("_by_salt/")).count(); + eprintln!( + "匹配到 {}/{} 个密钥(另有 {} 个按 salt 保存)", + matched, + raw_keys.len(), + entries.len() - matched + ); Ok(entries) } diff --git a/src/scanner/windows.rs b/src/scanner/windows.rs index 391ba33..9f5198c 100644 --- a/src/scanner/windows.rs +++ b/src/scanner/windows.rs @@ -6,6 +6,7 @@ /// - VirtualQueryEx: 枚举内存区域 /// - ReadProcessMemory: 读取内存内容 use anyhow::{Context, Result}; +use std::collections::HashMap; use std::path::Path; use windows::Win32::Foundation::{CloseHandle, HANDLE}; use windows::Win32::System::Diagnostics::Debug::ReadProcessMemory; @@ -79,20 +80,33 @@ pub fn scan_keys(db_dir: &Path) -> Result> { let _ = CloseHandle(process); } + // 无法匹配 DB 文件的密钥用 _by_salt/ 保存,daemon 会做 salt 回退查找, + // 确保所有在内存中找到的密钥都不会因 DB 文件权限或路径问题而丢失。 + let db_salt_map: HashMap<&str, &str> = db_salts + .iter() + .map(|(salt, name)| (salt.as_str(), name.as_str())) + .collect(); + 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; - } - } + let db_name = db_salt_map + .get(salt_hex.as_str()) + .map(|s| s.to_string()) + .unwrap_or_else(|| format!("_by_salt/{}", salt_hex)); + entries.push(KeyEntry { + db_name, + enc_key: key_hex.clone(), + salt: salt_hex.clone(), + }); } - eprintln!("匹配到 {}/{} 个密钥", entries.len(), raw_keys.len()); + + let matched = entries.iter().filter(|e| !e.db_name.starts_with("_by_salt/")).count(); + eprintln!( + "匹配到 {}/{} 个密钥(另有 {} 个按 salt 保存)", + matched, + raw_keys.len(), + entries.len() - matched + ); Ok(entries) }