Initial import

This commit is contained in:
2026-09-04 21:46:38 +08:00
commit 5e3aa97644
36 changed files with 4608 additions and 0 deletions
+646
View File
@@ -0,0 +1,646 @@
# WxAgent C# 技术开发计划
> 版本:0.2
> 日期:2026-09-03
> 目标:使用 C# 独立实现 wxautox4 商业版的业务功能;只兼容功能,不兼容 Python API。第一阶段不实现 Windows Service 和正式控制面。
## 1. 结论
可以采用以下开发方式:
- Linux 作为主要开发、代码审查、构建和纯逻辑测试环境。
- Linux 交叉编译 `win-x64` 发布包。
- Windows 真机运行 Desktop Agent,在已登录且未锁定的用户会话中连接微信并执行 UI Automation 验证。
- 当前项目的 Windows MCP 已能识别微信窗口及关键 UIA 节点,可作为第一台真机验证环境。
限制:
- Linux 只能编译 Windows UIA 代码,不能执行 FlaUI、COM、剪贴板、窗口和微信操作测试。
- Windows 真机必须保持用户登录、桌面可交互、微信已登录。
- 暂不使用 NativeAOT;普通 self-contained/single-file 发布可从 Linux 生成。安装包、代码签名和 UIA 真机测试放在 Windows 完成。
## 2. 范围
### 2.1 包含
- 微信进程、窗口和登录状态检测
- 会话列表、会话切换和子窗口
- 消息发送、读取、解析、监听和去重
- 文本、图片、文件、语音、链接等消息类型
- 联系人、好友申请和好友信息
- 群聊创建、成员和群信息管理
- 历史消息、附件下载、OCR、语音转文字
- 朋友圈读取、发布、点赞和评论
- 本地 Desktop Agent、CLI、日志和诊断工具
- 只读扫描 `Weixin.exe` 内存,校验 SQLCipher 密钥并读取本地微信数据库
- 面向未来 Web/UI 控制面的应用层 API
### 2.2 暂不包含
- Windows Service
- 正式 Web UI 或桌面 UI
- SaaS、多租户和远程集群控制
- 商业授权、计费和自动升级
- 绕过登录、验证码、风控或平台限制
- 微信协议、数据库写入、DLL 注入或进程内存修改
只读数据库例外严格限定为:以最小进程权限读取 `Weixin.exe` 内存中的 WCDB 候选密钥,以目标数据库 page 1 HMAC-SHA512 验证后,使用 SQLCipher 4 只读连接查询本机数据库。禁止输出未验证候选、记录完整密钥、修改数据库或将该能力扩展为协议/登录绕过。
## 3. 最小项目结构
第一阶段使用以下结构,不提前拆分微服务:
```text
WxAgent.sln
├── src/
│ ├── WxAgent.Core/ # net8.0,模型、解析、去重、状态机、业务用例
│ ├── WxAgent.Windows/ # net8.0-windowsFlaUI/UIA/Win32 实现
│ └── WxAgent.Host/ # net8.0-windowsDesktop Agent 和 CLI
└── tests/
└── WxAgent.Core.Tests/ # net8.0,可在 Linux 执行
```
开始真机自动化后再增加:
```text
tests/WxAgent.Windows.Tests/ # 仅在 Windows 真机执行
```
这种拆分的目的不是建立通用插件框架,而是让纯业务逻辑能够在 Linux 运行测试,同时隔离 Windows 专用引用。
## 4. 技术选型
| 能力 | 选型 | 说明 |
| --- | --- | --- |
| Runtime | .NET 8 LTS,后续可升级 .NET 10 | 优先使用成熟工具链 |
| UI Automation | FlaUI.UIA3 | 首选封装,缺失能力再直接调用 UIA COM |
| Win32 | Microsoft.Windows.CsWin32 | 按需生成 P/Invoke,不手写大批声明 |
| 并发 | `Channel<T>``Task``IAsyncEnumerable<T>` | 所有 UI 写操作进入单队列 |
| 配置 | `Microsoft.Extensions.Configuration` | JSON + 环境变量 |
| 日志 | `Microsoft.Extensions.Logging` | 暂不额外引入日志框架 |
| 序列化 | `System.Text.Json` | UI 快照、命令和事件 |
| 持久化 | SQLite | 仅保存消息游标、短期去重和任务状态 |
| 微信数据库只读 | Microsoft.Data.Sqlite.Core + SQLCipher bundle | raw key、只读模式、`query_only`,不写库 |
| 测试 | xUnit | Linux 纯逻辑测试 + Windows 真机测试 |
| 发布 | self-contained `win-x64` | 第一阶段只支持 Windows x64 |
除非验证证明 FlaUI 无法满足需求,否则不要从零封装完整 UI Automation COM API。
## 5. Linux 编译、Windows 验证方案
### 5.1 Windows 项目配置
`WxAgent.Windows.csproj``WxAgent.Host.csproj` 使用:
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PlatformTarget>x64</PlatformTarget>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PublishTrimmed>false</PublishTrimmed>
</PropertyGroup>
</Project>
```
`EnableWindowsTargeting` 允许 Linux/macOS 恢复和编译 Windows Targeting Pack。`PublishTrimmed` 暂时关闭,避免 FlaUI、COM 或反射代码被错误裁剪。
### 5.2 Linux 构建命令
```bash
dotnet restore WxAgent.sln -p:EnableWindowsTargeting=true
dotnet test tests/WxAgent.Core.Tests -c Release
dotnet build WxAgent.sln -c Release -p:EnableWindowsTargeting=true
dotnet publish src/WxAgent.Host \
-c Release \
-r win-x64 \
--self-contained true \
-p:EnableWindowsTargeting=true \
-p:PublishSingleFile=true \
-p:PublishTrimmed=false
```
发布产物:
```text
src/WxAgent.Host/bin/Release/net8.0-windows10.0.19041.0/win-x64/publish/
```
### 5.3 Windows 真机验证流程
```text
Linux build/test/publish
复制 publish 目录到 Windows
Windows 用户登录并启动微信
运行 WxAgent.Host doctor
运行 smoke 测试
保存 UI 树、日志和测试报告
```
优先通过 Windows MCP 操作和检查微信;MCP 不足时,再通过 SSH/PowerShell 上传、启动和收集结果。
建议 CLI
```powershell
WxAgent.Host doctor
WxAgent.Host inspect-ui --output artifacts/ui-tree.json
WxAgent.Host status
WxAgent.Host conversations
WxAgent.Host open-chat "文件传输助手"
WxAgent.Host send-text "文件传输助手" "wxagent smoke test"
WxAgent.Host messages --visible
WxAgent.Host listen --seconds 60
WxAgent.Host smoke
```
### 5.4 自动化边界
- Linux CI:编译全部项目,执行 `WxAgent.Core.Tests`
- Windows 真机:执行 `WxAgent.Windows.Tests` 和 smoke/endurance 测试。
- 安装包、签名、FlaUI/UIA 执行、剪贴板和截图只能在 Windows 验证。
- 微信操作不能在 Windows Service 的 Session 0 中运行。
## 6. 当前真机 UIA 基线
2026-09-03 已通过 Windows MCP 对当前微信窗口进行 UI Automation 检查。已确认以下节点可见:
| 区域 | ControlType | AutomationId/名称 |
| --- | --- | --- |
| 主视图 | Group | `MainView` |
| 左侧导航 | ToolBar | `MainView.main_tabbar` |
| 会话列表 | List | `session_list` |
| 会话项 | ListItem | `session_item_{会话名称}` |
| 聊天页面 | Group | `chat_message_page` |
| 消息列表 | List | `chat_message_list` |
| 消息项 | ListItem | `chat_message_list.qt_scrollarea_viewport.chat_bubble_item_view` |
| 输入框 | Edit | `chat_input_field` |
| 输入区工具栏 | ToolBar | `tool_bar_accessible` |
| 当前会话标题 | Text | 路径末端 `current_chat_name_label` |
| 发送按钮 | Button | 名称 `发送` |
| 发送文件按钮 | Button | 名称 `发送文件` |
| 朋友圈入口 | Button | 名称 `朋友圈` |
| 通讯录入口 | Button | 名称 `通讯录` |
| 更多入口 | Button | `MainView.main_tabbar.tabbar_setting` |
这份基线可以直接用于第一版定位器,不需要重新从桌面坐标开始摸索。AutomationId 不是微信公开稳定接口,必须保留 Name、ControlType、祖先节点和相对关系作为回退条件。
定位顺序统一为:
```text
AutomationId
→ ControlType + Name
→ 稳定祖先节点 + 相对关系
→ UIA Pattern
→ 坐标兜底
```
禁止业务代码直接散落绝对坐标。确需坐标时集中登记,并记录适用 DPI、窗口尺寸和升级路径。
## 7. 功能参考对象
### 7.1 功能行为基线:wxautox4 公开文档
以下文档作为“实现什么”和“如何验收”的主要参考,不复制或反编译商业包实现:
| 范围 | 参考地址 |
| --- | --- |
| 安装与支持环境 | <https://docs.wxauto.org/docs/install.html> |
| 快速开始 | <https://docs.wxauto.org/docs/start.html> |
| 基础概念 | <https://docs.wxauto.org/docs/concepts.html> |
| 完整示例 | <https://docs.wxauto.org/docs/example.html> |
| WeChat 功能 | <https://docs.wxauto.org/docs/class/WeChat.html> |
| Chat 功能 | <https://docs.wxauto.org/docs/class/Chat.html> |
| Message 类型与动作 | <https://docs.wxauto.org/docs/class/Message.html> |
| Session 功能 | <https://docs.wxauto.org/docs/class/Session.html> |
| Moment 功能 | <https://docs.wxauto.org/docs/class/Moment.html> |
| 其他模型 | <https://docs.wxauto.org/docs/class/Other.html> |
| CLI 行为参考 | <https://docs.wxauto.org/docs/cli/index.html> |
| CLI 命令 | <https://docs.wxauto.org/docs/cli/commands.html> |
| 常见问题 | <https://docs.wxauto.org/docs/issues.html> |
| 可接受使用政策 | <https://docs.wxauto.org/legal/acceptable-use.html> |
| 商业许可 | <https://docs.wxauto.org/legal/commercial-license.html> |
| 微信历史版本参考 | <https://github.com/SiverKing/wechat4.0-windows-versions> |
行为复制原则:
1. 依据公开文档建立功能矩阵。
2. 使用测试账号观察输入、输出和错误场景。
3. 独立设计 C# 模型和实现。
4. 不反编译、不搬运商业包源码或受保护资源。
### 7.2 C#/UIA 实现参考
| 主题 | 参考地址 |
| --- | --- |
| FlaUI 源码与示例 | <https://github.com/FlaUI/FlaUI> |
| FlaUI UIA3 实现 | <https://github.com/FlaUI/FlaUI/tree/master/src/FlaUI.UIA3> |
| FlaUI Inspect 工具 | <https://github.com/FlaUI/FlaUInspect> |
| CsWin32 | <https://github.com/microsoft/CsWin32> |
| UI Automation Client 概览 | <https://learn.microsoft.com/windows/win32/winauto/uiauto-clientportal> |
| UIA 树结构 | <https://learn.microsoft.com/windows/win32/winauto/uiauto-treeoverview> |
| UIA 客户端线程模型 | <https://learn.microsoft.com/windows/win32/winauto/uiauto-threading> |
| UIA 客户端缓存 | <https://learn.microsoft.com/windows/win32/winauto/uiauto-cachingforclients> |
| UIA 事件 | <https://learn.microsoft.com/dotnet/framework/ui-automation/ui-automation-events-for-clients> |
| 虚拟化控件 | <https://learn.microsoft.com/windows/win32/winauto/uiauto-workingwithvirtualizeditems> |
| ItemContainer Pattern | <https://learn.microsoft.com/windows/win32/winauto/uiauto-implementingitemcontainer> |
| Inspect.exe | <https://learn.microsoft.com/windows/win32/winauto/inspect-objects> |
| Accessibility Insights | <https://accessibilityinsights.io/docs/windows/getstarted/inspect/> |
| Linux 构建 Windows Target | <https://learn.microsoft.com/dotnet/core/tools/sdk-errors/netsdk1100> |
| dotnet publish | <https://learn.microsoft.com/dotnet/core/tools/dotnet-publish> |
优先阅读顺序:
1. wxautox4 的 `WeChat``Chat``Message` 文档。
2. FlaUI UIA3 示例。
3. Microsoft UIA threading、tree、events、cache、virtualized items。
4. 使用 FlaUInspect/Accessibility Insights 对照当前微信控件树。
## 8. 功能复制矩阵
### 8.1 WeChat 级能力
| wxautox4 公开能力 | C# 计划能力 | 主要实现位置 | 阶段 |
| --- | --- | --- | --- |
| `KeepRunning` | Desktop Agent 生命周期 | Host | M1 |
| `GetSession` | 获取当前会话列表 | SessionReader | M2 |
| `ChatWith` | 搜索并打开会话 | ChatNavigator | M2 |
| `GetSubWindow` | 查找聊天子窗口 | WindowRegistry | M4 |
| `GetAllSubWindow` | 枚举聊天子窗口 | WindowRegistry | M4 |
| `AddListenChat` | 注册监听目标 | MessageMonitor | M3 |
| `RemoveListenChat` | 移除监听目标 | MessageMonitor | M3 |
| `StartListening` | 开始监听 | MessageMonitor | M3 |
| `StopListening` | 停止监听 | MessageMonitor | M3 |
| `SwitchToChat` | 切换聊天页面 | MainNavigator | M2 |
| `SwitchToContact` | 切换通讯录 | MainNavigator | M4 |
| `Moments` | 进入朋友圈 | MomentsNavigator | M5 |
| `PublishMoment` | 发布朋友圈 | MomentsClient | M5 |
| `GetNewFriends` | 获取好友申请 | ContactClient | M4 |
| `AddNewFriend` | 添加好友 | ContactClient | M4 |
| `EditFriendInfo` | 修改备注等信息 | ContactClient | M4 |
| `GetNextNewMessage` | 获取下一个新消息会话 | MessageMonitor | M3 |
| `GetAllRecentGroups` | 最近群聊列表 | GroupClient | M4 |
| `SendUrlCard` | 发送链接卡片 | MessageSender | M5 |
| `GetHistoryMessage` | 获取历史消息 | HistoryReader | M4 |
| `GetFriendDetails` | 好友列表及详情 | ContactClient | M4 |
| `CreateGroup` | 创建群聊 | GroupClient | M4 |
| `IsOnline` | 登录/在线状态 | WechatConnection | M1 |
| `GetMyInfo` | 获取当前账号信息 | AccountReader | M4 |
| `GetDialog` | 对话框识别与操作 | DialogHandler | M2 |
### 8.2 Chat 级能力
| wxautox4 公开能力 | C# 计划能力 | 阶段 |
| --- | --- | --- |
| `Show` | 激活聊天窗口 | M1 |
| `ChatInfo` | 当前会话信息 | M2 |
| `AtAll` | 群聊 @所有人 | M4 |
| `SendMsg` | 文本、@、引用文本发送 | M2/M3 |
| `SendFiles` | 图片和文件发送 | M2 |
| `SendAudio` | 语音文件发送 | M5 |
| `GetAllMessage` | 可见消息读取 | M2 |
| `AddGroupMembers` | 添加群成员 | M4 |
| `SetGroupName` | 修改群名称 | M4 |
| `SetGroupRemark` | 修改群备注 | M4 |
| `SetGroupAnnouncement` | 修改群公告 | M4 |
| `SetGroupMyNickname` | 修改群昵称 | M4 |
| `Close` | 关闭聊天窗口 | M2 |
### 8.3 Message 级能力
| 类别 | C# 计划能力 | 阶段 |
| --- | --- | --- |
| 通用属性 | 会话、发送人、时间、方向、消息类型、原始 UI 节点摘要 | M2 |
| `roll_into_view` | 虚拟化消息滚动到可见区域 | M3 |
| `click` | 激活消息 | M3 |
| `select_option` | 打开消息菜单并选择动作 | M3 |
| `quote` | 引用回复 | M3 |
| `forward` | 转发 | M4 |
| `tickle` | 拍一拍 | M5 |
| `sender_info` | 读取发送人信息 | M4 |
| `add_friend`/`delete_friend` | 好友操作 | M4 |
| `download`/`save_files` | 图片、文件、视频下载 | M4 |
| `ocr` | 图片 OCR | M4 |
| `to_text` | 语音转文字 | M4 |
| `get_url` | 获取链接 URL | M5 |
| `get_content` | 合并消息或特殊内容读取 | M5 |
| `to_markdown` | 标准化内容输出 | M5 |
### 8.4 Session 和朋友圈
| wxautox4 公开能力 | C# 计划能力 | 阶段 |
| --- | --- | --- |
| Session `search` | 会话搜索 | M2 |
| `go_top`/`roll_up`/`roll_down` | 会话列表滚动 | M2 |
| `click`/`double_click` | 打开主窗口或子窗口 | M2/M4 |
| `delete`/`hide` | 删除或隐藏会话 | M4 |
| `select_option` | 会话上下文菜单 | M4 |
| Moment `GetMoments` | 读取朋友圈 | M5 |
| `Refresh` | 刷新朋友圈 | M5 |
| `Publish` | 发布朋友圈 | M5 |
| `Like` | 点赞 | M5 |
| `Comment` | 评论 | M5 |
## 9. 核心设计
### 9.1 自动化命令队列
同一个微信窗口的写操作必须串行:
```text
业务请求
→ Channel<AutomationCommand>
→ 专用 UIA 工作线程
→ 微信窗口
```
不要并行点击、输入或滚动同一个窗口。读取操作也应通过统一调度,避免读取过程中页面被另一个命令切换。
### 9.2 UIA 线程
- 在专用 Windows 线程初始化 UIA/COM。
- UIA 事件处理器只采集最小信息并快速返回。
- 不在 UIA 回调线程执行业务回调或耗时操作。
- UIA 元素引用可能失效;跨步骤保存 RuntimeId 或轻量快照,不长期持有元素对象。
### 9.3 消息监听
微信 4.x 消息列表存在虚拟化,监听采用:
```text
UIA 事件
+ 低频轮询兜底
+ 可见消息快照比较
+ 有界滑动窗口去重
```
消息指纹建议:
```text
会话标识 + 发送方向 + 发送人 + 消息类型 + 内容摘要 + 时间标记 + 邻近顺序
```
微信没有公开稳定消息 ID,因此指纹只能用于工程去重,不能作为永久业务主键。
### 9.4 操作重试
允许重试:
- 查找窗口和控件
- 页面状态等待
- 会话读取
- 消息读取
- 历史加载
禁止盲目重试:
- 发送消息或文件
- 添加/删除联系人
- 群成员变更
- 发布朋友圈
- 点赞或评论
写操作可能已成功但结果确认失败,自动重试会产生重复或破坏性结果。
### 9.5 数据库只读边界
```text
Weixin.exe 可读内存
→ WCDB 十六进制候选
→ 具体 db_storage 数据库 page 1 salt/HMAC 验证
→ 按账号根目录指纹和相对路径保存证据
→ SQLCipher 4 ReadOnly + query_only 查询
```
- 扫描每个 `Weixin.exe` PID,只读取 `MEM_COMMIT` 且可读、非 guard/no-access 的区域。
- 账号身份使用规范化 `db_storage` 根目录指纹;PID、昵称、微信号或文件名均不能单独作为身份。
- 在实现进程句柄到数据库路径关联前,绑定置信度只能标记为 `PageHmacVerified`,不能宣称账号当前 active。
- 密钥保存必须由 CLI 显式请求,采用原子替换和当前用户 ACL;受控环境暂不引入 DPAPI。
- SQLCipher 连接只允许文件系统只读模式、私有缓存、关闭池、`PRAGMA query_only=ON` 和有界查询。
### 9.6 统一错误码
至少包含:
```text
WechatNotRunning
WechatNotLoggedIn
WindowNotFound
ControlNotFound
UiStructureChanged
Timeout
PermissionMismatch
SessionLocked
UnsupportedWechatVersion
ResultUnconfirmed
OperationCancelled
InvalidOperationState
```
## 10. 里程碑和开发顺序
### M0:功能基线,12 周
- 冻结功能矩阵
- 固定首个微信版本和 Windows 版本
- 准备测试账号、测试群和文件传输助手场景
- 保存首份脱敏 UI 树基线
验收:每项功能有输入、输出、错误场景和对应文档地址。
### M1:技术验证,23 周
M1A/Foundation + Database MVP 首先交付工程基础、`doctor`、脱敏 `inspect-ui`、只读密钥扫描和 SQLCipher 元数据查询。该切片完成不代表 M1 完成。
按顺序实现:
1. `doctor`:检测 Windows、微信进程、窗口、登录状态和权限。
2. `inspect-ui`:导出脱敏 UI 树。
3. 查找 `session_list``chat_message_list``chat_input_field`
4. 打开文件传输助手。
5. 发送一条文本。
6. 读取发送后的可见消息。
7. 接收并发现一条新消息。
8. 微信重启后重新连接。
状态(2026-09-04):M1 已完成。文件传输助手激活优先使用 AutomationId;文本通过 UIA ValuePattern 设置并在点击发送前逐字校验,避免输入法组合态;新消息采用 UIA 结构事件加两秒低频轮询;微信重启并由用户正常完成手机登录确认后可重新连接并通过消息级 smoke。
Go/No-Go:通过。消息发送、读取和监听未依赖绝对坐标;坐标点击仅使用 UIA 元素实时边界中心作为集中兜底。
### M2:基础消息,46 周
- 会话列表和搜索
- 打开会话
- 当前会话信息
- 发送文本、图片和文件
- 读取可见消息
- 基础消息类型解析
- 统一错误和超时
验收:连续发送 500 条测试消息无重复;100%、125%、150% DPI 可用。
进度(2026-09-04):本轮 M2 扩展切片已完成会话搜索结果输出和逐行精确匹配、会话切换、文件/图片剪贴板发送、微信真实引用回复格式解析,以及文件传输助手可见历史滚动读取。历史消息使用可访问文本与同文本出现序号生成稳定指纹,并经过有界去重;默认 CLI 输出不包含消息或引用正文。该切片真机验收通过,但不替代 M2 的 500 条连续发送及多 DPI 总体验收。
### M3:监听和完整消息模型,4–6 周
- 实时消息监听
- 轮询兜底
- 消息去重
- 文本、图片、文件、视频、语音、引用、系统消息等解析
- Agent 重启后的短期续接
- `IAsyncEnumerable<MessageEvent>` 事件输出
验收:持续监听 8 小时,消息无明显遗漏和重复;回调异常不影响监听器。
进度(2026-09-04):M3 功能实现完成。监听采用 UIA 结构事件快速唤醒和两秒轮询兜底,公开 `IAsyncEnumerable<MessageEvent>`,支持 checkpoint 恢复、`Reconnected` 事件、有界去重、逐回调异常隔离及默认脱敏 JSONL 输出。文本、图片、文件、视频、语音、链接、引用和系统类型均有解析覆盖。真机完成实时事件、Host 停止期间消息恢复、RuntimeId 变化后不重复输出和约 1.6 小时稳定性验证;稳定性阶段 20/20 条事件、0 重复、0 内容泄漏。完整 8 小时持续运行由用户明确取消,不再作为本轮完成阻塞;详见 `docs/validation/M3-Listening-2026-09-04.md`
### M4:联系人、群聊、历史和附件,6–10 周
- 联系人与好友申请
- 好友备注和详情
- 群成员、群名称、公告和昵称
- 创建群聊
- 历史消息滚动读取
- 图片、文件和视频下载
- OCR、语音转文字
验收:所有破坏性操作均有显式确认,且不会自动重试。
### M5:朋友圈和低频功能,5–8 周
- 朋友圈读取、发布、点赞、评论
- 链接卡片
- 合并消息和特殊消息内容
- 多聊天子窗口
- 功能矩阵剩余项目
验收:公开功能矩阵逐项关闭,不以“基本支持”代替明确结果。
### M6:稳定性,610 周
- 控件失效恢复
- 微信重启恢复
- 弹窗和页面状态恢复
- Windows 10/11、不同 DPI 和窗口尺寸
- 2472 小时监听
- 一万条消息去重测试
- 版本变化诊断
- 内存、句柄和 COM 对象检查
## 11. 测试计划
### 11.1 Linux 可执行测试
- 消息模型和序列化
- 文本、时间和消息类型解析
- 消息指纹与去重
- 状态机
- 错误映射
- UI 树快照的离线节点匹配
- 文件名和路径处理
UIA 节点先转换成可序列化的 `UiNodeSnapshot`,解析器尽量针对快照工作,以便 Linux 执行测试。
### 11.2 Windows 真机 smoke
每次发布至少执行:
1. 检测微信和登录状态。
2. 切换文件传输助手。
3. 发送唯一测试文本。
4. 在消息列表读回测试文本。
5. 发送一个小文件。
6. 下载或确认文件消息。
7. 开启 60 秒监听并从另一测试账号发送消息。
8. 保存脱敏 UI 树和测试报告。
### 11.3 Windows endurance
- 8、24、72 小时监听
- 微信重启 20 次
- 网络断开/恢复
- 会话快速切换
- 长消息列表滚动
- 多群并发产生消息
- 窗口尺寸和 DPI 变化
### 11.4 完成定义
每个功能必须具备:
- C# 公共调用方式
- 超时和取消行为
- 明确错误码
- 至少一个纯逻辑或真机检查
- 当前微信版本的验收记录
- 失败时可生成诊断信息
## 12. 诊断和升级策略
每次 UIA 失败记录:
- 微信版本
- Windows 版本和 DPI
- 当前页面和窗口状态
- 控件定位条件
- 找到的候选节点摘要
- 操作阶段和超时时间
- Correlation ID
- 可选脱敏 UI 树和截图
微信升级后的处理流程:
```text
运行 doctor/smoke
→ 与上一个 UI 树快照比较
→ 更新集中定位器
→ 运行离线快照测试
→ Windows 真机回归
```
定位器、消息识别规则和控件路径集中维护,避免每个功能自行查找控件。这是减少后续二次摸索的关键。
## 13. 人员和预计成本
推荐:
- 1 名资深 C#/Windows UIA 工程师
- 1 名 C# 工程师
- 0.5 名真机测试工程师
预计:
| 目标 | 两人团队工期 | 投入 |
| --- | ---: | ---: |
| 技术验证 | 3–5 周 | 1–2 人月 |
| 基础消息能力 | 2–3 个月 | 4–6 人月 |
| 高频功能覆盖 | 4–5 个月 | 7–10 人月 |
| 完整功能覆盖 | 6–8 个月 | 9–15 人月 |
| 商业稳定版本 | 8–11 个月 | 14–22 人月 |
不实现 Service 只能节省少量投入,主要工作仍然是微信 UI 行为识别、消息解析和版本兼容。
## 14. 第一批开发任务
按以下顺序直接开始,不再重新做技术选型:
1. 创建解决方案和四个初始项目。
2. 配置 Linux Windows targeting 和 `win-x64` publish。
3. 引入 FlaUI.UIA3、CsWin32 和只读 SQLCipher 运行时。
4. 实现 `doctor`、微信窗口连接和数据库根目录诊断。
5. 实现 UI 树脱敏导出。
6. 固化本文件列出的关键 AutomationId。
7. 实现打开文件传输助手。
8. 实现发送文本并读回确认。
9. 实现可见消息快照与去重。
10. 实现只读进程内存扫描、page 1 HMAC 校验、多账号密钥证据和 SQLCipher 元数据查询。
11. 在当前 Windows 真机运行第一轮 smoke。
第一轮只使用文件传输助手和专用测试账号,不操作真实联系人或群聊。
+38
View File
@@ -0,0 +1,38 @@
# M1 技术验证验收记录(2026-09-04
## 环境
- Windows10.0.19044,主机 `DESKTOP-EGI7QCK`
- 用户会话:`Rogee`Session 1,活动 console
- 微信:4.1.8.29
- 部署目录:`C:\Users\Rogee\wx-agent`
- 默认测试目标:文件传输助手
## Linux 检查
```bash
dotnet test tests/WxAgent.Core.Tests -c Release
dotnet build WxAgent.sln -c Release -p:EnableWindowsTargeting=true
dotnet publish src/WxAgent.Host -c Release -r win-x64 --self-contained true \
-p:EnableWindowsTargeting=true -p:PublishSingleFile=true \
-p:IncludeNativeLibrariesForSelfExtract=true -p:PublishTrimmed=false
```
结果:Core 测试 9/9 通过;Release 构建 0 warnings、0 errors;单文件发布成功。
## 真机结果
- `doctor`:登录后主窗口、`MainView``session_list` 检测正常;未选择聊天时不再误报 UI 结构变化。
- 文件传输助手:可按会话 AutomationId 直接激活;会话未出现时可通过搜索结果 AutomationId 激活。
- `chat send`:唯一 ASCII 标记发送成功,随后 `chat read --include-content` 精确读回。
- 输入法兼容:最初使用模拟键盘输入时观察到输入法组合态改变消息。改为 UIA ValuePattern 直接设置文本,并在发送前逐字读取校验;不一致时中止发送,不执行盲目重试。修复后发送和精确读回通过。
- `chat listen`:监听期间由另一个 CLI 进程发送唯一标记;UIA 结构事件配合两秒低频轮询发现 1 条新消息。
- `smoke --timeout 45``doctor`、脱敏 UI 树、唯一文本发送和读回确认全部通过;导出 168 个脱敏节点。
- 微信重启恢复:停止所有 Weixin 进程并重新启动后,微信要求手机确认登录;用户正常确认后出现 5 个 Weixin 进程,CLI 无需重启服务即可重新附加,发送、读取、监听和消息级 smoke 再次通过。
- Session 断开:RDP 会话断开时输入 API 返回 access denied/invalid handle;恢复为活动 console 后正常。该行为由 `doctor`/`SessionLocked` 边界约束,不尝试绕过桌面会话限制。
## 结论
M1 Go/No-Go**Go**。
发送、读取、监听和恢复均通过 UIA 元素定位及实时边界中心兜底完成,没有维护固定绝对坐标。发送写操作只执行一次;若输入内容或发送结果无法确认,返回 `ResultUnconfirmed`,不自动重试。
+32
View File
@@ -0,0 +1,32 @@
# M1A/Foundation + Database MVP 真机验收
日期:2026-09-04
主机:`DESKTOP-EGI7QCK` / Windows `10.0.19044.0`
微信:`Weixin.exe 4.1.8.29`
状态:M1A 通过;M1 仍未完成。
## Linux
- .NET SDK`8.0.424`
- `dotnet test tests/WxAgent.Core.Tests -c Release`7/7 通过。
- `dotnet build WxAgent.sln -c Release -p:EnableWindowsTargeting=true`0 warning / 0 error。
- self-contained single-file `win-x64` 发布成功;产物约 182 MB,包含自提取 SQLCipher native library。
## Windows
- `doctor`:发现 4 个 Weixin 进程,版本均为 4.1.8.29;4/4 可用最小只读权限打开;发现 1 个账号数据库根目录。
- SSH 在 Session 0 运行时正确报告 `SessionLocked`,不伪装为 UIA 成功。
- `inspect-ui`:通过 InteractiveToken 任务在 Session 1 执行成功;导出 166 个节点。离线校验确认所有非空 Name 均为长度+短哈希,AutomationId 仅保留已知结构常量或长度+短哈希;未出现原始 `session_item_*` 后缀及明文“微信”/“发送”等名称。
- `smoke`:在 Session 1 执行成功,`doctor` 无错误、6/6 关键控件命中,并再次导出 166 个脱敏节点。
- `db scan --save`:扫描 4 个进程,发现 17 个数据库,page 1 HMAC 验证 17/17;普通 CLI 输出不含完整密钥。
- 密钥文件 ACL:继承关闭,仅 `DESKTOP-EGI7QCK\Rogee` 拥有 FullControl。
- `db query`:只读打开 `contact/contact.db`,运行时报告 SQLCipher `4.5.2 community`、SQLite `3.39.2`,读取 26 个 schema 对象,写入探针被拒绝。
- 首轮评审后补充验证:WCDB 长十六进制游程使用定长缓冲;不可读/跳过内存区不再拼接候选;密钥临时文件在写入任何密钥字节前即关闭继承并限制到当前用户;重复扫描会合并历史账号/数据库;发现、扫描和 UIA 路径均传播取消;CLI 拒绝未知、重复、缺值和非法超时参数。
- 验收结束后删除测试密钥文件;未生成解密数据库副本。
## 已知风险
- `SQLitePCLRaw.bundle_e_sqlcipher` 2.1.11 已弃用且内置 SQLite 3.39.2;它仅获准用于当前开发与真机验证。正式商业发布前必须完成许可/安全基线 gate;若不接受,应改用 Zetetic 官方 SQLCipher for .NET 并重新执行数据库验收。
- 尚未实现 PID 打开文件句柄与账号根目录的关联,证据等级仅为 `PageHmacVerified`,不能表示账号当前 active。
- 正在写入的 WAL 数据库可能出现只读一致性/锁问题;本次元数据查询成功,暂不增加解密副本或快照逻辑。
- M1 尚缺文件传输助手打开、发送/读回、新消息发现和微信重启恢复。
@@ -0,0 +1,66 @@
# M2 会话搜索、引用和历史读取验收(2026-09-04)
## 环境
- Windows 10.0.19044`DESKTOP-EGI7QCK`
- 用户 `Rogee`Session 1 活动 console
- 微信 4.1.8.29
- 验收目标仅使用文件传输助手
## Linux 验证
```bash
dotnet test tests/WxAgent.Core.Tests -c Release
dotnet build WxAgent.sln -c Release -p:EnableWindowsTargeting=true
dotnet publish src/WxAgent.Host -c Release -r win-x64 --self-contained true \
-p:EnableWindowsTargeting=true -p:PublishSingleFile=true \
-p:IncludeNativeLibrariesForSelfExtract=true -p:PublishTrimmed=false
```
结果:Core 测试 13/13 通过;Release 构建 0 warnings、0 errorsWindows 单文件发布及部署成功。
## 真机验收
### 会话搜索和精确匹配
```powershell
WxAgent.Host session search --query 文件传输助手 --exact
WxAgent.Host session open --name 文件传输助手
```
- 搜索退出码 0,返回 1 个结果。
- 结果名称逐行精确匹配“文件传输助手”,`IsExactMatch=true`;不会把“文件传输助手测试”等部分匹配作为精确结果。
- 会话打开退出码 0,当前会话确认成功。
- 搜索框文本使用 UIA ValuePattern 设置并逐字确认,完成后恢复原值。
### 真实引用回复与解析
```powershell
WxAgent.Host chat reply-latest --text wxagent-quote-<随机标记> --timeout 60
WxAgent.Host chat read --limit 30 --include-content
```
- 使用消息行实时边界右侧的发送方气泡位置打开右键菜单,不维护固定屏幕坐标。
- 引用回复发送退出码 0,产生新的可见消息。
- 当前微信可访问名称实测格式:`回复文本\n引用 Rogee 的消息 : 图片`
- 解析结果:回复正文精确匹配;引用发送者为 `Rogee`;引用正文为“图片”。
- CLI 未指定 `--include-content` 时,正文和引用内容均输出为 `null`
### 历史滚动和去重
```powershell
WxAgent.Host chat history --limit 100 --scrolls 4 --timeout 60
```
-`chat_message_list` 实时边界中心执行鼠标滚轮向上读取,结束后回滚到原方向。
- 使用可访问名称和同文本出现序号生成稳定 SHA-256 指纹,避免 UIA RuntimeId 在元素刷新后变化导致重复。
- 真机结果:退出码 0,读取 7 条消息,7 个唯一指纹,重复数 0。
- 类型结果:Text 4、File 2、Image 1。
- 默认输出正文和引用均为空,隐私检查无泄漏。
- RDP 会话断开时滚轮输入返回 access denied;通过 `doctor`/Session 约束识别,恢复活动 console 后同一命令通过,不绕过桌面会话边界。
## 结论
本轮目标通过:会话搜索输出及精确匹配、真实引用回复解析、历史滚动读取和有界去重均完成代码、测试、部署和真机验收。
尚未宣称整个 M2 里程碑完成:计划中的 500 条连续发送与 100%/125%/150% DPI 总体验收仍属于后续 M2 稳定性工作,不在本轮目标内。
@@ -0,0 +1,99 @@
# M3 监听和完整消息模型验收(2026-09-04)
## 环境
- Windows 10.0.19044`DESKTOP-EGI7QCK`
- 用户 `Rogee`Session 1 活动 console
- 微信 4.1.13.63
- 验收目标仅使用文件传输助手
- Windows PowerShell 5.1 验证脚本如包含中文常量,使用 UTF-8 BOM 或 `-EncodedCommand`;无 BOM 的临时脚本会把中文参数显示为乱码,但 Host 原始 UTF-8 JSON 和 Unicode 命令行参数验证为正确。
## Linux 验证
```bash
dotnet test tests/WxAgent.Core.Tests -c Release
dotnet build WxAgent.sln -c Release -p:EnableWindowsTargeting=true
dotnet publish src/WxAgent.Host -c Release -r win-x64 --self-contained true \
-p:EnableWindowsTargeting=true -p:PublishSingleFile=true \
-p:IncludeNativeLibrariesForSelfExtract=true -p:PublishTrimmed=false
```
结果:Core 测试 15/15 通过;Release 构建 0 warnings、0 errorsWindows 单文件发布及部署成功。
最终部署包在聊天页状态执行 `doctor``inspect-ui``smoke`:三项退出码均为 0;doctor 找到全部六个基线控件,smoke 完成文件传输助手文本发送及确认。
## 实现范围
- `WechatChatClient.ListenEventsAsync` 公开 `IAsyncEnumerable<MessageEvent>`
- UIA 结构事件负责快速唤醒,两秒轮询负责防遗漏;轮询等待使用可取消的单次读取,不遗留后台等待任务。
- `MessageEventState` 保留最多 4096 个语义指纹;指纹不依赖可能变化的 UIA RuntimeId,同文本消息以出现序号区分。
- checkpoint 版本为 2,原子写入并限制为当前用户 ACL;Host 重启时产生 `Reconnected` 事件并恢复停机期间可见消息。
- 可选回调由 `MessageCallbackDispatcher` 逐个执行并隔离异常。
- 消息类型覆盖 Text、Image、File、Video、Voice、Link、Quote 和 System。
- `chat monitor` 输出紧凑的逐行 JSON;未指定 `--include-content` 时不输出正文和引用内容。
## 真机验收
### 实时事件和隐私输出
```powershell
WxAgent.Host chat monitor --seconds 30 --state-file artifacts\m3-state.json
WxAgent.Host chat send --text wxagent-m3-<随机标记>
```
- checkpoint 建立后发送唯一消息,monitor 输出 1 个 `MessageReceived`
- 事件 ID 唯一,重复数 0。
- 默认输出中 `message.content` 和引用内容均为 `null`
- JSON 每个事件严格占一行,可由 `ConvertFrom-Json` 逐行解析。
### Host 重启短期续接
1. 使用固定 state file 启动并停止 monitor。
2. monitor 停止期间向文件传输助手发送唯一消息。
3. 使用相同 state file 再次启动 monitor。
结果:先输出 1 个 `Recovered=true``Reconnected`,随后输出停机期间消息的 `MessageReceived`,该消息同样标记为 recovered,旧消息未重复输出。
### RuntimeId 变化后的稳定去重
监听期间发送 1 条唯一消息,再通过会话搜索和重新打开文件传输助手刷新 UIA 元素:
- 观察消息 1,唯一消息 1,重复 0。
- 已存在的引用消息未因 RuntimeId 变化而重新输出。
### 稳定性运行
原计划启动 8 小时任务,每约 5 分钟发送一条测试消息;首小时检查时监听器、checkpoint 和事件输出均正常。用户随后明确确认稳定性已满足并取消剩余监听,因此停止任务并清理临时 JSONL、checkpoint 和发送产物。
停止时汇总:
- 发送/预期事件:20
- 观察事件:20
- 唯一事件:20
- 发送失败:0
- 重复:0
- 默认正文泄漏:0
- checkpoint ACL:受保护
实际稳定运行约 1.6 小时;未宣称执行满 8 小时,完整时长由用户明确豁免。
### 回调异常隔离和类型解析
Core 测试验证:
- 前一回调抛出异常后,后一回调仍执行。
- 视频、语音、链接、引用和系统消息分类正确。
- 微信真实引用格式 `回复文本\n引用 <发送者> 的消息 : <原文>` 正确解析。
- checkpoint 恢复和 4096 条有界去重正确。
图片、文件、文本和真实引用消息此前已在同一微信版本的文件传输助手真机验证。
## 清理
- 已停止并注销临时监听进程。
- 已删除含运行事件的临时 JSONL、checkpoint、发送结果和 PowerShell 验证脚本。
- 保留不含正文、联系人或密钥的汇总结果与本验收记录。
## 结论
M3 功能和本轮用户要求的验证范围通过:实时监听、轮询兜底、完整消息模型、有界稳定去重、回调隔离、Host 重启续接、脱敏 JSONL 和 `IAsyncEnumerable<MessageEvent>` 均已实现并验证。完整 8 小时持续运行由用户明确取消,不作为本轮完成阻塞。
+353
View File
@@ -0,0 +1,353 @@
# 微信数据库密钥获取流程
## 适用范围
本文记录当前对 Windows 微信 `Weixin.exe 4.1.8.29` 的只读验证结果,以及后续将其接入 `agent-wechat` 的实现要点。
## 实测结论
- 目标进程:`Weixin.exe`
- 当前版本:`4.1.8.29`
- 发现微信进程:4 个,按工作集内存选择主进程
- 扫描约 577 MB 可读内存,耗时约 1.5 秒
- 发现数据库:17 个
- 找到密钥:17/17
- 页面 HMAC 校验:17/17
- SQLite 解密打开验证:17/17
测试过程中生成的密钥和解密数据库均已清理。
## 方案概览
```text
Weixin.exe
-> OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ)
-> VirtualQueryEx 枚举已提交且可读内存
-> ReadProcessMemory 读取内存段
-> 扫描 WCDB raw-key 字符串
-> 通过数据库 page 1 的 salt 筛选候选
-> HMAC-SHA512 验证候选 key
-> 建立 数据库相对路径 -> enc_key 的映射
-> 使用 SQLCipher 读取数据库
```
该方案不注入 DLL、不修改微信代码和内存,也不需要解密微信网络流量;但它仍然会申请微信进程的只读句柄并读取进程内存,准确说是低侵入外部内存扫描。
## 密钥格式与扫描
WCDB 在内存中缓存的候选内容通常表现为十六进制字符串:
```text
64 hex 字符:32 字节 enc_key
96 hex 字符:32 字节 enc_key + 16 字节 salt
```
扫描器还兼容更长的十六进制字符串,并从前 64 个字符取 key、末尾 32 个字符取 salt。
扫描区域条件:
- `MEM_COMMIT`
- 可读保护属性
- 单个区域小于 500 MB
- 不写入目标进程
## 密钥验证
从每个 `.db` 文件读取第一个 4096 字节页面:
```text
page_size = 4096
key_size = 32
salt_size = 16
reserve = 80
```
验证过程:
1. 取数据库 page 1 的前 16 字节作为 salt。
2. 对 salt 每字节执行 `byte ^ 0x3A` 得到 HMAC salt。
3. 使用 PBKDF2-HMAC-SHA512 派生 32 字节 HMAC key,迭代次数为 2。
4. 对 page 1 的加密数据和页号 1 计算 HMAC-SHA512。
5. 与页面末尾保存的 HMAC 比较。
只有 HMAC 正确的候选才会绑定到该数据库 salt。
## 数据库读取
解密读取需要 SQLCipher 4 兼容配置,核心参数:
```sql
PRAGMA key = "x'<64 hex key>'";
PRAGMA cipher_compatibility = 4;
```
验证 SQL
```sql
SELECT count(*) FROM sqlite_master;
```
数据库分类包括:
```text
contact/contact.db
session/session.db
message/message_*.db
favorite/favorite.db
emoticon/emoticon.db
media/media_*.db
```
## agent-wechat 接入改造点
当前 `agent-wechat` 的密钥提取和数据库路径实现偏 Linux:
```text
/proc/<pid>/maps
/proc/<pid>/mem
pgrep
/home/wechat/...
```
Windows 版本需要替换为:
```text
Toolhelp32Snapshot 或 WMI -> 查找 Weixin.exe PID
VirtualQueryEx -> 枚举内存区域
ReadProcessMemory -> 读取内存
Documents/AppData 路径扫描 -> 定位 xwechat_files/db_storage
```
`wechat-decrypt` 的输出格式是:
```json
{
"contact\\contact.db": {
"enc_key": "<64 hex>",
"salt": "<32 hex>"
},
"_db_dir": "..."
}
```
`agent-wechat` 当前提取器接口期望类似:
```json
{
"keys": {
"contact.db": "<64 hex>"
}
}
```
接入时需要增加格式和路径适配层,不能直接替换脚本。
## 多账号设计预留
密钥不能只保存为一个全局 `key`。正确主键应至少包含:
```text
account_root / account_id / database_relative_path / salt
```
建议保存结构:
```json
{
"accounts": {
"<account-root-fingerprint>": {
"account_root": "<path>",
"wechat_version": "4.1.8.29",
"pid": 1234,
"databases": {
"contact/contact.db": {
"salt": "<32 hex>",
"enc_key": "<64 hex>"
}
}
}
}
}
```
不要使用微信昵称、微信号或数据库文件名作为唯一账号标识;它们可能为空、变化或重复。优先使用账号数据库根目录的规范化路径和稳定指纹,并同时保存 salt 用于校验。
## 当前机器多账号调查
当前机器的微信数据根目录为:
```text
C:\Users\Rogee\Documents\xwechat_files
```
当前观察到:
```text
xwechat_files/all_users/login/ 1 个历史登录标识
xwechat_files/*/db_storage/ 1 个账号数据库根目录
Backup/ 1 个备份目录
```
当前账号数据库目录名类似:
```text
<account-id>_<suffix>\db_storage
```
`all_users\login\<user>\key_info.db` 存在 `LoginKeyInfoTable`,包含:
```text
user_name_md5
key_md5
key_info_md5
key_info_data
```
当前机器该表有 30 行,但只有 1 个 distinct user 和 1 个 distinct key。该表不应直接作为数据库解密 key 的唯一来源,仍应以目标账号数据库 page 1 的 salt 和进程内存扫描结果交叉验证。
## 多账号密钥存储方案
不要使用一个全局 `keys.json`。扫描时应为每个账号数据库根目录单独建立上下文:
```text
账号上下文 = account_root + Weixin.exe PID + 微信版本 + 数据库相对路径 + salt
```
推荐逻辑:
1. 枚举 `xwechat_files` 下所有包含 `db_storage` 的账号根目录。
2. 对每个账号根目录收集 `.db` 文件和 page 1 salt。
3. 扫描所有 `Weixin.exe` 进程内存。
4. 使用 `key + salt + page 1 HMAC` 将密钥绑定到具体账号根目录。
5. 每个账号单独保存密钥映射。
6. 账号退出登录后仍保留历史账号的密钥记录,但标记为 inactive。
7. 下次登录该账号时,用数据库根目录、salt 和数据库 page 1 重新确认,不依赖 PID。
建议内部模型:
```text
AccountKeySet {
account_root_fingerprint
account_root_path
account_hint
wechat_version
last_seen_at
active
databases {
relative_path
salt
enc_key
verified_at
}
}
```
`account_root_fingerprint` 可由以下信息组合计算:
```text
规范化 account_root 路径
+ 账号目录名
+ 数据库文件相对路径集合
+ page 1 salt 集合
```
不要把微信昵称、微信号或 PID 作为主键:昵称会变化,微信号可能不可见,PID 每次启动都会改变。
## 多账号并发注意事项
如果多个微信进程同时运行:
```text
扫描每个 PID
-> 读取该 PID 内存中的候选 key
-> 只与该账号根目录的 salt 集合验证
-> 成功后绑定 PID 对应账号
```
不能把所有账号的数据库 salt 混在一个全局集合中,否则可能发生错误绑定或重复扫描。
如果同一账号同时存在多个微信进程,应以数据库根目录和 salt 作为最终身份,而不是 PID。
密钥文件应按账号隔离,并避免在日志中输出完整 `enc_key`。当前方案运行于受控环境,保留明文 `hex_key` 存储,不引入 DPAPI 或 Windows Credential Manager。
## 当前登录账号与密钥的绑定
密钥本身不携带可直接读取的微信号或昵称。参考 `agent-wechat` 的做法,先确定“当前微信进程对应的数据库根目录”,再把该进程扫描出的密钥绑定到该目录。
Linux 版流程:
```text
WeChat PID
-> 扫描 /proc/<pid>/fd
-> 找到该进程打开的 db_storage/*.db
-> 截取 xwechat_files/ 后的第一级目录
-> 得到 account_dir
-> 扫描同一 PID 的内存获取 keys
-> 写入 session_id + account_dir + db_name + hex_key
```
对应代码:
```text
wechat_db.rs::find_account_dir()
login.rs::handle_detecting_user()
wechat_keys.rs::store_keys()
```
`agent-wechat` 实际使用的存储主键是:
```text
(session_id, account_dir, db_name)
```
其中:
- `session_id`:应用管理的会话/实例
- `account_dir`:微信账号数据库根目录名
- `db_name`:如 `session.db``contact.db``message_0.db`
- `hex_key`:对应数据库的密钥
Windows 版应使用以下等价流程:
```text
Weixin.exe PID
-> 获取该 PID 持有的文件句柄
-> 找到 xwechat_files\\<account_dir>\\db_storage\\*.db
-> 得到 account_dir
-> 只扫描该 PID 的内存
-> 用该 account_dir 下每个数据库的 page 1 salt 验证 key
-> 写入 (session_id, account_dir, relative_db_path, hex_key)
```
Windows 获取进程打开文件的方法按可靠性排序:
1. `NtQuerySystemInformation(SystemExtendedHandleInformation)` 枚举系统句柄并解析文件对象。
2. 使用 Sysinternals `handle.exe` 辅助诊断,不作为最终运行依赖。
3. 通过账号目录、数据库最近写入时间、进程 PID 和密钥 salt 做候选匹配。
不能只通过以下信息判断账号:
```text
密钥值:没有账号语义
PID:重启后变化
微信昵称:可能为空或变化
微信号:可能不可见或变化
数据库文件名:不同账号相同
```
最可靠的绑定证据是:
```text
进程 PID 持有的数据库文件路径
+ 数据库 page 1 salt
+ 候选密钥 HMAC 验证
```
如果同一个微信进程管理多个登录账号,应以每个 `db_storage` 根目录分别扫描和验证;不能把一个 PID 的所有候选 key 无条件归给一个账号。
## 参考实现
- `ylytdeng/wechat-decrypt/find_all_keys_windows.py`
- `ylytdeng/wechat-decrypt/key_scan_common.py`
- `multi-user-wechat/agent-wechat/packages/agent-server-rust/src/tools/wechat_keys.rs`
- `multi-user-wechat/agent-wechat/packages/agent-server-rust/src/tools/wechat_db.rs`