fix: accept WeChat 4.1.7 and newer

This commit is contained in:
pandorafuture
2026-08-26 06:20:16 +00:00
parent 7bed9e767a
commit 6c7fdda7db
5 changed files with 55 additions and 10 deletions
+4
View File
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
### Changed
- Accept WeChat 4.1.7 and newer for key extraction instead of maintaining a fixed version-prefix allowlist.
## [0.7.4] - 2026-07-22
### Features
+4
View File
@@ -4,6 +4,10 @@
## [未发布]
### 变更
- 密钥提取改为支持 WeChat 4.1.7 及以上版本,不再维护固定版本前缀白名单。
## [0.7.4] - 2026-07-22
### 功能
+2 -2
View File
@@ -38,7 +38,7 @@ wx-cli 提供了最关键的两样东西:完整的历史上下文,以及持
## 支持范围
- **平台**macOSarm64 / Apple Silicon
- **WeChat 版本**4.1.7.x / 4.1.8.x
- **WeChat 版本**4.1.7 及以上
## 前置条件
@@ -265,4 +265,4 @@ wx-cli/
- `wx-cli key list` 确认密钥正确
- `wx-cli info <db>` 检查文件是否为加密状态
- 确认 WeChat 版本 4.1.7.x / 4.1.8.x 范围内
- 确认 WeChat 版本不低于 4.1.7
+3 -1
View File
@@ -20,7 +20,9 @@ pub enum KeychainError {
#[error("WeChat is not running")]
WeChatNotRunning,
#[error("WeChat version {version} is not supported for key extraction (requires 4.1.7.x or 4.1.8.x)")]
#[error(
"WeChat version {version} is not supported for key extraction (requires 4.1.7 or newer)"
)]
UnsupportedVersion { version: String },
#[error("could not detect WeChat account directory")]
+42 -7
View File
@@ -3,17 +3,30 @@ use std::process::Command;
use crate::error::KeychainError;
pub const SUPPORTED_VERSION: &str = "4.1.8.21";
pub const SUPPORTED_VERSION: &str = "4.1.7+";
/// Version prefixes accepted for LLDB key extraction.
/// Encryption params (PBKDF2-HMAC-SHA512, 256K iterations) are identical across these versions.
const EXTRACTION_VERSION_PREFIXES: &[&str] = &["4.1.7", "4.1.8"];
/// Minimum version accepted for key extraction.
/// Encryption params (PBKDF2-HMAC-SHA512, 256K iterations) are identical from this version onward.
const MIN_EXTRACTION_VERSION: (u64, u64, u64) = (4, 1, 7);
/// Check whether a version string is compatible with our LLDB key extraction.
fn is_extraction_compatible(version: &str) -> bool {
EXTRACTION_VERSION_PREFIXES
.iter()
.any(|prefix| version == *prefix || version.starts_with(&format!("{prefix}.")))
let mut components = version.split('.');
let Some(major) = components.next().and_then(|value| value.parse().ok()) else {
return false;
};
let Some(minor) = components.next().and_then(|value| value.parse().ok()) else {
return false;
};
let Some(patch) = components.next().and_then(|value| value.parse().ok()) else {
return false;
};
if components.any(|value| value.is_empty() || value.parse::<u64>().is_err()) {
return false;
}
(major, minor, patch) >= MIN_EXTRACTION_VERSION
}
#[derive(Debug, Clone)]
@@ -354,6 +367,28 @@ fn tiebreak_by_wal_mtime<'a>(accounts: &[&'a AccountDirInfo]) -> Option<&'a Acco
mod tests {
use super::*;
#[test]
fn test_extraction_version_accepts_4_1_7_and_newer() {
for version in [
"4.1.7", "4.1.7.31", "4.1.8", "4.1.9", "4.1.13", "4.2.0", "5.0.0",
] {
assert!(
is_extraction_compatible(version),
"expected {version} to be supported"
);
}
}
#[test]
fn test_extraction_version_rejects_older_and_invalid_versions() {
for version in ["4.1.6.99", "4.0.99", "3.9.9", "4.1", "4.1.beta", ""] {
assert!(
!is_extraction_compatible(version),
"expected {version:?} to be rejected"
);
}
}
#[test]
fn test_extract_base_wxid_with_suffix() {
assert_eq!(