8bb2d85d8c9ee82860e8baaa09a7e6e24978d63b
2 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
acd44376ba |
fix: find_image_key 三个 fallback 入口路径展开 + 方案2 hint (#71)
* fix: find_image_key 三个 fallback CLI 入口对齐 config.load_config 路径展开 PR #63 给 config.load_config() 加了 expanduser + expandvars, 但 find_image_key.py / find_image_key_macos.py / find_image_key_monitor.py 三个 CLI 入口的 main() 为了让单测注入隔离 config (find_image_key_macos.py docstring 明文写着), 走 raw json.load(config_path), 绕开了 load_config 那层路径展开. 用户 config.json 里写 "db_dir": "~/Documents/..." 或 "$HOME/Documents/..." 时, 下游拼出的 attach_dir 仍带 ~ / $HOME 字面字符, glob *_t.dat 扫不到 → find_image_key.py / monitor.py 报 "No V2 .dat files found", macOS 文件 dispatcher 还会误报"请先在微信中查看 1-2 张图片让微信生成 V2 .dat 文件", 但磁盘上 attach 已经塞满了 .dat. 三个文件各加 1 行 expanduser(expandvars(...)), 与 PR #63 / config.py:213 对齐, 不动 main() 既有的 raw json.load(为保留单测注入口子). * fix(find_image_key_macos): 方案2 V2 _t.dat 样本不足时补一行下一步指引 dispatcher 层 (找不到 V2 模板分支) 已有"请先在微信中查看 1-2 张图片让微信 生成 V2 .dat 文件"指引, 但 _find_via_bruteforce 子路径在 V2 _t.dat 样本 < 3 时只 print "样本不足 (需 >= 3 个), 无法投票反推 xor_key", 用户不知该做什么. 加一行等价 hint, 与 dispatcher 层 UX 风格保持一致. |
||
|
|
49356e1692 |
feat: macOS 图片 AES key 从磁盘 kvcomm 缓存派生(解决 #23) (#60)
* feat: macOS 图片 AES key 从磁盘 kvcomm 缓存派生(issue #23)
macOS 用户长期无法用 C 版 find_image_key_macos 从微信进程内存提取
V2 图片密钥(issue #23 报告 197K 候选全部失败)。新增
find_image_key_macos.py 走完全不同的路径:从磁盘 kvcomm 缓存
文件名派生密钥,无需扫描内存、无需 root、无需重签名。
派生算法
--------
- 扫 ~/.../app_data/net/kvcomm/key_<code>_*.statistic 文件名
- 对每个 (code, wxid) 候选:
xor_key = code & 0xFF
aes_key = MD5(str(code) + cleaned_wxid).hex()[:16] # ASCII 字符串
- 用 V2 _t.dat 文件 [0xF:0x1F] 16 字节做 AES-128-ECB 模板验证:
解出来必须是图像 magic(JPEG / PNG / GIF / WebP / wxgf)
- 为防短 magic 偶然命中,要求多个不同模板都通过验证才算成功
- 命中后写回 config.json 的 image_aes_key / image_xor_key,
monitor_web.py 自动加载
致谢
----
派生算法源自 @hicccc77 在 issue #23 的评论;参考实现见其 WeFlow
项目 (CC BY-NC-SA 4.0)。本模块是独立的 Python clean-room 实现,
未复制其 TypeScript 源码;函数边界与变量命名沿用算法的自然结构
(regex 模式 / MD5 调用顺序 / magic 字节表等不可避免地相同)。
健壮性细节
----------
- 多候选 kvcomm 路径:枚举 5 个不同的 macOS 微信版本路径布局
- 多模板交叉验证:默认收集 3 个不同密文,全部通过才算命中
- 已有 image_aes_key 仍有效时短路返回,不重写 config
- 原子写 config.json:tmp + os.replace + finally 清理 .tmp
- 多 wxid 候选:同时试 raw 和归一化后的 wxid(A_Hare_626a → A_Hare)
- print(flush=True) 逐次显式(与 find_image_key.py 风格一致)
测试
----
新增 tests/test_find_image_key_macos.py,53 个测试覆盖:
派生算法 / wxid 归一化 / kvcomm 路径推算(含多候选)/ 模板收集
(去重 / 子目录 / max_files 边界)/ AES 验证(5 种 magic / 短输入
/ 空 key)/ 多模板交叉验证 / 端到端集成(命中 / 各种失败分支)/
原子写 / main 短路(已有有效 key 不重写 / 已有错 key 落到派生)。
全部通过:python -m unittest discover tests → 88/88。
兼容性
------
- 无新增依赖(pycryptodome 已在 requirements.txt)
- 不改任何现有 Python 文件,零回归风险
- 现有 Windows / Linux 路径 (find_image_key.py / find_image_key_monitor.py) 不受影响
* feat: macOS 图片 AES key 加方案2 fallback (issue #68 思路)
PR #60 的方案1 (kvcomm 缓存派生) 在 kvcomm 缺失 / 多账号歧义 / 首次
启动等场景下会失败。@H3CoF6 在 issue #68 提出关键洞察:
wxid 目录后 4 位 hex == md5(str(uin))[:4]
意味着不需要 kvcomm,可以从 wxid 目录名 + 任意 V2 .dat 反推 uin。
本 commit 在保留 PR #60 方案1 不变的前提下,加方案2 作为 dispatcher
fallback。
方案2 算法
----------
1. 从 db_dir 提 wxid 后 4 位 hex 作为 md5 前缀目标
2. 扫多个 V2 .dat 末字节投票反推 xor_key (假设 JPG EOI 0xD9,
默认至少 3 个样本投票)
3. 枚举 0~2^32 中 (uin & 0xff == xor_key) 的 2^24 个候选,
md5(str(uin))[:4] 匹配 wxid 后缀 → 得 ~256 个 uin 候选
4. 对每个候选算 aes_key, 用 PR #60 的 verify_aes_key_against_all
做 AES 模板交叉验证, 唯一定位 uin
实现
----
- find_image_key_macos 重构为 dispatcher: 先方案1 (kvcomm),
失败 fallback 方案2 (候选搜索); 模板收集移到 dispatcher 共享
- 新增 helper: extract_wxid_parts, derive_xor_key_from_v2_dat,
bruteforce_uin_candidates
- 模块顶部 docstring 加方案2 算法说明 + @H3CoF6 致谢
(保留 PR #60 对 @hicccc77 的方案1 致谢)
clean-room 声明
---------------
方案2 按 issue #68 的算法描述独立实现,未引用 @H3CoF6 任何代码。
方案1 仍沿用 PR #60 实现 (其 clean-room 声明对 @hicccc77 / WeFlow
保持不变)。
健壮性细节
----------
- xor_key 反推默认 min_samples=3, 样本不足直接放弃方案2 (避免
1-2 个样本时一旦撞到非 JPG 就 lock 错 xor_key)
- wxid 后缀正则收紧为 [0-9a-fA-F]{4} (md5 hex), 非 hex 后缀直接
返回 None 而非误导用户跑空候选搜索
- 投票分歧时打印 warning, 但仍试取多数 (兼容 attach 含少量非 JPG)
- 删除重构后未用的 import glob; Counter 统一在模块顶部 import
测试
----
新增 17 个测试 (53 → 70), 全部 7.4s 内通过:
- ExtractWxidPartsTests (5)
- DeriveXorKeyFromV2DatTests (7, 含新增 below_min_samples 边界)
- BruteforceUinCandidatesTests (1, 真跑全空间金标准验证)
- FindViaBruteforceTests (3)
- DispatcherFallbackTests (1, mock 加速)
顺手修复 2 个 pre-existing 测试 fail
------------------------------------
test_account_with_4char_alnum_suffix_stripped 与
test_returns_raw_and_normalized_when_different 用 6-char 后缀
your_wxid_a1b2c3, 但 normalize_wxid 只去 4-char 后缀 (匹配真实
macOS 路径) → 测试期望与代码不一致, 长期 fail。统一改用 4-char
后缀让测试与 macOS 现实对齐。
兼容性
------
- API 不变: find_image_key_macos(db_dir) 签名 / 返回值不变
- 现有 53 个测试全部仍通过 (含 happy path / 各种返回 None 分支 /
main 短路 / 原子写)
- 真实数据验证: 在本地 macOS 微信 4.x 上方案2 端到端跑通, 结果
与方案1 完全一致
* fix: replace test fixture with synthetic uin/wxid (privacy hardening)
PR #60 测试 fixture 与 docstring 示例之前用了真实 uin (8 位十进制)
作为 golden value,并在 docstring 里把 wxid 后缀作为示例展示。虽然
单独的 uin/suffix 不直接 unlock 任何资产 (需要配合真实 wxid + 物理
访问加密文件),但行业最佳实践 (yt-dlp / openssl / Linux kernel test
fixture) 都明确要求用合成确定性值, 不绑定任何真实账号。
合成方案
--------
- uin: 12345678 (8 位, 一目了然 placeholder)
- suffix: md5("12345678")[:4] = "25d5" (派生, self-consistent)
- wxid_full 示例: your_wxid_25d5
- wxid_norm 示例: your_wxid
- aes_key_test_value: a0c093edddc98490 = md5("12345678your_wxid")[:16]
- xor_key: 0x4E (= 12345678 & 0xFF)
改动范围
--------
- tests/test_find_image_key_macos.py: 全部 fixture 改用合成值,
bruteforce 测试的 xor 也对应更新 (0x7F → 0x4E)
- find_image_key_macos.py:260 docstring 示例: 真实 wxid 字符串
替换为 placeholder
- 长 kvcomm 缓存文件名 fixture 同步合成 (避免暴露真实时间戳 / 内部 ID)
测试
----
70/70 仍通过 (7.1s), 合成 fixture self-consistent。
非范围 (历史 commit
|