diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000..2f4f87f
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,12 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(xargs:*)",
+ "Bash(tree:*)",
+ "Bash(go build:*)",
+ "Bash(go run:*)"
+ ],
+ "deny": [],
+ "ask": []
+ }
+}
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..e6616eb
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,22 @@
+{
+ // 使用 IntelliSense 了解相关属性。
+ // 悬停以查看现有属性的描述。
+ // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Launch file",
+ "type": "go",
+ "request": "launch",
+ "mode": "debug",
+ "program": "${file}"
+ },
+ {
+ "name": "Launch Package",
+ "type": "go",
+ "request": "launch",
+ "mode": "auto",
+ "program": "${fileDirname}"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index c71a9c6..420e59d 100644
--- a/README.md
+++ b/README.md
@@ -4,18 +4,213 @@
未经修改的源代码在 [main 分支](https://github.com/CJYKK/chatlog_backup/tree/main),本人不对代码中的任何内容负责。
-# 提示
+## 项目概述
+
+这是一个微信聊天记录解密工具,支持Windows和macOS平台。工具通过注入DLL或内存扫描的方式获取微信数据库密钥,然后解密微信聊天数据库文件。
+
+## 主要功能
+
+- **密钥获取**:支持DLL注入和内存扫描两种方式获取微信数据库密钥
+- **数据库解密**:解密微信加密的SQLite数据库文件
+- **图片解密**:解密微信加密的图片文件(需要图片密钥)
+- **自动监控**:监控微信数据目录,自动解密新增数据
+- **HTTP服务**:提供本地HTTP服务,支持MCP协议
+- **多平台支持**:支持Windows和macOS
+- **多版本支持**:支持微信3.x和4.x版本
+
+## 快速开始
+
+### 1. 获取密钥DLL
获取key请前往 https://github.com/ycccccccy/wx_key
-更新日志:
+将 `wx_key.dll` 文件放置在 `lib/windows_x64/` 目录下。
-1.2025年12月13日同步了wx_key项目相关图片解密处理逻辑
+### 2. 编译项目
+```bash
+go build -o chatlog_alpha.exe main.go
+```
-2.http://127.0.0.1:5030/默认页面增加了清除图片缓存功能,方便测试
+### 3. 运行程序
+```bash
+./chatlog_alpha.exe
+```
+### 4. 完整使用逻辑
+#### 1.启动chatlog
+#### 2.启动微信(不要点击登录)
+#### 3.等待chatlog获取到pid信息时,再点击登录
+#### 4.30s后返回key,注意获取图片key还未适配,欢迎大佬提交
-3.对ffmpeg未安装导致的dat转换失败进行显示提示
+## 使用说明
-#重要提示
-1.对dat转换一定要安装ffmpeg,并且在系统变量设置bin目录的path,否则会显式报错
+### 界面操作
+程序启动后会出现TUI界面,主要功能包括:
-2.图片解密aes key请转为hex,如:16位aes key -》 34000000386000006538323730000000
+1. **获取密钥**:从微信进程获取数据库密钥和图片密钥
+2. **解密数据**:解密微信数据文件
+3. **启动HTTP服务**:启动本地HTTP & MCP服务器
+4. **开启自动解密**:监控数据目录,自动解密新增数据
+5. **设置**:配置应用程序选项
+6. **切换账号**:切换当前操作的账号
+7. **退出**:退出程序
+
+### 密钥获取流程
+
+1. **DLL方式(推荐)**:
+ - 程序会尝试加载 `wx_key.dll`
+ - 初始化DLL Hook到微信进程
+ - 轮询获取密钥(需要用户登录微信并查看聊天记录触发数据库读取)
+ - 获取成功后自动清理Hook
+
+2. **原生方式(备用)**:
+ - 如果DLL不可用,使用内存扫描方式
+ - 在微信进程内存中搜索密钥模式
+ - 需要微信已登录状态
+
+### 临时账户管理
+
+程序支持临时账户管理,当微信未登录或重启时:
+
+- **临时账户名称**:格式为 `未登录微信_PID`,会跟随PID变化自动更新
+- **状态监控**:实时监控微信进程状态变化
+- **自动切换**:微信登录后自动切换为真实账户名称
+- **数据清理**:微信退出后自动清理相关数据
+
+## 配置说明
+
+### 重要配置项
+
+1. **HTTP服务地址**:默认 `127.0.0.1:5030`
+2. **工作目录**:解密后文件的存储目录
+3. **数据目录**:微信数据文件所在目录
+4. **数据密钥**:微信数据库解密密钥
+5. **图片密钥**:微信图片解密密钥
+
+### 配置文件
+
+配置文件位于用户目录下的 `.chatlog/config.json`,包含:
+- 历史账号信息
+- 最后使用的账号
+- HTTP服务配置
+- 工作目录设置
+
+## 重要提示
+
+### 1. ffmpeg依赖
+对dat转换一定要安装ffmpeg,并且在系统变量设置bin目录的path,否则会显式报错。
+
+### 2. 图片密钥格式
+图片解密aes key请转为hex,如:16位aes key → `34000000386000006538323730000000`
+
+### 3. 使用注意事项
+- 获取密钥时微信会短暂卡顿,这是正常现象
+- 请确保微信已登录并打开聊天窗口查看历史消息
+- 对于微信V4,图片密钥可能不是必需的
+- 程序需要管理员/root权限来访问进程内存
+
+## 更新日志
+
+### 2025年12月14日
+- 优化临时账户名称管理,跟随PID变化自动更新
+- 改进微信进程状态监控逻辑
+- 修复微信重启后PID不更新的问题
+- 增强错误处理和用户提示
+
+### 2025年12月13日
+- 同步了wx_key项目相关图片解密处理逻辑
+- http://127.0.0.1:5030/默认页面增加了清除图片缓存功能,方便测试
+- 对ffmpeg未安装导致的dat转换失败进行显示提示
+
+### 早期版本
+- 实现DLL密钥获取方式
+- 添加DLL日志记录功能
+- 支持微信未登录状态获取PID
+- 实现自动监控和解密功能
+- 添加HTTP服务支持
+
+## 文件结构
+
+```
+chatlog_alpha/
+├── main.go # 程序入口
+├── internal/
+│ ├── chatlog/ # 聊天记录处理核心
+│ │ ├── app.go # TUI应用程序
+│ │ ├── ctx/ # 上下文管理
+│ │ └── manager.go # 管理器
+│ ├── wechat/ # 微信相关功能
+│ │ ├── wechat.go # 微信账号管理
+│ │ ├── key/ # 密钥提取器
+│ │ ├── decrypt/ # 解密器
+│ │ └── process/ # 进程检测
+│ └── ui/ # 用户界面组件
+├── pkg/
+│ ├── util/ # 工具函数
+│ └── config/ # 配置管理
+├── lib/
+│ └── windows_x64/ # Windows DLL文件
+└── dll调用指南.md # DLL使用指南
+```
+
+## 技术实现
+
+### 密钥获取机制
+
+1. **DLL注入方式**:
+ - 使用 `wx_key.dll` 注入Shellcode到微信进程
+ - 通过共享内存传递密钥数据
+ - 支持微信4.x版本
+
+2. **内存扫描方式**:
+ - 读取微信进程内存
+ - 搜索特定的密钥模式
+ - 支持微信3.x和4.x版本
+
+### 进程监控
+
+- 实时检测微信进程状态
+- 支持多实例微信
+- 自动处理进程重启
+- 状态变化时更新UI
+
+### 日志系统
+
+- DLL操作日志保存到程序运行目录
+- 详细的错误信息和调试信息
+- 支持日志级别控制
+
+## 常见问题
+
+### Q: 获取密钥超时怎么办?
+A: 请确保:
+1. 微信已登录(不能停留在登录界面)
+2. 打开任意聊天窗口
+3. 向上滚动查看历史消息(触发数据库读取)
+4. 或者发送/接收一条新消息
+
+### Q: DLL加载失败怎么办?
+A: 请检查:
+1. `wx_key.dll` 文件是否在 `lib/windows_x64/` 目录
+2. 系统架构是否匹配(需要64位系统)
+3. 杀毒软件是否拦截了DLL加载
+
+### Q: 图片解密失败怎么办?
+A: 请检查:
+1. 图片密钥是否正确(需要16字节的HEX字符串)
+2. 微信版本是否支持图片解密
+3. 图片文件是否完整
+
+### Q: 程序无法检测到微信进程?
+A: 请检查:
+1. 微信是否正在运行
+2. 程序是否有足够的权限
+3. 防病毒软件是否阻止了进程访问
+
+## 许可证
+
+本项目基于原chatlog项目,具体许可证信息请参考原项目。
+
+## 免责声明
+
+本项目仅供学习和研究使用,请勿用于非法用途。使用本工具产生的任何后果由使用者自行承担。
+
+**重要**:请遵守当地法律法规,尊重他人隐私,合法使用本工具。
\ No newline at end of file
diff --git a/dll调用指南.md b/dll调用指南.md
new file mode 100644
index 0000000..89aa1fd
--- /dev/null
+++ b/dll调用指南.md
@@ -0,0 +1,164 @@
+# wx_key.dll 集成开发指南
+
+本文档旨在帮助开发者快速理解并集成 `wx_key.dll`。该组件封装了微信逆向工程的核心逻辑,让你无需关心底层的内存扫描与 Hook 实现,即可在 C#、Flutter 或 C++ 等上层应用中获取微信数据库密钥与图片密钥。
+
+---
+
+## 1. 核心原理
+
+简单来说,`wx_key.dll` 充当了**宿主程序**与**微信进程**之间的桥梁。
+
+1. **注入与扫描**:当你的程序加载此 DLL 并调用初始化后,它会通过 `RemoteScanner` 扫描微信进程内存,利用特征码定位密钥获取函数的入口(支持 4.x 多个版本)。PID 由外部传入,DLL 不再枚举进程。
+2. **拦截与共享**:定位成功后,DLL 会写入一段 Shellcode 进行 Hook。当微信尝试读取数据库时,Shellcode 会拦截 32 字节的密钥,将其拷贝到**共享内存缓冲**中,并递增一个 `sequenceNumber`。
+3. **轮询机制**:DLL 内部使用事件唤醒 + 轮询监听线程。你的程序只需定时检查共享内存是否有新的 `sequenceNumber`,即可拿到密钥。
+
+> **⚠️ 环境硬性要求**:
+> * **架构**:仅支持 x64 系统与 64 位微信客户端(Shellcode 为 x64 汇编)。
+> * **权限**:调用进程若失败可能需要 **管理员身份(Administrator)** 运行。
+
+---
+
+## 2. API 接口说明
+
+所有导出函数均为 C 风格接口,声明文件可见 `wx_key/include/hook_controller.h`。
+
+| 接口函数 | 参数说明 | 详细描述 |
+| :--- | :--- | :--- |
+| **`InitializeHook`** | `DWORD targetPid` (微信进程ID) | **启动入口**。执行远程扫描、分配共享内存并注入 Shellcode。成功返回 `true`,失败请调 `GetLastErrorMsg`。PID 需要调用方自行获取。 |
+| **`PollKeyData`** | `char* keyBuf`
`int size` (建议 >= 65) | **获取密钥**。非阻塞检查。如果捕获到密钥,会将其格式化为 64 位 HEX 字符串写入缓冲区并返回 `true`。一次读取后自动清空。 |
+| **`GetStatusMessage`** | `char* msgBuf`
`int size`
`int* outLevel` | **获取日志**。读取 DLL 内部运行日志(如“扫描成功”、“特征码未找到”等)。`outLevel` 对应:`0=Info, 1=Success, 2=Error`。 |
+| **`CleanupHook`** | 无 | **清理资源**。卸载远程 Hook、释放共享内存并关闭句柄。**程序退出前务必调用**。 |
+| **`GetLastErrorMsg`** | 无 | **错误诊断**。返回最近一次操作失败的具体原因。 |
+
+---
+
+## 3. 标准调用流程
+
+无论使用哪种语言,集成步骤都应该要遵循以下流程:
+
+### 第一步:定位进程
+自行查找 `Weixin.exe` 的 PID(进程 ID),DLL 不再替你枚举进程。
+
+### 第二步:加载 DLL
+将 `wx_key.dll` 加载到当前进程空间。
+* **Flutter**: `DynamicLibrary.open('assets/dll/wx_key.dll')`
+* **C#**: `NativeLibrary.Load("wx_key.dll")`
+
+### 第三步:初始化 (Initialize)
+调用 `InitializeHook(pid)`。
+* 如果返回 `false`,通常是因为**权限不足**或**微信版本不支持**(特征码失效),请立即打印 `GetLastErrorMsg()` 排查。
+
+### 第四步:轮询 (Polling)
+启动一个后台线程或定时器(建议间隔 100ms),循环调用 `PollKeyData` 和 `GetStatusMessage`。
+* **注意**:不要在 UI 线程直接做死循环,也不要设置过长的等待时间。
+* `PollKeyData` 每次返回后会清空共享缓冲;若需要去重,可对 `sequenceNumber` 进行本地缓存比对。
+
+### 第五步:清理 (Cleanup)
+在程序关闭或不再需要功能时,**必须**调用 `CleanupHook()`。
+* 如果不清理,残留在微信进程内的 Shellcode 可能会在微信后续运行时导致崩溃。
+
+---
+
+## 4. 代码集成示例 (C#)
+
+以下代码展示了如何通过 P/Invoke 封装一个健壮的调用类:
+
+```csharp
+using System.Runtime.InteropServices;
+using System.Text;
+
+public class WeChatKeyDumper
+{
+ private const string DllName = "wx_key.dll";
+
+ [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+ private static extern bool InitializeHook(uint targetPid);
+
+ [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+ private static extern bool PollKeyData(StringBuilder keyBuffer, int bufferSize);
+
+ [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+ private static extern bool GetStatusMessage(StringBuilder statusBuffer, int bufferSize, out int level);
+
+ [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+ private static extern bool CleanupHook();
+
+ [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr GetLastErrorMsg();
+
+ // 启动监听任务
+ public void Start(uint pid)
+ {
+ if (!InitializeHook(pid))
+ {
+ string error = Marshal.PtrToStringUTF8(GetLastErrorMsg());
+ throw new Exception($"初始化失败: {error} (请尝试以管理员身份运行)");
+ }
+
+ Task.Run(async () =>
+ {
+ var keyBuf = new StringBuilder(128);
+ var logBuf = new StringBuilder(512);
+ int level;
+
+ try
+ {
+ while (true)
+ {
+ // 1. 尝试获取密钥
+ if (PollKeyData(keyBuf, keyBuf.Capacity))
+ {
+ Console.WriteLine($"[KEY FOUND] {keyBuf}");
+ // 拿到密钥后,可根据需求决定是否继续监听
+ }
+
+ // 2. 获取内部日志
+ while (GetStatusMessage(logBuf, logBuf.Capacity, out level))
+ {
+ Console.WriteLine($"[DLL Log - L{level}] {logBuf}");
+ }
+
+ await Task.Delay(100); // 避免 CPU 占用过高
+ }
+ }
+ finally
+ {
+ CleanupHook(); // 确保线程退出时清理环境
+ }
+ });
+ }
+}
+```
+
+---
+
+## 5. 开发者避坑指南
+
+在实际集成中,你可能会遇到这些问题:
+
+1. **缓冲区溢出**:
+ `PollKeyData` 返回的是 Hex 字符串,加上结束符至少需要 65 字节。C# 的 `StringBuilder` 或 C++ 的 `char[]` 分配小了会导致内存踩踏,建议给 **128 字节** 以防万一。
+
+2. **单例原则**:
+ 同一个微信进程只能被 Hook 一次。如果需要重启扫描,请先调用 `CleanupHook` 彻底释放资源,再重新 `InitializeHook`。
+
+3. **版本兼容性**:
+ 如果遇到微信更新导致无法获取密钥,通常是特征码偏移变了。此时无需修改上层代码,只需更新 DLL 源码中的 `RemoteScanner` 特征码库并重新编译 DLL 即可。
+
+4. **日志前缀**:
+ DLL 返回的 `GetStatusMessage` 字符串不再附加 `[INFO]/[SUCCESS]` 等标签,UI 层可依据 `outLevel` 自行添加前缀。
+
+5. **共享内存结构(供参考)**:
+
+ ```c
+ typedef struct {
+ DWORD dataSize; // 固定 32
+ BYTE keyBuffer[32]; // 32 字节密钥
+ DWORD sequenceNumber; // 每次写入 +1,用于去重
+ } SharedKeyData;
+ ```
+
+ Shellcode 会在 `dataSize == 32` 时写入并递增 `sequenceNumber`。上层可以持有上次读到的 `sequenceNumber` 来避免重复处理同一条数据。
+
+6. **多线程安全**:
+ 虽然导出函数内部是线程安全的,但为了逻辑清晰,建议仅在一个专用的 Monitor 线程中进行轮询操作。
\ No newline at end of file
diff --git a/internal/chatlog/app.go b/internal/chatlog/app.go
index d0ee2ce..c011c19 100644
--- a/internal/chatlog/app.go
+++ b/internal/chatlog/app.go
@@ -6,6 +6,7 @@ import (
"runtime"
"time"
+ "github.com/rs/zerolog/log"
"github.com/sjzar/chatlog/internal/chatlog/ctx"
"github.com/sjzar/chatlog/internal/ui/footer"
"github.com/sjzar/chatlog/internal/ui/form"
@@ -141,6 +142,24 @@ func (a *App) refresh() {
case <-a.stopRefresh:
return
case <-tick.C:
+ // 如果当前账号为空,尝试查找微信进程
+ if a.ctx.Current == nil {
+ // 获取微信实例
+ instances := a.m.wechat.GetWeChatInstances()
+ if len(instances) > 0 {
+ // 找到微信进程,设置第一个为当前账号
+ a.ctx.SwitchCurrent(instances[0])
+ log.Info().Msgf("检测到微信进程,PID: %d,已设置为当前账号", instances[0].PID)
+ }
+ }
+
+ // 刷新当前账号状态(如果存在)
+ if a.ctx.Current != nil {
+ a.ctx.Current.RefreshStatus()
+ // 更新上下文信息
+ a.ctx.Refresh()
+ }
+
if a.ctx.AutoDecrypt || a.ctx.HTTPEnabled {
a.m.RefreshSession()
}
diff --git a/internal/chatlog/ctx/context.go b/internal/chatlog/ctx/context.go
index 05420ac..e83f4dd 100644
--- a/internal/chatlog/ctx/context.go
+++ b/internal/chatlog/ctx/context.go
@@ -125,6 +125,7 @@ func (c *Context) SwitchCurrent(info *wechat.Account) {
}
func (c *Context) Refresh() {
if c.Current != nil {
+ oldAccount := c.Account
c.Account = c.Current.Name
c.Platform = c.Current.Platform
c.Version = c.Current.Version
@@ -132,15 +133,27 @@ func (c *Context) Refresh() {
c.PID = int(c.Current.PID)
c.ExePath = c.Current.ExePath
c.Status = c.Current.Status
- if c.Current.Key != "" && c.Current.Key != c.DataKey {
+ // 更新密钥数据 - 如果Current中的密钥为空,也更新Context
+ if c.Current.Key != c.DataKey {
c.DataKey = c.Current.Key
}
- if c.Current.ImgKey != "" && c.Current.ImgKey != c.ImgKey {
+ if c.Current.ImgKey != c.ImgKey {
c.ImgKey = c.Current.ImgKey
}
- if c.Current.DataDir != "" && c.Current.DataDir != c.DataDir {
+ if c.Current.DataDir != c.DataDir {
c.DataDir = c.Current.DataDir
}
+
+ // 如果账号名称发生变化(例如从临时名称变为真实名称),更新历史记录
+ if oldAccount != "" && oldAccount != c.Account {
+ // 将旧的历史记录迁移到新的账号名称下
+ if oldHistory, ok := c.History[oldAccount]; ok {
+ c.History[c.Account] = oldHistory
+ delete(c.History, oldAccount)
+ // 更新配置
+ c.UpdateConfig()
+ }
+ }
}
if c.DataUsage == "" && c.DataDir != "" {
go func() {
diff --git a/internal/wechat/key/extractor.go b/internal/wechat/key/extractor.go
index 1b50ede..d4261dd 100644
--- a/internal/wechat/key/extractor.go
+++ b/internal/wechat/key/extractor.go
@@ -2,6 +2,7 @@ package key
import (
"context"
+ "fmt"
"github.com/sjzar/chatlog/internal/errors"
"github.com/sjzar/chatlog/internal/wechat/decrypt"
@@ -23,11 +24,22 @@ type Extractor interface {
}
// NewExtractor 创建适合当前平台的密钥提取器
+// 对于Windows平台,优先使用DLL方式(如果DLL存在)
func NewExtractor(platform string, version int) (Extractor, error) {
switch {
case platform == "windows" && version == 3:
+ // 尝试使用DLL方式
+ if extractor, err := NewDLLExtractor(platform, version); err == nil {
+ return extractor, nil
+ }
+ // 如果DLL方式失败,回退到原来的方式
return windows.NewV3Extractor(), nil
case platform == "windows" && version == 4:
+ // 尝试使用DLL方式
+ if extractor, err := NewDLLExtractor(platform, version); err == nil {
+ return extractor, nil
+ }
+ // 如果DLL方式失败,回退到原来的方式
return windows.NewV4Extractor(), nil
case platform == "darwin" && version == 3:
return darwin.NewV3Extractor(), nil
@@ -37,3 +49,23 @@ func NewExtractor(platform string, version int) (Extractor, error) {
return nil, errors.PlatformUnsupported(platform, version)
}
}
+
+// NewDLLExtractor 创建使用DLL的密钥提取器(仅支持Windows)
+func NewDLLExtractor(platform string, version int) (Extractor, error) {
+ if platform != "windows" {
+ return nil, errors.PlatformUnsupported(platform, version)
+ }
+
+ // 检查DLL是否可用
+ if !windows.IsDLLAvailable() {
+ return nil, fmt.Errorf("wx_key.dll 不可用")
+ }
+
+ switch version {
+ case 3, 4:
+ // V3和V4都使用相同的DLL提取器
+ return windows.NewDLLV4Extractor(), nil
+ default:
+ return nil, errors.PlatformUnsupported(platform, version)
+ }
+}
diff --git a/internal/wechat/key/windows/dll_extractor.go b/internal/wechat/key/windows/dll_extractor.go
new file mode 100644
index 0000000..90b90ca
--- /dev/null
+++ b/internal/wechat/key/windows/dll_extractor.go
@@ -0,0 +1,506 @@
+package windows
+
+import (
+ "context"
+ "encoding/hex"
+ "fmt"
+ "sync"
+ "time"
+ "unsafe"
+
+ "github.com/rs/zerolog/log"
+ "golang.org/x/sys/windows"
+
+ "github.com/sjzar/chatlog/internal/wechat/decrypt"
+ "github.com/sjzar/chatlog/internal/wechat/model"
+ "github.com/sjzar/chatlog/pkg/util"
+)
+
+// DLL函数定义
+var (
+ modwxkey *windows.LazyDLL
+
+ procInitializeHook *windows.LazyProc
+ procPollKeyData *windows.LazyProc
+ procGetStatusMessage *windows.LazyProc
+ procCleanupHook *windows.LazyProc
+ procGetLastErrorMsg *windows.LazyProc
+
+ dllAvailable = false // 标记DLL是否可用
+)
+
+// DLLExtractor 使用wx_key.dll的密钥提取器
+type DLLExtractor struct {
+ validator *decrypt.Validator
+ mu sync.Mutex
+ initialized bool
+ pid uint32
+ lastKey string // 记录上次获取的密钥,用于简单去重
+ logger *util.DLLLogger // DLL日志记录器
+}
+
+// init 初始化DLL函数
+func init() {
+ // 加载DLL - 使用相对路径
+ dllPath := "lib/windows_x64/wx_key.dll"
+ modwxkey = windows.NewLazyDLL(dllPath)
+
+ // 尝试加载DLL,检查是否可用
+ err := modwxkey.Load()
+ if err != nil {
+ log.Debug().Err(err).Msg("wx_key.dll 加载失败,将使用原生方式")
+ dllAvailable = false
+ return
+ }
+
+ // 获取函数指针
+ procInitializeHook = modwxkey.NewProc("InitializeHook")
+ procPollKeyData = modwxkey.NewProc("PollKeyData")
+ procGetStatusMessage = modwxkey.NewProc("GetStatusMessage")
+ procCleanupHook = modwxkey.NewProc("CleanupHook")
+ procGetLastErrorMsg = modwxkey.NewProc("GetLastErrorMsg")
+
+ // 检查所有函数是否都成功获取
+ if procInitializeHook != nil && procPollKeyData != nil && procGetStatusMessage != nil &&
+ procCleanupHook != nil && procGetLastErrorMsg != nil {
+ dllAvailable = true
+ log.Debug().Msg("wx_key.dll 加载成功,将使用DLL方式获取密钥")
+ } else {
+ dllAvailable = false
+ log.Debug().Msg("wx_key.dll 函数获取失败,将使用原生方式")
+ }
+}
+
+// IsDLLAvailable 检查DLL是否可用
+func IsDLLAvailable() bool {
+ return dllAvailable
+}
+
+// NewDLLV4Extractor 创建使用DLL的V4密钥提取器
+func NewDLLV4Extractor() *DLLExtractor {
+ return &DLLExtractor{
+ logger: util.GetDLLLogger(),
+ }
+}
+
+// Extract 从进程中提取密钥(使用DLL方式)
+func (e *DLLExtractor) Extract(ctx context.Context, proc *model.Process) (string, string, error) {
+ // 即使状态是offline(未登录),也允许尝试初始化DLL
+ // 因为DLL方式可以在用户登录后拦截密钥
+ if proc.Status == model.StatusOffline {
+ log.Info().Msg("微信进程存在但未登录,将尝试初始化DLL,请登录微信后操作")
+ // 不返回错误,继续执行
+ }
+
+ e.mu.Lock()
+ defer e.mu.Unlock()
+
+ // 清理之前的初始化(如果存在)
+ if e.initialized {
+ e.cleanup()
+ }
+
+ // 初始化DLL
+ if err := e.initialize(proc.PID); err != nil {
+ return "", "", err
+ }
+
+ // 确保无论成功失败都清理
+ // 注意:这里使用defer确保cleanup在函数返回前执行
+ defer func() {
+ if e.initialized {
+ e.cleanup()
+ }
+ }()
+
+ // 轮询获取密钥
+ return e.pollKeys(ctx, proc.Version)
+}
+
+// initialize 初始化DLL Hook
+func (e *DLLExtractor) initialize(pid uint32) error {
+ // 调用InitializeHook
+ ret, _, err := procInitializeHook.Call(uintptr(pid))
+ if ret == 0 {
+ // 获取错误信息
+ errorMsg := e.getLastError()
+
+ // 记录错误日志
+ if e.logger != nil {
+ e.logger.LogInitialization(pid, false, errorMsg)
+ }
+
+ if errorMsg != "" {
+ return fmt.Errorf("初始化DLL失败: %s", errorMsg)
+ }
+ if err != nil {
+ return fmt.Errorf("初始化DLL失败: %v", err)
+ }
+ return fmt.Errorf("初始化DLL失败")
+ }
+
+ e.initialized = true
+ e.pid = pid
+
+ // 记录成功日志
+ if e.logger != nil {
+ e.logger.LogInitialization(pid, true, "")
+ e.logger.LogInfo(fmt.Sprintf("DLL初始化成功,PID: %d", pid))
+ }
+
+ log.Debug().Msgf("DLL初始化成功,PID: %d", pid)
+ return nil
+}
+
+// pollKeys 轮询获取密钥
+func (e *DLLExtractor) pollKeys(ctx context.Context, version int) (string, string, error) {
+ if !e.initialized {
+ return "", "", fmt.Errorf("DLL未初始化")
+ }
+
+ // 设置超时时间 - 改为30秒
+ timeout := time.After(30 * time.Second)
+ ticker := time.NewTicker(100 * time.Millisecond)
+ defer ticker.Stop()
+
+ var dataKey, imgKey string
+ loginPromptShown := false // 标记是否已显示登录提示
+ pollCount := 0 // 轮询计数器
+
+ for {
+ select {
+ case <-ctx.Done():
+ return "", "", ctx.Err()
+ case <-timeout:
+ // 检查是否获取到了数据密钥
+ if dataKey != "" {
+ // 获取到了数据密钥,但没有获取到图片密钥
+ warningMsg := "30秒轮询结束,已获取数据库密钥,但未获取到图片密钥\n" +
+ "注意:对于微信V4,图片密钥可能不是必需的,或者需要其他方式获取\n" +
+ "数据库密钥: " + dataKey
+ log.Warn().Msg("30秒轮询结束,已获取数据库密钥,但未获取到图片密钥")
+ log.Warn().Msg("注意:对于微信V4,图片密钥可能不是必需的,或者需要其他方式获取")
+ log.Warn().Msg("数据库密钥: " + dataKey)
+
+ // 记录到日志文件
+ if e.logger != nil {
+ e.logger.LogWarning(warningMsg)
+ }
+
+ // 返回数据库密钥,图片密钥为空
+ return dataKey, "", nil
+ } else {
+ // 没有获取到任何密钥
+ errorMsg := "获取密钥超时(30秒)!可能的原因:\n" +
+ "1. 微信未登录 - 请登录微信\n" +
+ "2. 未触发数据库读取 - 请打开聊天窗口并查看历史消息\n" +
+ "3. DLL Hook失败 - 检查日志文件查看详细错误\n" +
+ "4. 微信版本不受支持 - 当前支持: 4.0.x 及以上 4.x 版本"
+ log.Error().Msg("获取密钥超时(30秒)!可能的原因:")
+ log.Error().Msg("1. 微信未登录 - 请登录微信")
+ log.Error().Msg("2. 未触发数据库读取 - 请打开聊天窗口并查看历史消息")
+ log.Error().Msg("3. DLL Hook失败 - 检查日志文件查看详细错误")
+ log.Error().Msg("4. 微信版本不受支持 - 当前支持: 4.0.x 及以上 4.x 版本")
+
+ // 记录到日志文件
+ if e.logger != nil {
+ e.logger.LogError(errorMsg)
+ }
+ return "", "", fmt.Errorf("获取密钥超时(30秒,请查看上方错误提示)")
+ }
+ case <-ticker.C:
+ pollCount++
+
+ // 尝试获取密钥
+ key, err := e.pollKeyData()
+ if err != nil {
+ errorMsg := fmt.Sprintf("轮询密钥失败: %v", err)
+ log.Err(err).Msg("轮询密钥失败")
+ // 记录到日志文件
+ if e.logger != nil {
+ e.logger.LogError(errorMsg)
+ }
+ continue
+ }
+
+ if key != "" && key != e.lastKey {
+ // 简单去重:避免重复处理相同的密钥
+ e.lastKey = key
+
+ // 验证密钥类型
+ keyBytes, err := hex.DecodeString(key)
+ if err != nil {
+ errorMsg := fmt.Sprintf("解码密钥失败: %v", err)
+ log.Err(err).Msg("解码密钥失败")
+ // 记录到日志文件
+ if e.logger != nil {
+ e.logger.LogError(errorMsg)
+ }
+ continue
+ }
+
+ // 检查是否是数据库密钥
+ if e.validator != nil && e.validator.Validate(keyBytes) {
+ if dataKey == "" {
+ dataKey = key
+ msg := "通过DLL找到数据库密钥: " + key
+ log.Info().Msg(msg)
+ // 记录到日志文件
+ if e.logger != nil {
+ e.logger.LogPolling(true, key, "数据库")
+ e.logger.LogInfo(msg)
+ }
+ }
+ } else if e.validator == nil {
+ // 验证器为nil时,根据密钥长度判断
+ // 数据库密钥通常是32字节(64字符HEX字符串)
+ // 图片密钥通常是16字节(32字符HEX字符串)
+ if len(key) == 64 && dataKey == "" {
+ dataKey = key
+ msg := "通过DLL找到数据库密钥(无验证): " + key
+ log.Info().Msg(msg)
+ // 记录到日志文件
+ if e.logger != nil {
+ e.logger.LogPolling(true, key, "数据库")
+ e.logger.LogInfo(msg)
+ }
+ } else if len(key) == 32 && imgKey == "" {
+ imgKey = key
+ msg := "通过DLL找到图片密钥(无验证): " + imgKey
+ log.Info().Msg(msg)
+ // 记录到日志文件
+ if e.logger != nil {
+ e.logger.LogPolling(true, imgKey, "图片")
+ e.logger.LogInfo(msg)
+ }
+ }
+ }
+
+ // 检查是否是图片密钥(取前16字节)
+ if e.validator != nil && e.validator.ValidateImgKey(keyBytes) {
+ if imgKey == "" {
+ imgKey = key[:32] // 16字节的HEX字符串是32个字符
+ msg := "通过DLL找到图片密钥: " + imgKey
+ log.Info().Msg(msg)
+ // 记录到日志文件
+ if e.logger != nil {
+ e.logger.LogPolling(true, imgKey, "图片")
+ e.logger.LogInfo(msg)
+ }
+ }
+ }
+
+ // 如果两个密钥都找到了,返回
+ if dataKey != "" && imgKey != "" {
+ return dataKey, imgKey, nil
+ }
+
+ // 对于微信V3,只需要数据库密钥
+ if version == 3 && dataKey != "" {
+ return dataKey, "", nil
+ }
+
+ } else {
+ // 没有获取到密钥,每5秒显示一次操作提示
+ // 每100ms轮询一次,50次轮询 = 5秒
+ if !loginPromptShown && pollCount%50 == 0 {
+ msg := "等待获取密钥... 请按以下步骤操作:\n" +
+ "1. 确保微信已登录(不能停留在登录界面)\n" +
+ "2. 打开任意聊天窗口\n" +
+ "3. 向上滚动查看历史消息(触发数据库读取)\n" +
+ "4. 或者发送/接收一条新消息"
+ log.Info().Msg("等待获取密钥... 请按以下步骤操作:")
+ log.Info().Msg("1. 确保微信已登录(不能停留在登录界面)")
+ log.Info().Msg("2. 打开任意聊天窗口")
+ log.Info().Msg("3. 向上滚动查看历史消息(触发数据库读取)")
+ log.Info().Msg("4. 或者发送/接收一条新消息")
+
+ // 记录到日志文件
+ if e.logger != nil {
+ e.logger.LogInfo(msg)
+ }
+ loginPromptShown = true
+ }
+ }
+
+ // 获取状态信息
+ e.getStatusMessages()
+
+ // 每10秒显示一次调试信息
+ if pollCount%100 == 0 {
+ debugMsg := fmt.Sprintf("轮询中... 已轮询 %d 次,已等待 %.1f 秒", pollCount, float64(pollCount)*0.1)
+ log.Debug().Msg(debugMsg)
+
+ // 记录到日志文件
+ if e.logger != nil {
+ e.logger.LogDebug(debugMsg)
+ }
+ }
+ }
+ }
+}
+
+// pollKeyData 调用DLL的PollKeyData函数
+func (e *DLLExtractor) pollKeyData() (string, error) {
+ if !e.initialized {
+ return "", fmt.Errorf("DLL未初始化")
+ }
+
+ // 分配缓冲区(至少65字节,建议128字节)
+ buf := make([]byte, 128)
+ ret, _, _ := procPollKeyData.Call(
+ uintptr(unsafe.Pointer(&buf[0])),
+ uintptr(len(buf)),
+ )
+
+ if ret == 0 {
+ // 没有新密钥
+ return "", nil
+ }
+
+ // 找到以null结尾的字符串
+ for i := 0; i < len(buf); i++ {
+ if buf[i] == 0 {
+ key := string(buf[:i])
+ if key != "" {
+ debugMsg := fmt.Sprintf("从DLL获取到密钥字符串: %s (长度: %d)", key, len(key))
+ log.Debug().Msg(debugMsg)
+ // 记录到日志文件
+ if e.logger != nil {
+ e.logger.LogDebug(debugMsg)
+ }
+ }
+ return key, nil
+ }
+ }
+
+ key := string(buf)
+ if key != "" {
+ debugMsg := fmt.Sprintf("从DLL获取到密钥字符串(无null终止): %s (长度: %d)", key, len(key))
+ log.Debug().Msg(debugMsg)
+ // 记录到日志文件
+ if e.logger != nil {
+ e.logger.LogDebug(debugMsg)
+ }
+ }
+ return key, nil
+}
+
+// getStatusMessages 获取DLL状态信息
+func (e *DLLExtractor) getStatusMessages() {
+ if !e.initialized {
+ return
+ }
+
+ buf := make([]byte, 512)
+ var level int32
+
+ for {
+ ret, _, _ := procGetStatusMessage.Call(
+ uintptr(unsafe.Pointer(&buf[0])),
+ uintptr(len(buf)),
+ uintptr(unsafe.Pointer(&level)),
+ )
+
+ if ret == 0 {
+ break
+ }
+
+ // 找到以null结尾的字符串
+ var msg string
+ for i := 0; i < len(buf); i++ {
+ if buf[i] == 0 {
+ msg = string(buf[:i])
+ break
+ }
+ }
+
+ if msg != "" {
+ logLevel := "INFO"
+ if level == 1 {
+ logLevel = "SUCCESS"
+ } else if level == 2 {
+ logLevel = "ERROR"
+ }
+ log.Debug().Msgf("[DLL %s] %s", logLevel, msg)
+
+ // 记录到日志文件
+ if e.logger != nil {
+ e.logger.LogStatus(int(level), msg)
+ }
+ }
+ }
+}
+
+// getLastError 获取DLL最后错误信息
+func (e *DLLExtractor) getLastError() string {
+ ret, _, _ := procGetLastErrorMsg.Call()
+ if ret == 0 {
+ return ""
+ }
+
+ // 将指针转换为Go字符串
+ errorMsgPtr := (*byte)(unsafe.Pointer(ret))
+ if errorMsgPtr == nil {
+ return ""
+ }
+
+ // 计算字符串长度
+ length := 0
+ for {
+ if *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(errorMsgPtr)) + uintptr(length))) == 0 {
+ break
+ }
+ length++
+ if length > 1024 {
+ break
+ }
+ }
+
+ if length == 0 {
+ return ""
+ }
+
+ buf := make([]byte, length)
+ for i := 0; i < length; i++ {
+ buf[i] = *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(errorMsgPtr)) + uintptr(i)))
+ }
+
+ errorMsg := string(buf)
+
+ // 记录错误信息到日志文件
+ if e.logger != nil && errorMsg != "" {
+ e.logger.LogError(errorMsg)
+ }
+
+ return errorMsg
+}
+
+// cleanup 清理DLL资源
+func (e *DLLExtractor) cleanup() {
+ if !e.initialized {
+ return
+ }
+
+ procCleanupHook.Call()
+ e.initialized = false
+ e.lastKey = "" // 清理上次密钥记录
+
+ // 记录清理日志
+ if e.logger != nil {
+ e.logger.LogCleanup()
+ }
+
+ log.Debug().Msg("DLL资源已清理")
+}
+
+// SearchKey 在内存中搜索密钥(DLL方式不支持此功能)
+func (e *DLLExtractor) SearchKey(ctx context.Context, memory []byte) (string, bool) {
+ // DLL方式不支持直接内存搜索
+ return "", false
+}
+
+// SetValidate 设置验证器
+func (e *DLLExtractor) SetValidate(validator *decrypt.Validator) {
+ e.validator = validator
+}
\ No newline at end of file
diff --git a/internal/wechat/manager.go b/internal/wechat/manager.go
index b317f35..7d353a1 100644
--- a/internal/wechat/manager.go
+++ b/internal/wechat/manager.go
@@ -32,6 +32,11 @@ func GetAccounts() []*Account {
return DefaultManager.GetAccounts()
}
+// GetAllProcesses 获取所有微信进程
+func GetAllProcesses() ([]*model.Process, error) {
+ return DefaultManager.GetAllProcesses()
+}
+
// Manager 微信管理器
type Manager struct {
detector process.Detector
@@ -97,6 +102,22 @@ func (m *Manager) GetAccounts() []*Account {
return m.accounts
}
+// GetAllProcesses 获取所有微信进程
+func (m *Manager) GetAllProcesses() ([]*model.Process, error) {
+ // 重新加载进程信息
+ if err := m.Load(); err != nil {
+ return nil, err
+ }
+
+ // 从processMap中提取所有进程
+ processes := make([]*model.Process, 0, len(m.processMap))
+ for _, p := range m.processMap {
+ processes = append(processes, p)
+ }
+
+ return processes, nil
+}
+
// DecryptDatabase 便捷方法:通过账号名解密数据库
func (m *Manager) DecryptDatabase(ctx context.Context, accountName, dbPath, outputPath string) error {
// 获取账号
diff --git a/internal/wechat/process/windows/detector_windows.go b/internal/wechat/process/windows/detector_windows.go
index f5cc1a3..9c11187 100644
--- a/internal/wechat/process/windows/detector_windows.go
+++ b/internal/wechat/process/windows/detector_windows.go
@@ -1,6 +1,7 @@
package windows
import (
+ "fmt"
"path/filepath"
"strings"
@@ -15,7 +16,10 @@ func initializeProcessInfo(p *process.Process, info *model.Process) error {
files, err := p.OpenFiles()
if err != nil {
log.Err(err).Msgf("获取进程 %d 的打开文件失败", p.Pid)
- return err
+ // 即使获取打开文件失败,也返回进程信息(状态保持为offline)
+ // 为未登录的进程生成临时账号名称
+ info.AccountName = fmt.Sprintf("未登录微信_%d", p.Pid)
+ return nil
}
dbPath := V3DBFile
@@ -44,5 +48,9 @@ func initializeProcessInfo(p *process.Process, info *model.Process) error {
}
}
+ // 如果没有找到数据库文件,进程仍然存在,只是未登录
+ // 状态保持为 model.StatusOffline(调用方会处理)
+ // 为未登录的进程生成临时账号名称
+ info.AccountName = fmt.Sprintf("未登录微信_%d", p.Pid)
return nil
}
diff --git a/internal/wechat/wechat.go b/internal/wechat/wechat.go
index b509dab..2ad1fb5 100644
--- a/internal/wechat/wechat.go
+++ b/internal/wechat/wechat.go
@@ -2,8 +2,11 @@ package wechat
import (
"context"
+ "fmt"
"os"
+ "strings"
+ "github.com/rs/zerolog/log"
"github.com/sjzar/chatlog/internal/errors"
"github.com/sjzar/chatlog/internal/wechat/decrypt"
"github.com/sjzar/chatlog/internal/wechat/key"
@@ -43,26 +46,168 @@ func (a *Account) RefreshStatus() error {
// 查找所有微信进程
Load()
+ // 首先尝试通过名称查找
process, err := GetProcess(a.Name)
if err != nil {
- a.Status = model.StatusOffline
- return nil
+ // 如果通过名称找不到,尝试通过PID查找
+ if a.PID != 0 {
+ // 获取所有进程
+ processes, err := GetAllProcesses()
+ if err != nil {
+ a.Status = model.StatusOffline
+ return nil
+ }
+
+ // 通过PID查找
+ var foundByPID bool
+ for _, p := range processes {
+ if p.PID == a.PID {
+ process = p
+ foundByPID = true
+ break
+ }
+ }
+
+ if !foundByPID {
+ // 微信可能重启了,原来的PID找不到进程
+ // 尝试查找其他微信进程
+ if len(processes) > 0 {
+ // 选择第一个微信进程(假设只有一个微信实例)
+ process = processes[0]
+
+ // 保存旧的PID用于日志
+ oldPID := a.PID
+
+ // 重置账号状态为未登录状态
+ a.PID = process.PID
+ a.ExePath = process.ExePath
+ a.Platform = process.Platform
+ a.Version = process.Version
+ a.FullVersion = process.FullVersion
+ a.Status = process.Status
+ a.DataDir = process.DataDir
+
+ // 更新临时账户名称(跟随PID变化)
+ oldName := a.Name
+ a.Name = fmt.Sprintf("未登录微信_%d", process.PID)
+
+ // 如果名称变化,记录日志
+ if oldName != a.Name {
+ log.Info().Msgf("临时账户名称从 '%s' 更新为 '%s'", oldName, a.Name)
+ }
+
+ log.Info().Msgf("微信可能已重启,PID从 %d 变为 %d,账号重置为未登录状态", oldPID, process.PID)
+ return nil
+ } else {
+ // 没有找到任何微信进程 - 微信可能已退出
+ a.clearAccountData()
+ log.Info().Msg("微信进程未找到,可能已退出,已清除账号数据")
+ return nil
+ }
+ }
+ } else {
+ // PID为0,尝试查找所有微信进程
+ processes, err := GetAllProcesses()
+ if err != nil {
+ a.Status = model.StatusOffline
+ return nil
+ }
+
+ if len(processes) > 0 {
+ // 找到微信进程,更新账号信息
+ process = processes[0]
+
+ // 更新进程信息
+ a.PID = process.PID
+ a.ExePath = process.ExePath
+ a.Platform = process.Platform
+ a.Version = process.Version
+ a.FullVersion = process.FullVersion
+ a.Status = process.Status
+ a.DataDir = process.DataDir
+
+ // 更新临时账户名称(跟随PID变化)
+ oldName := a.Name
+ a.Name = fmt.Sprintf("未登录微信_%d", process.PID)
+
+ // 如果名称变化,记录日志
+ if oldName != a.Name {
+ log.Info().Msgf("临时账户名称从 '%s' 更新为 '%s'", oldName, a.Name)
+ }
+
+ log.Info().Msgf("微信已重新启动,PID: %d,账号重置为未登录状态", process.PID)
+ return nil
+ } else {
+ // 没有找到任何微信进程
+ a.Status = model.StatusOffline
+ return nil
+ }
+ }
}
- if process.AccountName == a.Name {
- // 更新进程信息
- a.PID = process.PID
- a.ExePath = process.ExePath
- a.Platform = process.Platform
- a.Version = process.Version
- a.FullVersion = process.FullVersion
- a.Status = process.Status
- a.DataDir = process.DataDir
+ // 检查PID是否变化(微信可能重启了)
+ if a.PID != 0 && a.PID != process.PID {
+ log.Info().Msgf("微信PID变化:从 %d 变为 %d,可能已重启", a.PID, process.PID)
+ }
+
+ // 更新进程信息
+ a.PID = process.PID
+ a.ExePath = process.ExePath
+ a.Platform = process.Platform
+ a.Version = process.Version
+ a.FullVersion = process.FullVersion
+ a.Status = process.Status
+ a.DataDir = process.DataDir
+
+ // 如果账号名称是临时名称,但进程有真实的账号名称,更新账号名称
+ if strings.HasPrefix(a.Name, "未登录微信_") && process.AccountName != "" && !strings.HasPrefix(process.AccountName, "未登录微信_") {
+ a.Name = process.AccountName
+ } else if strings.HasPrefix(a.Name, "未登录微信_") && (process.AccountName == "" || strings.HasPrefix(process.AccountName, "未登录微信_")) {
+ // 账号名称是临时名称,但进程没有真实名称(或也是临时名称)
+ // 检查PID是否变化,如果变化则更新临时名称
+ oldName := a.Name
+ // 从旧名称中提取旧的PID
+ oldPIDStr := strings.TrimPrefix(oldName, "未登录微信_")
+ var oldPID uint32
+ fmt.Sscanf(oldPIDStr, "%d", &oldPID)
+
+ // 如果PID变化,更新临时名称
+ if oldPID != process.PID {
+ a.Name = fmt.Sprintf("未登录微信_%d", process.PID)
+ log.Info().Msgf("临时账户PID变化,名称从 '%s' 更新为 '%s'", oldName, a.Name)
+ }
}
return nil
}
+// clearAccountData 清除账号数据(当微信退出时调用)
+func (a *Account) clearAccountData() {
+ // 保存旧的名称用于日志
+ oldName := a.Name
+
+ // 清除密钥数据
+ a.Key = ""
+ a.ImgKey = ""
+
+ // 清除路径信息
+ a.DataDir = ""
+
+ // 重置状态
+ a.Status = model.StatusOffline
+
+ // 重置PID
+ a.PID = 0
+
+ // 重置账号名称为临时名称(如果还有PID的话)
+ // 如果没有PID,保持原有名称或设置为空
+ if a.PID == 0 {
+ // 如果没有PID,无法生成临时名称,保持原有名称
+ // 但可以标记为已退出
+ log.Info().Msgf("账号 '%s' 的微信已退出,已清除相关数据", oldName)
+ }
+}
+
// GetKey 获取账号的密钥
func (a *Account) GetKey(ctx context.Context) (string, string, error) {
// 如果已经有密钥,直接返回
@@ -75,10 +220,9 @@ func (a *Account) GetKey(ctx context.Context) (string, string, error) {
return "", "", errors.RefreshProcessStatusFailed(err)
}
- // 检查账号状态
- if a.Status != model.StatusOnline {
- return "", "", errors.WeChatAccountNotOnline(a.Name)
- }
+ // 注意:不再检查账号状态是否为online
+ // 因为DLL提取器支持在未登录状态下工作
+ // 用户可以在获取密钥过程中登录微信
// 创建密钥提取器 - 使用新的接口,传入平台和版本信息
extractor, err := key.NewExtractor(a.Platform, a.Version)
@@ -91,12 +235,22 @@ func (a *Account) GetKey(ctx context.Context) (string, string, error) {
return "", "", err
}
- validator, err := decrypt.NewValidator(process.Platform, process.Version, process.DataDir)
- if err != nil {
- return "", "", err
+ // 只有在DataDir存在时才创建验证器
+ // 对于DLL方式,微信可能未登录,DataDir可能为空或路径不存在
+ var validator *decrypt.Validator
+ if process.DataDir != "" {
+ validator, err = decrypt.NewValidator(process.Platform, process.Version, process.DataDir)
+ if err != nil {
+ // 如果创建验证器失败,记录警告但不返回错误
+ // 因为DLL方式可以不依赖验证器
+ log.Warn().Err(err).Msg("创建验证器失败,将继续尝试获取密钥(DLL方式可能不需要验证器)")
+ validator = nil
+ }
}
- extractor.SetValidate(validator)
+ if validator != nil {
+ extractor.SetValidate(validator)
+ }
// 提取密钥
dataKey, imgKey, err := extractor.Extract(ctx, process)
diff --git a/lib/windows_x64/wx_key.exp b/lib/windows_x64/wx_key.exp
new file mode 100644
index 0000000..5591595
Binary files /dev/null and b/lib/windows_x64/wx_key.exp differ
diff --git a/lib/windows_x64/wx_key.lib b/lib/windows_x64/wx_key.lib
new file mode 100644
index 0000000..246292d
Binary files /dev/null and b/lib/windows_x64/wx_key.lib differ
diff --git a/lib/windows_x64/wx_key.pdb b/lib/windows_x64/wx_key.pdb
new file mode 100644
index 0000000..c7bf5c2
Binary files /dev/null and b/lib/windows_x64/wx_key.pdb differ
diff --git a/pkg/util/dll_logger.go b/pkg/util/dll_logger.go
new file mode 100644
index 0000000..adfa4f1
--- /dev/null
+++ b/pkg/util/dll_logger.go
@@ -0,0 +1,171 @@
+package util
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "sync"
+ "time"
+)
+
+// DLLLogger DLL日志记录器
+type DLLLogger struct {
+ mu sync.Mutex
+ logFile *os.File
+ logPath string
+ enabled bool
+}
+
+var (
+ dllLogger *DLLLogger
+ dllLoggerOnce sync.Once
+)
+
+// GetDLLLogger 获取DLL日志记录器单例
+func GetDLLLogger() *DLLLogger {
+ dllLoggerOnce.Do(func() {
+ dllLogger = &DLLLogger{
+ enabled: true,
+ }
+ // 尝试初始化日志文件
+ dllLogger.initLogFile()
+ })
+ return dllLogger
+}
+
+// initLogFile 初始化日志文件
+func (l *DLLLogger) initLogFile() {
+ if !l.enabled {
+ return
+ }
+
+ // 获取当前工作目录
+ workDir, err := os.Getwd()
+ if err != nil {
+ // 如果获取失败,使用默认工作目录
+ workDir = DefaultWorkDir("")
+ }
+
+ // 创建日志目录
+ logDir := filepath.Join(workDir, "logs")
+ if err := os.MkdirAll(logDir, 0755); err != nil {
+ // 如果创建目录失败,禁用日志
+ l.enabled = false
+ return
+ }
+
+ // 生成日志文件名:dll_YYYYMMDD_HHMMSS.log
+ timestamp := time.Now().Format("20060102_150405")
+ logFileName := fmt.Sprintf("dll_%s.log", timestamp)
+ l.logPath = filepath.Join(logDir, logFileName)
+
+ // 创建日志文件
+ file, err := os.OpenFile(l.logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
+ if err != nil {
+ l.enabled = false
+ return
+ }
+
+ l.logFile = file
+}
+
+// LogError 记录错误信息
+func (l *DLLLogger) LogError(message string) {
+ l.log("ERROR", message)
+}
+
+// LogStatus 记录状态信息
+func (l *DLLLogger) LogStatus(level int, message string) {
+ var levelStr string
+ switch level {
+ case 0:
+ levelStr = "INFO"
+ case 1:
+ levelStr = "SUCCESS"
+ case 2:
+ levelStr = "ERROR"
+ default:
+ levelStr = "UNKNOWN"
+ }
+ l.log(levelStr, message)
+}
+
+// LogInfo 记录普通信息
+func (l *DLLLogger) LogInfo(message string) {
+ l.log("INFO", message)
+}
+
+// LogDebug 记录调试信息
+func (l *DLLLogger) LogDebug(message string) {
+ l.log("DEBUG", message)
+}
+
+// LogWarning 记录警告信息
+func (l *DLLLogger) LogWarning(message string) {
+ l.log("WARNING", message)
+}
+
+// log 内部日志记录函数
+func (l *DLLLogger) log(level, message string) {
+ if !l.enabled || l.logFile == nil {
+ return
+ }
+
+ l.mu.Lock()
+ defer l.mu.Unlock()
+
+ timestamp := time.Now().Format("2006-01-02 15:04:05.000")
+ logEntry := fmt.Sprintf("[%s] [%s] %s\n", timestamp, level, message)
+
+ if _, err := l.logFile.WriteString(logEntry); err != nil {
+ // 写入失败,关闭文件并禁用日志
+ l.logFile.Close()
+ l.logFile = nil
+ l.enabled = false
+ }
+}
+
+// GetLogPath 获取日志文件路径
+func (l *DLLLogger) GetLogPath() string {
+ return l.logPath
+}
+
+// IsEnabled 检查日志是否启用
+func (l *DLLLogger) IsEnabled() bool {
+ return l.enabled
+}
+
+// Close 关闭日志文件
+func (l *DLLLogger) Close() {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+
+ if l.logFile != nil {
+ l.logFile.Close()
+ l.logFile = nil
+ }
+ l.enabled = false
+}
+
+// LogInitialization 记录DLL初始化信息
+func (l *DLLLogger) LogInitialization(pid uint32, success bool, errorMsg string) {
+ if success {
+ l.LogStatus(1, fmt.Sprintf("DLL初始化成功,PID: %d", pid))
+ } else {
+ l.LogError(fmt.Sprintf("DLL初始化失败,PID: %d, 错误: %s", pid, errorMsg))
+ }
+}
+
+// LogPolling 记录轮询信息
+func (l *DLLLogger) LogPolling(keyFound bool, key string, keyType string) {
+ if keyFound {
+ l.LogStatus(1, fmt.Sprintf("找到%s密钥: %s", keyType, key))
+ } else {
+ l.LogStatus(0, "轮询中...")
+ }
+}
+
+// LogCleanup 记录清理信息
+func (l *DLLLogger) LogCleanup() {
+ l.LogStatus(0, "DLL资源已清理")
+}
\ No newline at end of file
diff --git a/wx_key/dllmain.cpp b/wx_key/dllmain.cpp
new file mode 100644
index 0000000..71524d3
--- /dev/null
+++ b/wx_key/dllmain.cpp
@@ -0,0 +1,24 @@
+// 远程Hook控制器DLL入口点
+// 这个DLL在Flutter进程中运行,不会被注入到目标进程
+#include
+#define HOOK_EXPORTS
+
+#include "include/hook_controller.h"
+
+#pragma comment(lib, "Psapi.lib")
+#pragma comment(lib, "version.lib")
+
+// DLL 入口函数
+BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
+ switch (ul_reason_for_call) {
+ case DLL_PROCESS_ATTACH:
+ DisableThreadLibraryCalls(hModule);
+ break;
+
+ case DLL_PROCESS_DETACH:
+ // 清理资源
+ CleanupHook();
+ break;
+ }
+ return TRUE;
+}
\ No newline at end of file
diff --git a/wx_key/framework.h b/wx_key/framework.h
new file mode 100644
index 0000000..80cbbc9
--- /dev/null
+++ b/wx_key/framework.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容
+// Windows 头文件
+#include
diff --git a/wx_key/include/hook_controller.h b/wx_key/include/hook_controller.h
new file mode 100644
index 0000000..13f2943
--- /dev/null
+++ b/wx_key/include/hook_controller.h
@@ -0,0 +1,49 @@
+#ifndef HOOK_CONTROLLER_H
+#define HOOK_CONTROLLER_H
+
+#include
+
+#ifdef HOOK_EXPORTS
+#define HOOK_API extern "C" __declspec(dllexport)
+#else
+#define HOOK_API extern "C" __declspec(dllimport)
+#endif
+
+/**
+ * 初始化并安装Hook(轮询模式 - 无回调)
+ * @param targetPid 微信进程的PID
+ * @return 成功返回true,失败返回false
+ */
+HOOK_API bool InitializeHook(DWORD targetPid);
+
+/**
+ * 轮询检查是否有新的密钥数据(非阻塞)
+ * @param keyBuffer 输出缓冲区,用于接收密钥十六进制字符串(至少65字节)
+ * @param bufferSize keyBuffer的大小
+ * @return 如果有新数据返回true,否则返回false
+ */
+HOOK_API bool PollKeyData(char* keyBuffer, int bufferSize);
+
+/**
+ * 获取当前状态消息
+ * @param statusBuffer 输出缓冲区,用于接收状态消息(至少256字节)
+ * @param bufferSize statusBuffer的大小
+ * @param outLevel 输出状态级别 (0=info, 1=success, 2=error)
+ * @return 如果有新状态返回true,否则返回false
+ */
+HOOK_API bool GetStatusMessage(char* statusBuffer, int bufferSize, int* outLevel);
+
+/**
+ * 清理并卸载Hook
+ * @return 成功返回true,失败返回false
+ */
+HOOK_API bool CleanupHook();
+
+/**
+ * 获取最后一次错误信息
+ * @return 错误信息字符串
+ */
+HOOK_API const char* GetLastErrorMsg();
+
+#endif // HOOK_CONTROLLER_H
+
diff --git a/wx_key/include/ipc_manager.h b/wx_key/include/ipc_manager.h
new file mode 100644
index 0000000..d98a508
--- /dev/null
+++ b/wx_key/include/ipc_manager.h
@@ -0,0 +1,79 @@
+#ifndef IPC_MANAGER_H
+#define IPC_MANAGER_H
+
+#include
+#include
+#include
+#include
+
+// 共享内存数据结构
+#pragma pack(push, 1)
+struct SharedKeyData {
+ DWORD dataSize; // 数据大小
+ BYTE keyBuffer[32]; // 密钥数据(最大32字节)
+ DWORD sequenceNumber; // 序列号
+};
+#pragma pack(pop)
+
+// IPC管理器类(轮询模式 - 用于远程进程缓冲区读取)
+class IPCManager {
+public:
+ IPCManager();
+ ~IPCManager();
+
+ // 初始化IPC(控制器端 - 轮询模式)
+ bool Initialize(const std::string& uniqueId);
+
+ // 设置远程缓冲区地址(目标进程中的地址)
+ void SetRemoteBuffer(HANDLE hProcess, PVOID remoteBufferAddr);
+
+ // 清理资源
+ void Cleanup();
+
+ // 设置数据接收回调
+ void SetDataCallback(std::function callback);
+
+ // 启动监听线程(轮询远程缓冲区)
+ bool StartListening();
+
+ // 停止监听线程
+ void StopListening();
+
+ // 获取共享内存地址(用于传递给Shellcode)
+ PVOID GetSharedMemoryAddress() const;
+
+ // 获取事件句柄(用于传递给Shellcode)
+ HANDLE GetEventHandle() const;
+
+ // 获取共享内存名称
+ std::string GetSharedMemoryName() const { return sharedMemoryName; }
+
+ // 获取事件名称
+ std::string GetEventName() const { return eventName; }
+
+private:
+ std::string uniqueId;
+ std::string sharedMemoryName;
+ std::string eventName;
+
+ HANDLE hMapFile;
+ HANDLE hEvent;
+ PVOID pSharedMemory;
+
+ // 远程进程轮询相关
+ HANDLE hTargetProcess;
+ PVOID pRemoteBuffer;
+ DWORD lastSequenceNumber;
+
+ HANDLE hListeningThread;
+ std::atomic shouldStopListening;
+
+ std::function dataCallback;
+
+ // 监听线程函数(轮询模式)
+ static DWORD WINAPI ListeningThreadProc(LPVOID lpParam);
+ void ListeningLoop();
+};
+
+#endif // IPC_MANAGER_H
+
diff --git a/wx_key/include/remote_hooker.h b/wx_key/include/remote_hooker.h
new file mode 100644
index 0000000..5762bf3
--- /dev/null
+++ b/wx_key/include/remote_hooker.h
@@ -0,0 +1,72 @@
+#ifndef REMOTE_HOOKER_H
+#define REMOTE_HOOKER_H
+
+#include
+#include
+#include "shellcode_builder.h"
+#include "remote_memory.h"
+
+// 远程Hook管理器
+class RemoteHooker {
+public:
+ RemoteHooker(HANDLE hProcess);
+ ~RemoteHooker();
+ void EnableHardwareBreakpointMode(bool enabled) { useHardwareBreakpoint = enabled; }
+
+ /**
+ * 安装远程Hook
+ * @param targetFunctionAddress 目标函数地址
+ * @param shellcodeConfig Shellcode配置
+ * @return 成功返回true
+ */
+ bool InstallHook(uintptr_t targetFunctionAddress, const ShellcodeConfig& shellcodeConfig);
+
+ /**
+ * 卸载Hook
+ * @return 成功返回true
+ */
+ bool UninstallHook();
+
+ /**
+ * 获取Trampoline地址(用于Shellcode配置)
+ * @return Trampoline地址
+ */
+ uintptr_t GetTrampolineAddress() const { return trampolineAddress; }
+
+ /**
+ * 获取远程Shellcode地址(供VEH模式使用)
+ */
+ uintptr_t GetRemoteShellcodeAddress() const { return remoteShellcodeAddress; }
+
+private:
+ HANDLE hProcess;
+
+ // Hook相关状态
+ uintptr_t targetAddress;
+ uintptr_t remoteShellcodeAddress;
+ uintptr_t trampolineAddress;
+ std::vector originalBytes;
+ bool isHookInstalled;
+ bool useHardwareBreakpoint{false};
+ RemoteMemory trampolineMemory;
+ RemoteMemory shellcodeMemory;
+
+ // 在远程进程分配内存
+ PVOID RemoteAllocate(SIZE_T size, DWORD protect);
+
+ // 读取/写入/保护
+ bool RemoteWrite(PVOID address, const void* data, SIZE_T size);
+ bool RemoteRead(PVOID address, void* buffer, SIZE_T size);
+ bool RemoteProtect(PVOID address, SIZE_T size, DWORD newProtect, DWORD* oldProtect);
+
+ // 创建Trampoline(保存原始指令)
+ bool CreateTrampoline(uintptr_t targetAddress);
+
+ // 计算需要备份的指令长度
+ size_t CalculateHookLength(const BYTE* code);
+
+ // 生成跳转指令(5字节短跳转或14字节长跳转)
+ std::vector GenerateJumpInstruction(uintptr_t from, uintptr_t to);
+};
+
+#endif // REMOTE_HOOKER_H
diff --git a/wx_key/include/remote_memory.h b/wx_key/include/remote_memory.h
new file mode 100644
index 0000000..b726f98
--- /dev/null
+++ b/wx_key/include/remote_memory.h
@@ -0,0 +1,106 @@
+#ifndef REMOTE_MEMORY_H
+#define REMOTE_MEMORY_H
+
+#include
+#include "syscalls.h"
+
+// RAII wrapper for remote allocations using NtAllocateVirtualMemory/NtFreeVirtualMemory
+class RemoteMemory {
+public:
+ RemoteMemory() = default;
+ RemoteMemory(HANDLE process, SIZE_T size, ULONG protect) {
+ allocate(process, size, protect);
+ }
+
+ RemoteMemory(const RemoteMemory&) = delete;
+ RemoteMemory& operator=(const RemoteMemory&) = delete;
+
+ RemoteMemory(RemoteMemory&& other) noexcept {
+ moveFrom(std::move(other));
+ }
+
+ RemoteMemory& operator=(RemoteMemory&& other) noexcept {
+ if (this != &other) {
+ reset();
+ moveFrom(std::move(other));
+ }
+ return *this;
+ }
+
+ ~RemoteMemory() {
+ reset();
+ }
+
+ bool allocate(HANDLE process, SIZE_T size, ULONG protect) {
+ reset();
+ hProcess = process;
+ sizeBytes = size;
+ base = nullptr;
+ SIZE_T regionSize = size;
+ NTSTATUS status = IndirectSyscalls::NtAllocateVirtualMemory(
+ hProcess,
+ &base,
+ 0,
+ ®ionSize,
+ MEM_COMMIT | MEM_RESERVE,
+ protect
+ );
+ if (status != STATUS_SUCCESS) {
+ base = nullptr;
+ sizeBytes = 0;
+ return false;
+ }
+ return true;
+ }
+
+ void reset() {
+ if (base) {
+ SIZE_T regionSize = 0;
+ PVOID addr = base;
+ IndirectSyscalls::NtFreeVirtualMemory(hProcess, &addr, ®ionSize, MEM_RELEASE);
+ base = nullptr;
+ sizeBytes = 0;
+ hProcess = nullptr;
+ }
+ }
+
+ bool protect(ULONG newProtect, ULONG* oldProtect = nullptr) {
+ if (!base || sizeBytes == 0) {
+ return false;
+ }
+ PVOID addr = base;
+ SIZE_T regionSize = sizeBytes;
+ ULONG oldProt = 0;
+ NTSTATUS status = IndirectSyscalls::NtProtectVirtualMemory(
+ hProcess,
+ &addr,
+ ®ionSize,
+ newProtect,
+ &oldProt
+ );
+ if (oldProtect) {
+ *oldProtect = oldProt;
+ }
+ return status == STATUS_SUCCESS;
+ }
+
+ PVOID get() const { return base; }
+ SIZE_T size() const { return sizeBytes; }
+ bool valid() const { return base != nullptr; }
+
+private:
+ void moveFrom(RemoteMemory&& other) {
+ hProcess = other.hProcess;
+ base = other.base;
+ sizeBytes = other.sizeBytes;
+ other.hProcess = nullptr;
+ other.base = nullptr;
+ other.sizeBytes = 0;
+ }
+
+ HANDLE hProcess{ nullptr };
+ PVOID base{ nullptr };
+ SIZE_T sizeBytes{ 0 };
+};
+
+#endif // REMOTE_MEMORY_H
diff --git a/wx_key/include/remote_scanner.h b/wx_key/include/remote_scanner.h
new file mode 100644
index 0000000..6a6ce75
--- /dev/null
+++ b/wx_key/include/remote_scanner.h
@@ -0,0 +1,69 @@
+#ifndef REMOTE_SCANNER_H
+#define REMOTE_SCANNER_H
+
+#include
+#include
+#include
+
+// 远程进程信息
+struct RemoteModuleInfo {
+ HMODULE baseAddress;
+ SIZE_T imageSize;
+ std::string moduleName;
+};
+
+// 远程特征码扫描器
+class RemoteScanner {
+public:
+ RemoteScanner(HANDLE hProcess);
+ ~RemoteScanner();
+
+ // 获取远程进程的模块信息
+ bool GetRemoteModuleInfo(const std::string& moduleName, RemoteModuleInfo& outInfo);
+
+ // 在远程进程中查找特征码(单个结果)
+ uintptr_t FindPattern(const RemoteModuleInfo& moduleInfo, const BYTE* pattern, const char* mask);
+
+ // 在远程进程中查找特征码(所有结果)
+ std::vector FindAllPatterns(const RemoteModuleInfo& moduleInfo, const BYTE* pattern, const char* mask);
+
+ // 读取远程内存
+ bool ReadRemoteMemory(uintptr_t address, void* buffer, SIZE_T size);
+
+ // 获取微信版本号
+ std::string GetWeChatVersion();
+
+private:
+ HANDLE hProcess;
+
+ // 本地缓冲区,用于批量读取远程内存
+ std::vector scanBuffer;
+
+ // 内存匹配辅助函数
+ bool MatchPattern(const BYTE* data, const BYTE* pattern, const char* mask, size_t length);
+};
+
+// 微信版本配置
+struct WeChatVersionConfig {
+ std::string version; // 版本号
+ std::vector pattern; // 特征码
+ std::string mask; // 掩码
+ int offset; // 偏移量
+
+ WeChatVersionConfig(const std::string& ver, const std::vector& pat, const std::string& msk, int off)
+ : version(ver), pattern(pat), mask(msk), offset(off) {}
+};
+
+// 版本配置管理器
+class VersionConfigManager {
+public:
+ static void InitializeConfigs();
+ static const WeChatVersionConfig* GetConfigForVersion(const std::string& version);
+
+private:
+ static std::vector configs;
+ static bool initialized;
+};
+
+#endif // REMOTE_SCANNER_H
+
diff --git a/wx_key/include/remote_veh.h b/wx_key/include/remote_veh.h
new file mode 100644
index 0000000..418a37c
--- /dev/null
+++ b/wx_key/include/remote_veh.h
@@ -0,0 +1,28 @@
+#ifndef REMOTE_VEH_H
+#define REMOTE_VEH_H
+
+#include
+#include
+#include "remote_memory.h"
+
+struct RemoteVehConfig {
+ HANDLE hProcess;
+ uintptr_t targetAddress;
+ uintptr_t shellcodeAddress;
+};
+
+// 工具:在远程进程注册VEH,并为所有线程设置硬件断点
+// 返回句柄和用于卸载的必要信息
+struct RemoteVehHandle {
+ PVOID vehHandle;
+ PVOID handlerCode;
+ PVOID dataBlock;
+ uintptr_t unregStubAddress;
+ RemoteMemory remoteMemory;
+ bool installed;
+};
+
+RemoteVehHandle InstallRemoteVeh(const RemoteVehConfig& cfg);
+void UninstallRemoteVeh(const RemoteVehConfig& cfg, RemoteVehHandle& handle);
+
+#endif // REMOTE_VEH_H
diff --git a/wx_key/include/shellcode_builder.h b/wx_key/include/shellcode_builder.h
new file mode 100644
index 0000000..e195ac7
--- /dev/null
+++ b/wx_key/include/shellcode_builder.h
@@ -0,0 +1,37 @@
+#ifndef SHELLCODE_BUILDER_H
+#define SHELLCODE_BUILDER_H
+
+#include
+#include
+#include
+
+// Shellcode配置
+struct ShellcodeConfig {
+ PVOID sharedMemoryAddress; // 共享内存地址
+ HANDLE eventHandle; // 事件句柄
+ uintptr_t trampolineAddress; // Trampoline地址(原始函数继续执行的地址)
+ bool enableStackSpoofing{false}; // 是否启用堆栈伪造
+ uintptr_t spoofStackPointer{0}; // 伪造栈指针(指向伪栈顶)
+};
+
+// Shellcode构建器
+class ShellcodeBuilder {
+public:
+ ShellcodeBuilder();
+ ~ShellcodeBuilder();
+
+ // 构建Hook Shellcode
+ std::vector BuildHookShellcode(const ShellcodeConfig& config);
+
+ // 获取Shellcode大小
+ size_t GetShellcodeSize() const;
+
+private:
+ std::vector shellcode;
+
+ // 清除Shellcode
+ void Clear();
+};
+
+#endif // SHELLCODE_BUILDER_H
+
diff --git a/wx_key/include/string_obfuscator.h b/wx_key/include/string_obfuscator.h
new file mode 100644
index 0000000..898d4d5
--- /dev/null
+++ b/wx_key/include/string_obfuscator.h
@@ -0,0 +1,61 @@
+ #ifndef STRING_OBFUSCATOR_H
+ #define STRING_OBFUSCATOR_H
+
+ #include
+ #include
+
+ // 编译时字符串加密 - XOR加密
+ template
+ class ObfuscatedString {
+ private:
+ std::array data;
+
+ constexpr char decrypt_char(char c, size_t i) const {
+ return c ^ static_cast(0xAA + (i % 256));
+ }
+
+ public:
+ template
+ constexpr ObfuscatedString(const char* str, std::index_sequence)
+ : data{ {static_cast(str[I] ^ static_cast(0xAA + (I % 256)))...} } {}
+
+ std::string decrypt() const {
+ std::string result;
+ if (N > 0) {
+ result.reserve(N - 1);
+ for (size_t i = 0; i < N - 1; ++i) {
+ result.push_back(decrypt_char(data[i], i));
+ }
+ }
+ return result;
+ }
+ };
+
+ // 辅助宏定义
+ #define OBFUSCATE_STR(str) \
+ ([]() { \
+ constexpr auto size = sizeof(str); \
+ return ObfuscatedString(str, std::make_index_sequence{}); \
+ }().decrypt())
+
+ // 常用字符串的加密版本
+ namespace ObfuscatedStrings {
+ inline std::string GetSharedMemoryName() {
+ return OBFUSCATE_STR("Global\\WxKeySharedMemory_{GUID}");
+ }
+
+ inline std::string GetEventName() {
+ return OBFUSCATE_STR("Global\\WxKeyEvent_{GUID}");
+ }
+
+ inline std::string GetWeixinDllName() {
+ return OBFUSCATE_STR("Weixin.dll");
+ }
+
+ inline std::string GetNtdllName() {
+ return OBFUSCATE_STR("ntdll.dll");
+ }
+ }
+
+ #endif // STRING_OBFUSCATOR_H
+
diff --git a/wx_key/include/syscalls.h b/wx_key/include/syscalls.h
new file mode 100644
index 0000000..b401811
--- /dev/null
+++ b/wx_key/include/syscalls.h
@@ -0,0 +1,188 @@
+#ifndef SYSCALLS_H
+#define SYSCALLS_H
+
+#include
+#include
+#include
+
+// Extended NT definitions
+#ifndef STATUS_SUCCESS
+#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
+#endif
+
+#ifndef STATUS_UNSUCCESSFUL
+#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L)
+#endif
+
+// CLIENT_ID structure (if not defined)
+#ifndef _CLIENT_ID_DEFINED
+#define _CLIENT_ID_DEFINED
+typedef struct _MY_CLIENT_ID {
+ PVOID UniqueProcess;
+ PVOID UniqueThread;
+} MY_CLIENT_ID, *PMY_CLIENT_ID;
+#endif
+
+// OBJECT_ATTRIBUTES structure (simplified)
+#ifndef _MY_OBJECT_ATTRIBUTES_DEFINED
+#define _MY_OBJECT_ATTRIBUTES_DEFINED
+typedef struct _MY_OBJECT_ATTRIBUTES {
+ ULONG Length;
+ HANDLE RootDirectory;
+ PVOID ObjectName;
+ ULONG Attributes;
+ PVOID SecurityDescriptor;
+ PVOID SecurityQualityOfService;
+} MY_OBJECT_ATTRIBUTES, *PMY_OBJECT_ATTRIBUTES;
+#endif
+
+// NT函数原型
+typedef NTSTATUS(NTAPI* pNtOpenProcess)(
+ PHANDLE ProcessHandle,
+ ACCESS_MASK DesiredAccess,
+ PMY_OBJECT_ATTRIBUTES ObjectAttributes,
+ PMY_CLIENT_ID ClientId
+);
+
+typedef NTSTATUS(NTAPI* pNtReadVirtualMemory)(
+ HANDLE ProcessHandle,
+ PVOID BaseAddress,
+ PVOID Buffer,
+ SIZE_T BufferSize,
+ PSIZE_T NumberOfBytesRead
+);
+
+typedef NTSTATUS(NTAPI* pNtWriteVirtualMemory)(
+ HANDLE ProcessHandle,
+ PVOID BaseAddress,
+ PVOID Buffer,
+ SIZE_T BufferSize,
+ PSIZE_T NumberOfBytesWritten
+);
+
+typedef NTSTATUS(NTAPI* pNtAllocateVirtualMemory)(
+ HANDLE ProcessHandle,
+ PVOID* BaseAddress,
+ ULONG_PTR ZeroBits,
+ PSIZE_T RegionSize,
+ ULONG AllocationType,
+ ULONG Protect
+);
+
+typedef NTSTATUS(NTAPI* pNtFreeVirtualMemory)(
+ HANDLE ProcessHandle,
+ PVOID* BaseAddress,
+ PSIZE_T RegionSize,
+ ULONG FreeType
+);
+
+typedef NTSTATUS(NTAPI* pNtProtectVirtualMemory)(
+ HANDLE ProcessHandle,
+ PVOID* BaseAddress,
+ PSIZE_T RegionSize,
+ ULONG NewProtect,
+ PULONG OldProtect
+);
+
+typedef NTSTATUS(NTAPI* pNtQueryInformationProcess)(
+ HANDLE ProcessHandle,
+ PROCESSINFOCLASS ProcessInformationClass,
+ PVOID ProcessInformation,
+ ULONG ProcessInformationLength,
+ PULONG ReturnLength
+);
+
+// 间接系统调用类
+class IndirectSyscalls {
+public:
+ static bool Initialize();
+ static void Cleanup();
+
+ // 封装的系统调用函数
+ static NTSTATUS NtOpenProcess(
+ PHANDLE ProcessHandle,
+ ACCESS_MASK DesiredAccess,
+ PMY_OBJECT_ATTRIBUTES ObjectAttributes,
+ PMY_CLIENT_ID ClientId
+ );
+
+ static NTSTATUS NtReadVirtualMemory(
+ HANDLE ProcessHandle,
+ PVOID BaseAddress,
+ PVOID Buffer,
+ SIZE_T BufferSize,
+ PSIZE_T NumberOfBytesRead
+ );
+
+ static NTSTATUS NtWriteVirtualMemory(
+ HANDLE ProcessHandle,
+ PVOID BaseAddress,
+ PVOID Buffer,
+ SIZE_T BufferSize,
+ PSIZE_T NumberOfBytesWritten
+ );
+
+ static NTSTATUS NtAllocateVirtualMemory(
+ HANDLE ProcessHandle,
+ PVOID* BaseAddress,
+ ULONG_PTR ZeroBits,
+ PSIZE_T RegionSize,
+ ULONG AllocationType,
+ ULONG Protect
+ );
+
+ static NTSTATUS NtFreeVirtualMemory(
+ HANDLE ProcessHandle,
+ PVOID* BaseAddress,
+ PSIZE_T RegionSize,
+ ULONG FreeType
+ );
+
+ static NTSTATUS NtProtectVirtualMemory(
+ HANDLE ProcessHandle,
+ PVOID* BaseAddress,
+ PSIZE_T RegionSize,
+ ULONG NewProtect,
+ PULONG OldProtect
+ );
+
+ static NTSTATUS NtQueryInformationProcess(
+ HANDLE ProcessHandle,
+ PROCESSINFOCLASS ProcessInformationClass,
+ PVOID ProcessInformation,
+ ULONG ProcessInformationLength,
+ PULONG ReturnLength
+ );
+
+private:
+ static bool initialized;
+
+ // 动态获取的函数指针
+ static pNtOpenProcess fnNtOpenProcess;
+ static pNtReadVirtualMemory fnNtReadVirtualMemory;
+ static pNtWriteVirtualMemory fnNtWriteVirtualMemory;
+ static pNtAllocateVirtualMemory fnNtAllocateVirtualMemory;
+ static pNtFreeVirtualMemory fnNtFreeVirtualMemory;
+ static pNtProtectVirtualMemory fnNtProtectVirtualMemory;
+ static pNtQueryInformationProcess fnNtQueryInformationProcess;
+
+ // 直接SSN调用的stub指针
+ static pNtOpenProcess scNtOpenProcess;
+ static pNtReadVirtualMemory scNtReadVirtualMemory;
+ static pNtWriteVirtualMemory scNtWriteVirtualMemory;
+ static pNtAllocateVirtualMemory scNtAllocateVirtualMemory;
+ static pNtFreeVirtualMemory scNtFreeVirtualMemory;
+ static pNtProtectVirtualMemory scNtProtectVirtualMemory;
+ static pNtQueryInformationProcess scNtQueryInformationProcess;
+
+ // 辅助函数:从ntdll解析函数地址
+ template
+ static bool ResolveFunction(const char* functionName, T& functionPointer);
+
+ // 辅助:从ntdll stub提取SSN并构建直调stub
+ static uint32_t ExtractSyscallNumber(void* fnAddress);
+ static void* CreateSyscallStub(uint32_t ssn);
+};
+
+#endif // SYSCALLS_H
+
diff --git a/wx_key/include/veh_hook_manager.h b/wx_key/include/veh_hook_manager.h
new file mode 100644
index 0000000..3f62abd
--- /dev/null
+++ b/wx_key/include/veh_hook_manager.h
@@ -0,0 +1,32 @@
+#ifndef VEH_HOOK_MANAGER_H
+#define VEH_HOOK_MANAGER_H
+
+#include
+#include
+#include
+#include
+
+// 硬件断点 + VEH 管理(当前进程,多线程遍历)
+class VehHookManager {
+public:
+ VehHookManager();
+ ~VehHookManager();
+
+ // 安装硬件断点并注册VEH
+ bool Install(uintptr_t address, std::function callback);
+ // 卸载
+ void Uninstall();
+
+private:
+ static LONG CALLBACK VectoredHandler(EXCEPTION_POINTERS* info);
+ bool SetHardwareBreakpoint(uintptr_t address);
+ void ClearHardwareBreakpoint();
+
+ uintptr_t targetAddress;
+ void* vehHandle;
+ std::function userCallback;
+ std::unordered_map originalContexts;
+ bool installed;
+};
+
+#endif // VEH_HOOK_MANAGER_H
diff --git a/wx_key/pch.cpp b/wx_key/pch.cpp
new file mode 100644
index 0000000..b6fb8f4
--- /dev/null
+++ b/wx_key/pch.cpp
@@ -0,0 +1,5 @@
+// pch.cpp: 与预编译标头对应的源文件
+
+#include "pch.h"
+
+// 当使用预编译的头时,需要使用此源文件,编译才能成功。
diff --git a/wx_key/pch.h b/wx_key/pch.h
new file mode 100644
index 0000000..9660927
--- /dev/null
+++ b/wx_key/pch.h
@@ -0,0 +1,13 @@
+// pch.h: 这是预编译标头文件。
+// 下方列出的文件仅编译一次,提高了将来生成的生成性能。
+// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。
+// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。
+// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。
+
+#ifndef PCH_H
+#define PCH_H
+
+// 添加要在此处预编译的标头
+#include "framework.h"
+
+#endif //PCH_H
diff --git a/wx_key/src/hook_controller.cpp b/wx_key/src/hook_controller.cpp
new file mode 100644
index 0000000..6cd414b
--- /dev/null
+++ b/wx_key/src/hook_controller.cpp
@@ -0,0 +1,490 @@
+#define HOOK_EXPORTS
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "../include/hook_controller.h"
+#include "../include/syscalls.h"
+#include "../include/remote_scanner.h"
+#include "../include/ipc_manager.h"
+#include "../include/remote_hooker.h"
+#include "../include/shellcode_builder.h"
+#include "../include/string_obfuscator.h"
+#include "../include/remote_veh.h"
+#include "../include/remote_memory.h"
+
+#pragma execution_character_set("utf-8")
+
+// 全局状态
+namespace {
+ bool InitializeContext(DWORD targetPid);
+ void CleanupContext();
+ struct StatusMessage {
+ std::string message;
+ int level;
+ };
+
+ struct HookContext {
+ HANDLE hProcess{ nullptr };
+ std::unique_ptr ipc;
+ std::unique_ptr hooker;
+ RemoteMemory remoteData;
+ RemoteMemory spoofStack;
+ CRITICAL_SECTION dataLock{};
+ bool csInitialized{ false };
+ std::string pendingKeyData;
+ bool hasNewKey{ false };
+ std::vector statusQueue;
+ bool initialized{ false };
+
+ void InitLock() {
+ if (!csInitialized) {
+ InitializeCriticalSection(&dataLock);
+ csInitialized = true;
+ }
+ }
+
+ void FreeLock() {
+ if (csInitialized) {
+ DeleteCriticalSection(&dataLock);
+ csInitialized = false;
+ }
+ }
+
+ void ResetDataQueues() {
+ pendingKeyData.clear();
+ hasNewKey = false;
+ statusQueue.clear();
+ }
+ };
+
+ HookContext g_ctx;
+ std::string g_lastError;
+ const char* kSupportedRange = "4.0.x 及以上 4.x 版本";
+
+ std::string WideToUtf8(const std::wstring& wide) {
+ if (wide.empty()) {
+ return std::string();
+ }
+ int sizeNeeded = WideCharToMultiByte(
+ CP_UTF8,
+ 0,
+ wide.c_str(),
+ static_cast(wide.size()),
+ nullptr,
+ 0,
+ nullptr,
+ nullptr
+ );
+ if (sizeNeeded <= 0) {
+ return std::string();
+ }
+ std::string utf8(sizeNeeded, 0);
+ WideCharToMultiByte(
+ CP_UTF8,
+ 0,
+ wide.c_str(),
+ static_cast(wide.size()),
+ reinterpret_cast(&utf8[0]),
+ sizeNeeded,
+ nullptr,
+ nullptr
+ );
+ return utf8;
+ }
+
+ // 生成唯一ID
+ std::string GenerateUniqueId(DWORD pid) {
+ std::stringstream ss;
+ ss << std::hex << pid << "_" << GetTickCount64();
+ return ss.str();
+ }
+
+ // 发送状态信息
+ void SendStatus(const std::string& message, int level) {
+ // 统一由外层日志系统加等级前缀
+ const std::string& prefixed = message;
+ if (g_ctx.csInitialized) {
+ EnterCriticalSection(&g_ctx.dataLock);
+ }
+ g_ctx.statusQueue.push_back({prefixed, level});
+ // 限制队列大小
+ if (g_ctx.statusQueue.size() > 100) {
+ g_ctx.statusQueue.erase(g_ctx.statusQueue.begin());
+ }
+ if (g_ctx.csInitialized) {
+ LeaveCriticalSection(&g_ctx.dataLock);
+ }
+ }
+
+ std::string GetSystemErrorMessage(DWORD errorCode) {
+ if (errorCode == 0) {
+ return std::string();
+ }
+
+ LPWSTR buffer = nullptr;
+ DWORD length = FormatMessageW(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ nullptr,
+ errorCode,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ reinterpret_cast(&buffer),
+ 0,
+ nullptr
+ );
+
+ std::string message;
+ if (length && buffer) {
+ std::wstring wideMessage(buffer, length);
+ while (!wideMessage.empty() && (wideMessage.back() == L'\r' || wideMessage.back() == L'\n')) {
+ wideMessage.pop_back();
+ }
+ message = WideToUtf8(wideMessage);
+ }
+
+ if (buffer) {
+ LocalFree(buffer);
+ }
+ return message;
+ }
+
+ std::string FormatWin32Error(const std::string& baseMessage, DWORD errorCode) {
+ std::ostringstream oss;
+ oss << baseMessage;
+ if (errorCode != 0) {
+ oss << " (code " << errorCode << ")";
+ std::string detail = GetSystemErrorMessage(errorCode);
+ if (!detail.empty()) {
+ oss << ": " << detail;
+ }
+ }
+ return oss.str();
+ }
+
+ std::string FormatNtStatusError(const std::string& baseMessage, NTSTATUS status) {
+ std::ostringstream oss;
+ oss << baseMessage << " (NTSTATUS 0x"
+ << std::uppercase << std::hex << std::setw(8) << std::setfill('0')
+ << static_cast(status) << ")";
+ return oss.str();
+ }
+
+ // 设置错误信息
+ void SetLastError(const std::string& error) {
+ g_lastError = error;
+ SendStatus(error, 2); // level 2 = error
+ }
+ // 数据回调处理(从IPC线程调用)
+ void OnDataReceived(const SharedKeyData& data) {
+ // Validate data
+ if (data.dataSize != 32) {
+ SendStatus("收到的密钥数据长度不正确", 2);
+ return;
+ }
+
+ // 转换为十六进制字符串
+ std::stringstream ss;
+ ss << std::hex << std::setfill('0');
+ for (DWORD i = 0; i < data.dataSize; i++) {
+ ss << std::setw(2) << static_cast(data.keyBuffer[i]);
+ }
+
+ std::string keyHex = ss.str();
+
+ // 存入队列
+ if (g_ctx.csInitialized) {
+ EnterCriticalSection(&g_ctx.dataLock);
+ }
+ g_ctx.pendingKeyData = keyHex;
+ g_ctx.hasNewKey = true;
+ if (g_ctx.csInitialized) {
+ LeaveCriticalSection(&g_ctx.dataLock);
+ }
+
+ SendStatus("已成功接收到密钥", 1); // level 1 = success
+ }
+}
+
+namespace {
+ bool InitializeContext(DWORD targetPid) {
+ if (g_ctx.initialized) {
+ SetLastError("Hook已经初始化");
+ return false;
+ }
+
+ g_ctx.InitLock();
+ g_ctx.ResetDataQueues();
+
+ SendStatus("开始初始化Hook系统...", 0);
+
+ // 1. 初始化系统调用
+ SendStatus("正在初始化系统调用...", 0);
+ if (!IndirectSyscalls::Initialize()) {
+ DWORD errorCode = GetLastError();
+ SetLastError(FormatWin32Error("初始化间接系统调用失败", errorCode));
+ g_ctx.FreeLock();
+ return false;
+ }
+
+ // 2. 打开进程
+ SendStatus("正在打开目标进程...", 0);
+ MY_OBJECT_ATTRIBUTES objAttr;
+ memset(&objAttr, 0, sizeof(MY_OBJECT_ATTRIBUTES));
+ objAttr.Length = sizeof(MY_OBJECT_ATTRIBUTES);
+
+ MY_CLIENT_ID clientId;
+ memset(&clientId, 0, sizeof(MY_CLIENT_ID));
+ clientId.UniqueProcess = (PVOID)(ULONG_PTR)targetPid;
+
+ HANDLE hProcess = NULL;
+ NTSTATUS status = IndirectSyscalls::NtOpenProcess(
+ &hProcess,
+ PROCESS_ALL_ACCESS,
+ &objAttr,
+ &clientId
+ );
+ g_ctx.hProcess = hProcess;
+ if (status != STATUS_SUCCESS || !g_ctx.hProcess) {
+ SetLastError(FormatNtStatusError("打开目标进程失败", status));
+ CleanupContext();
+ return false;
+ }
+
+ // 3. 获取微信版本
+ SendStatus("正在检测微信版本...", 0);
+ RemoteScanner scanner(g_ctx.hProcess);
+ std::string wechatVersion = scanner.GetWeChatVersion();
+ if (wechatVersion.empty()) {
+ SetLastError("获取微信版本失败,目标进程可能已退出");
+ CleanupContext();
+ return false;
+ }
+
+ {
+ std::stringstream versionMsg;
+ versionMsg << u8"检测到的微信版本: " << wechatVersion;
+ SendStatus(versionMsg.str(), 0);
+ }
+
+ // 4. 获取版本配置
+ const WeChatVersionConfig* config = VersionConfigManager::GetConfigForVersion(wechatVersion);
+ if (!config) {
+ std::string errorMsg = std::string(u8"不支持的微信版本: ") + wechatVersion + ",支持范围: " + kSupportedRange;
+ SetLastError(errorMsg);
+ CleanupContext();
+ return false;
+ }
+
+ // 5. 扫描函数
+ SendStatus("正在扫描目标函数...", 0);
+ std::string weixinDll = ObfuscatedStrings::GetWeixinDllName();
+ RemoteModuleInfo moduleInfo;
+
+ if (!scanner.GetRemoteModuleInfo(weixinDll, moduleInfo)) {
+ SetLastError("未找到Weixin.dll模块");
+ CleanupContext();
+ return false;
+ }
+
+ std::vector results = scanner.FindAllPatterns(
+ moduleInfo,
+ config->pattern.data(),
+ config->mask.c_str()
+ );
+
+ if (results.size() != 1) {
+ std::stringstream errorMsg;
+ errorMsg << u8"模式匹配失败,找到 " << results.size() << u8" 个结果";
+ SetLastError(errorMsg.str());
+ CleanupContext();
+ return false;
+ }
+
+ uintptr_t targetFunctionAddress = results[0] + config->offset;
+
+ {
+ std::stringstream addrMsg;
+ addrMsg << u8"目标函数地址: 0x" << std::hex << targetFunctionAddress;
+ SendStatus(addrMsg.str(), 0);
+ }
+
+ // 6. 在目标进程中分配数据缓冲区(用于存放密钥)
+ SendStatus("正在分配远程数据缓冲区...", 0);
+ if (!g_ctx.remoteData.allocate(g_ctx.hProcess, sizeof(SharedKeyData), PAGE_READWRITE)) {
+ SetLastError("分配远程数据缓冲区失败");
+ CleanupContext();
+ return false;
+ }
+
+ // 6.1 分配伪栈
+ SendStatus("正在分配远程伪栈...", 0);
+ const SIZE_T spoofStackSize = 0x8000; // 32KB 伪栈
+ if (!g_ctx.spoofStack.allocate(g_ctx.hProcess, spoofStackSize, PAGE_READWRITE)) {
+ SetLastError("分配远程伪栈失败");
+ CleanupContext();
+ return false;
+ }
+ uintptr_t spoofStackTop = reinterpret_cast(g_ctx.spoofStack.get()) + spoofStackSize - 0x20; // 留出对齐空间
+
+ // 7. 初始化IPC
+ SendStatus("正在初始化IPC通信...", 0);
+ std::string uniqueId = GenerateUniqueId(targetPid);
+ g_ctx.ipc = std::make_unique();
+ if (!g_ctx.ipc->Initialize(uniqueId)) {
+ DWORD ipcError = GetLastError();
+ SetLastError(FormatWin32Error("初始化IPC通信失败", ipcError));
+ CleanupContext();
+ return false;
+ }
+
+ g_ctx.ipc->SetRemoteBuffer(g_ctx.hProcess, g_ctx.remoteData.get());
+ g_ctx.ipc->SetDataCallback(OnDataReceived);
+ if (!g_ctx.ipc->StartListening()) {
+ DWORD ipcError = GetLastError();
+ SetLastError(FormatWin32Error("启动IPC监听失败", ipcError));
+ CleanupContext();
+ return false;
+ }
+
+ // 8. 创建hook
+ SendStatus("正在准备安装Hook...", 0);
+ g_ctx.hooker = std::make_unique(g_ctx.hProcess);
+ g_ctx.hooker->EnableHardwareBreakpointMode(false); // !!暂时不用硬件断点+VEH,测试期间先用稳定的Inline Hook!!
+
+ // 9. 配置Shellcode
+ ShellcodeConfig shellcodeConfig{};
+ shellcodeConfig.sharedMemoryAddress = g_ctx.remoteData.get(); // 使用远程分配的地址
+ shellcodeConfig.eventHandle = nullptr; // 不再使用事件,改用轮询
+ shellcodeConfig.trampolineAddress = 0; // 将由RemoteHooker填充
+ shellcodeConfig.enableStackSpoofing = true; // 强制开启堆栈伪造
+ shellcodeConfig.spoofStackPointer = spoofStackTop;
+
+ // 10. 安装hook
+ SendStatus("正在安装远程Hook...", 0);
+ if (!g_ctx.hooker->InstallHook(targetFunctionAddress, shellcodeConfig)) {
+ DWORD hookError = GetLastError();
+ SetLastError(FormatWin32Error("安装Hook失败", hookError));
+ CleanupContext();
+ return false;
+ }
+
+ g_ctx.initialized = true;
+ SendStatus("Hook安装成功,现在登录微信...", 1);
+ return true;
+ }
+
+ void CleanupContext() {
+ if (g_ctx.hooker) {
+ g_ctx.hooker->UninstallHook();
+ g_ctx.hooker.reset();
+ }
+
+ if (g_ctx.ipc) {
+ g_ctx.ipc->StopListening();
+ g_ctx.ipc->Cleanup();
+ g_ctx.ipc.reset();
+ }
+
+ g_ctx.remoteData.reset();
+ g_ctx.spoofStack.reset();
+
+ if (g_ctx.hProcess) {
+ CloseHandle(g_ctx.hProcess);
+ g_ctx.hProcess = nullptr;
+ }
+
+ IndirectSyscalls::Cleanup();
+
+ if (g_ctx.csInitialized) {
+ EnterCriticalSection(&g_ctx.dataLock);
+ g_ctx.ResetDataQueues();
+ LeaveCriticalSection(&g_ctx.dataLock);
+ g_ctx.FreeLock();
+ }
+
+ g_ctx.initialized = false;
+ }
+}
+
+// 导出函数
+HOOK_API bool InitializeHook(DWORD targetPid) {
+ return InitializeContext(targetPid);
+}
+
+HOOK_API bool CleanupHook() {
+ if (!g_ctx.initialized) {
+ return true;
+ }
+ SendStatus("正在清理Hook...", 0);
+ CleanupContext();
+ return true;
+}
+
+HOOK_API bool PollKeyData(char* keyBuffer, int bufferSize) {
+ if (!g_ctx.initialized || !keyBuffer || bufferSize < 65) {
+ return false;
+ }
+
+ if (g_ctx.csInitialized) {
+ EnterCriticalSection(&g_ctx.dataLock);
+ }
+
+ if (!g_ctx.hasNewKey) {
+ if (g_ctx.csInitialized) {
+ LeaveCriticalSection(&g_ctx.dataLock);
+ }
+ return false;
+ }
+
+ size_t copyLen = (g_ctx.pendingKeyData.length() < static_cast(bufferSize - 1)) ? g_ctx.pendingKeyData.length() : static_cast(bufferSize - 1);
+ memcpy(keyBuffer, g_ctx.pendingKeyData.c_str(), copyLen);
+ keyBuffer[copyLen] = '\0';
+ g_ctx.hasNewKey = false;
+ g_ctx.pendingKeyData.clear();
+
+ if (g_ctx.csInitialized) {
+ LeaveCriticalSection(&g_ctx.dataLock);
+ }
+
+ return true;
+}
+
+HOOK_API bool GetStatusMessage(char* statusBuffer, int bufferSize, int* outLevel) {
+ if (!g_ctx.initialized || !statusBuffer || bufferSize < 256 || !outLevel) {
+ return false;
+ }
+
+ if (g_ctx.csInitialized) {
+ EnterCriticalSection(&g_ctx.dataLock);
+ }
+
+ if (g_ctx.statusQueue.empty()) {
+ if (g_ctx.csInitialized) {
+ LeaveCriticalSection(&g_ctx.dataLock);
+ }
+ return false;
+ }
+
+ StatusMessage msg = g_ctx.statusQueue.front();
+ g_ctx.statusQueue.erase(g_ctx.statusQueue.begin());
+
+ if (g_ctx.csInitialized) {
+ LeaveCriticalSection(&g_ctx.dataLock);
+ }
+
+ size_t copyLen = (msg.message.length() < static_cast(bufferSize - 1)) ? msg.message.length() : static_cast(bufferSize - 1);
+ memcpy(statusBuffer, msg.message.c_str(), copyLen);
+ statusBuffer[copyLen] = '\0';
+ *outLevel = msg.level;
+
+ return true;
+}
+
+HOOK_API const char* GetLastErrorMsg() {
+ return g_lastError.c_str();
+}
diff --git a/wx_key/src/ipc_manager.cpp b/wx_key/src/ipc_manager.cpp
new file mode 100644
index 0000000..0e85ccf
--- /dev/null
+++ b/wx_key/src/ipc_manager.cpp
@@ -0,0 +1,272 @@
+#include "../include/ipc_manager.h"
+#include "../include/string_obfuscator.h"
+#include
+#include
+#include
+#include
+
+IPCManager::IPCManager()
+ : hMapFile(nullptr)
+ , hEvent(nullptr)
+ , pSharedMemory(nullptr)
+ , hTargetProcess(nullptr)
+ , pRemoteBuffer(nullptr)
+ , lastSequenceNumber(0)
+ , hListeningThread(nullptr)
+ , shouldStopListening(false)
+{
+}
+
+IPCManager::~IPCManager() {
+ Cleanup();
+}
+
+bool IPCManager::Initialize(const std::string& uniqueId) {
+ // 在唯一标识后追加随机片段,进一步降低命名的可预测性
+ std::random_device rd;
+ std::mt19937_64 gen(rd());
+ std::uniform_int_distribution dist;
+ unsigned long long randSuffix = dist(gen);
+
+ std::stringstream uniq;
+ uniq << uniqueId << "_" << std::hex << randSuffix;
+ this->uniqueId = uniq.str();
+
+ // 生成唯一的共享内存和事件名称
+ std::string baseMemName = ObfuscatedStrings::GetSharedMemoryName();
+ std::string baseEventName = ObfuscatedStrings::GetEventName();
+
+ // 替换{GUID}为实际的uniqueId
+ size_t pos = baseMemName.find("{GUID}");
+ if (pos != std::string::npos) {
+ baseMemName.replace(pos, 6, uniqueId);
+ }
+
+ pos = baseEventName.find("{GUID}");
+ if (pos != std::string::npos) {
+ baseEventName.replace(pos, 6, uniqueId);
+ }
+
+ auto tryCreateResources = [&](const std::string& memName, const std::string& evtName) -> bool {
+ HANDLE mapHandle = CreateFileMappingA(
+ INVALID_HANDLE_VALUE,
+ nullptr,
+ PAGE_READWRITE,
+ 0,
+ sizeof(SharedKeyData),
+ memName.c_str()
+ );
+
+ if (mapHandle == nullptr) {
+ return false;
+ }
+
+ PVOID sharedView = MapViewOfFile(
+ mapHandle,
+ FILE_MAP_ALL_ACCESS,
+ 0,
+ 0,
+ sizeof(SharedKeyData)
+ );
+
+ if (sharedView == nullptr) {
+ DWORD err = GetLastError();
+ CloseHandle(mapHandle);
+ SetLastError(err);
+ return false;
+ }
+
+ ZeroMemory(sharedView, sizeof(SharedKeyData));
+
+ // 创建事件对象(手动重置)
+ HANDLE eventHandle = CreateEventA(
+ nullptr,
+ TRUE, // 手动重置
+ FALSE, // 初始状态非信号
+ evtName.c_str()
+ );
+
+ if (eventHandle == nullptr) {
+ DWORD err = GetLastError();
+ UnmapViewOfFile(sharedView);
+ CloseHandle(mapHandle);
+ SetLastError(err);
+ return false;
+ }
+
+ hMapFile = mapHandle;
+ hEvent = eventHandle;
+ pSharedMemory = sharedView;
+ sharedMemoryName = memName;
+ eventName = evtName;
+ return true;
+ };
+
+ auto convertGlobalToLocal = [](const std::string& name) -> std::string {
+ const std::string globalPrefix = "Global\\";
+ if (name.rfind(globalPrefix, 0) == 0) {
+ return std::string("Local\\") + name.substr(globalPrefix.length());
+ }
+ return name;
+ };
+
+ if (tryCreateResources(baseMemName, baseEventName)) {
+ return true;
+ }
+
+ DWORD firstError = GetLastError();
+ bool needsFallback = (firstError == ERROR_ACCESS_DENIED || firstError == ERROR_PRIVILEGE_NOT_HELD);
+
+ if (needsFallback) {
+ std::string localMemName = convertGlobalToLocal(baseMemName);
+ std::string localEventName = convertGlobalToLocal(baseEventName);
+
+ if ((localMemName != baseMemName || localEventName != baseEventName) &&
+ tryCreateResources(localMemName, localEventName)) {
+ return true;
+ }
+ }
+
+ SetLastError(firstError);
+ return false;
+}
+
+void IPCManager::SetRemoteBuffer(HANDLE hProcess, PVOID remoteBufferAddr) {
+ hTargetProcess = hProcess;
+ pRemoteBuffer = remoteBufferAddr;
+ lastSequenceNumber = 0;
+}
+
+void IPCManager::Cleanup() {
+ StopListening();
+
+ if (pSharedMemory) {
+ UnmapViewOfFile(pSharedMemory);
+ pSharedMemory = nullptr;
+ }
+
+ if (hEvent) {
+ CloseHandle(hEvent);
+ hEvent = nullptr;
+ }
+
+ if (hMapFile) {
+ CloseHandle(hMapFile);
+ hMapFile = nullptr;
+ }
+}
+
+void IPCManager::SetDataCallback(std::function callback) {
+ dataCallback = callback;
+}
+
+bool IPCManager::StartListening() {
+ if (hListeningThread != nullptr) {
+ return true; // 已经在监听
+ }
+
+ shouldStopListening.store(false);
+ if (hEvent) {
+ ResetEvent(hEvent);
+ }
+ hListeningThread = CreateThread(
+ nullptr,
+ 0,
+ ListeningThreadProc,
+ this,
+ 0,
+ nullptr
+ );
+
+ return (hListeningThread != nullptr);
+}
+
+void IPCManager::StopListening() {
+ if (hListeningThread == nullptr) {
+ return;
+ }
+
+ shouldStopListening.store(true);
+ SetEvent(hEvent); // 唤醒等待线程
+
+ // 等待线程退出
+ WaitForSingleObject(hListeningThread, 5000);
+ CloseHandle(hListeningThread);
+ hListeningThread = nullptr;
+}
+
+PVOID IPCManager::GetSharedMemoryAddress() const {
+ return pSharedMemory;
+}
+
+HANDLE IPCManager::GetEventHandle() const {
+ return hEvent;
+}
+
+DWORD WINAPI IPCManager::ListeningThreadProc(LPVOID lpParam) {
+ IPCManager* pThis = static_cast(lpParam);
+ pThis->ListeningLoop();
+ return 0;
+}
+
+void IPCManager::ListeningLoop() {
+ // 轮询模式:周期性读取远程进程中的缓冲区
+ while (!shouldStopListening.load()) {
+ // 添加轻微抖动,避免稳定的轮询间隔特征
+ DWORD jitteredWait = 80 + (GetTickCount() & 0x3F); // 80-143ms
+ DWORD waitResult = WaitForSingleObject(hEvent, jitteredWait);
+ if (waitResult == WAIT_OBJECT_0) {
+ if (shouldStopListening.load()) {
+ break;
+ }
+ ResetEvent(hEvent);
+ }
+
+ if (!hTargetProcess || !pRemoteBuffer) {
+ continue;
+ }
+
+ // 从远程进程读取数据
+ SharedKeyData keyData;
+ ZeroMemory(&keyData, sizeof(keyData));
+
+ SIZE_T bytesRead = 0;
+ BOOL readResult = ReadProcessMemory(
+ hTargetProcess,
+ pRemoteBuffer,
+ &keyData,
+ sizeof(SharedKeyData),
+ &bytesRead
+ );
+
+ if (readResult && bytesRead == sizeof(SharedKeyData)) {
+ // 检查是否有新数据(通过序列号判断)
+ if (keyData.dataSize > 0 &&
+ keyData.dataSize <= 32 &&
+ keyData.sequenceNumber != lastSequenceNumber &&
+ keyData.sequenceNumber != 0) {
+
+ // 更新序列号
+ lastSequenceNumber = keyData.sequenceNumber;
+
+ // 调用回调函数
+ if (dataCallback) {
+ dataCallback(keyData);
+ }
+
+ // 清空远程缓冲区(防止重复读取)
+ SharedKeyData zeroData;
+ ZeroMemory(&zeroData, sizeof(zeroData));
+ SIZE_T bytesWritten = 0;
+ WriteProcessMemory(
+ hTargetProcess,
+ pRemoteBuffer,
+ &zeroData,
+ sizeof(SharedKeyData),
+ &bytesWritten
+ );
+ }
+ }
+ }
+}
+
diff --git a/wx_key/src/remote_hooker.cpp b/wx_key/src/remote_hooker.cpp
new file mode 100644
index 0000000..4fee8a5
--- /dev/null
+++ b/wx_key/src/remote_hooker.cpp
@@ -0,0 +1,418 @@
+#include "../include/remote_hooker.h"
+#include "../include/syscalls.h"
+#include "../include/shellcode_builder.h"
+#include
+#include
+
+// 简单的x64反汇编长度检测器
+// 支持常见指令,用于计算需要备份多少字节
+namespace X64Disasm {
+ // 检查是否为REX前缀 (0x40-0x4F)
+ inline bool IsRexPrefix(BYTE b) {
+ return (b >= 0x40 && b <= 0x4F);
+ }
+
+ // 获取单条指令的长度
+ size_t GetInstructionLength(const BYTE* code) {
+ size_t len = 0;
+ bool hasRex = false;
+
+ // 跳过REX前缀
+ if (IsRexPrefix(code[len])) {
+ hasRex = true;
+ len++;
+ }
+
+ BYTE opcode = code[len];
+ len++;
+
+ // 常见指令长度判断(简化版)
+ switch (opcode) {
+ // 单字节指令
+ case 0x50: case 0x51: case 0x52: case 0x53:
+ case 0x54: case 0x55: case 0x56: case 0x57:
+ case 0x58: case 0x59: case 0x5A: case 0x5B:
+ case 0x5C: case 0x5D: case 0x5E: case 0x5F:
+ case 0x90: case 0xC3: case 0xCC:
+ return len;
+
+ // MOV指令
+ case 0x88: case 0x89: case 0x8A: case 0x8B:
+ len++; // ModRM
+ if ((code[len-1] & 0xC0) != 0xC0) {
+ // 有内存操作数
+ BYTE modrm = code[len-1];
+ BYTE mod = (modrm >> 6) & 3;
+ BYTE rm = modrm & 7;
+
+ if (rm == 4) len++; // 有SIB字节
+ if (mod == 1) len++; // disp8
+ else if (mod == 2) len += 4; // disp32
+ }
+ return len;
+
+ // 立即数指令
+ case 0xB0: case 0xB1: case 0xB2: case 0xB3:
+ case 0xB4: case 0xB5: case 0xB6: case 0xB7:
+ return len + 1; // imm8
+
+ case 0xB8: case 0xB9: case 0xBA: case 0xBB:
+ case 0xBC: case 0xBD: case 0xBE: case 0xBF:
+ return len + (hasRex ? 8 : 4); // imm32/64
+
+ // 短跳转
+ case 0x70: case 0x71: case 0x72: case 0x73:
+ case 0x74: case 0x75: case 0x76: case 0x77:
+ case 0x78: case 0x79: case 0x7A: case 0x7B:
+ case 0x7C: case 0x7D: case 0x7E: case 0x7F:
+ case 0xEB:
+ return len + 1; // rel8
+
+ case 0xE8: case 0xE9: // CALL/JMP rel32
+ return len + 4;
+
+ // 双字节指令
+ case 0x0F:
+ len++;
+ opcode = code[len-1];
+ if (opcode >= 0x80 && opcode <= 0x8F) {
+ return len + 4; // 条件跳转 rel32
+ }
+ return len + 1; // 简化处理
+
+ // LEA
+ case 0x8D:
+ len++; // ModRM
+ if ((code[len-1] & 0x07) == 4) len++; // SIB
+ if (((code[len-1] >> 6) & 3) == 2) len += 4; // disp32
+ return len;
+
+ default:
+ // 未知指令,返回最小长度
+ return len + 1;
+ }
+ }
+}
+
+RemoteHooker::RemoteHooker(HANDLE hProcess)
+ : hProcess(hProcess)
+ , targetAddress(0)
+ , remoteShellcodeAddress(0)
+ , trampolineAddress(0)
+ , isHookInstalled(false)
+ , useHardwareBreakpoint(false)
+{
+}
+
+RemoteHooker::~RemoteHooker() {
+ UninstallHook();
+}
+
+namespace {
+ void DebugProtectChange(const char* label, void* address, SIZE_T size, DWORD protect) {
+ char buffer[160]{};
+ _snprintf_s(buffer, sizeof(buffer) - 1, _TRUNCATE, "[RemoteHooker] %s addr=%p size=%zu prot=0x%lx\n",
+ label, address, static_cast(size), static_cast(protect));
+ OutputDebugStringA(buffer);
+ }
+}
+
+PVOID RemoteHooker::RemoteAllocate(SIZE_T size, DWORD protect) {
+ PVOID baseAddress = nullptr;
+ SIZE_T regionSize = size;
+
+ NTSTATUS status = IndirectSyscalls::NtAllocateVirtualMemory(
+ hProcess,
+ &baseAddress,
+ 0,
+ ®ionSize,
+ MEM_COMMIT | MEM_RESERVE,
+ protect
+ );
+
+ return (status == STATUS_SUCCESS) ? baseAddress : nullptr;
+}
+
+bool RemoteHooker::RemoteWrite(PVOID address, const void* data, SIZE_T size) {
+ SIZE_T bytesWritten = 0;
+
+ NTSTATUS status = IndirectSyscalls::NtWriteVirtualMemory(
+ hProcess,
+ address,
+ (PVOID)data,
+ size,
+ &bytesWritten
+ );
+
+ return (status == STATUS_SUCCESS && bytesWritten == size);
+}
+
+bool RemoteHooker::RemoteRead(PVOID address, void* buffer, SIZE_T size) {
+ SIZE_T bytesRead = 0;
+
+ NTSTATUS status = IndirectSyscalls::NtReadVirtualMemory(
+ hProcess,
+ address,
+ buffer,
+ size,
+ &bytesRead
+ );
+
+ return (status == STATUS_SUCCESS && bytesRead == size);
+}
+
+bool RemoteHooker::RemoteProtect(PVOID address, SIZE_T size, DWORD newProtect, DWORD* oldProtect) {
+ ULONG oldProt = 0;
+
+ NTSTATUS status = IndirectSyscalls::NtProtectVirtualMemory(
+ hProcess,
+ &address,
+ &size,
+ newProtect,
+ &oldProt
+ );
+
+ if (oldProtect) {
+ *oldProtect = oldProt;
+ }
+
+ return (status == STATUS_SUCCESS);
+}
+
+size_t RemoteHooker::CalculateHookLength(const BYTE* code) {
+ size_t totalLen = 0;
+ const size_t minLen = 14; // 我们需要至少14字节来放置长跳转
+
+ while (totalLen < minLen) {
+ size_t instrLen = X64Disasm::GetInstructionLength(code + totalLen);
+ if (instrLen == 0) {
+ return 0; // 失败
+ }
+ totalLen += instrLen;
+ }
+
+ return totalLen;
+}
+
+bool RemoteHooker::CreateTrampoline(uintptr_t targetAddr) {
+ // 读取目标地址的原始字节
+ BYTE originalCode[32];
+ if (!RemoteRead((PVOID)targetAddr, originalCode, sizeof(originalCode))) {
+ return false;
+ }
+
+ // 计算需要备份的指令长度
+ size_t hookLen = CalculateHookLength(originalCode);
+ if (hookLen == 0 || hookLen > 32) {
+ return false;
+ }
+
+ originalBytes.assign(originalCode, originalCode + hookLen);
+
+ // 分配Trampoline内存
+ // Trampoline = 原始指令 + 跳转回原函数的JMP指令
+ SIZE_T trampolineSize = hookLen + 14; // 原始指令 + 长跳转
+ RemoteMemory trampMem;
+ if (!trampMem.allocate(hProcess, trampolineSize, PAGE_READWRITE)) {
+ return false;
+ }
+ PVOID trampolineAddr = trampMem.get();
+
+ trampolineAddress = (uintptr_t)trampolineAddr;
+
+ // 写入原始指令
+ if (!RemoteWrite(trampolineAddr, originalCode, hookLen)) {
+ return false;
+ }
+
+ // 生成跳转回原函数的指令
+ uintptr_t returnAddress = targetAddr + hookLen;
+ std::vector jmpBack = GenerateJumpInstruction(trampolineAddress + hookLen, returnAddress);
+
+ if (!RemoteWrite((PVOID)(trampolineAddress + hookLen), jmpBack.data(), jmpBack.size())) {
+ trampolineAddress = 0;
+ return false;
+ }
+
+ if (!trampMem.protect(PAGE_EXECUTE_READ)) {
+ trampolineAddress = 0;
+ return false;
+ }
+ DebugProtectChange("trampoline RX", trampolineAddr, trampolineSize, PAGE_EXECUTE_READ);
+ trampolineMemory = std::move(trampMem);
+
+ return true;
+}
+
+std::vector RemoteHooker::GenerateJumpInstruction(uintptr_t from, uintptr_t to) {
+ std::vector jmp;
+
+ // 计算相对偏移
+ INT64 offset = (INT64)to - (INT64)from - 5;
+
+ // 如果可以使用5字节短跳转(rel32)
+ if (offset >= INT32_MIN && offset <= INT32_MAX) {
+ jmp.push_back(0xE9); // JMP rel32
+ INT32 offset32 = (INT32)offset;
+ jmp.push_back((BYTE)(offset32 & 0xFF));
+ jmp.push_back((BYTE)((offset32 >> 8) & 0xFF));
+ jmp.push_back((BYTE)((offset32 >> 16) & 0xFF));
+ jmp.push_back((BYTE)((offset32 >> 24) & 0xFF));
+ }
+ else {
+ // 使用14字节长跳转
+ // mov rax, addr64
+ jmp.push_back(0x48);
+ jmp.push_back(0xB8);
+ for (int i = 0; i < 8; i++) {
+ jmp.push_back((BYTE)((to >> (i * 8)) & 0xFF));
+ }
+ // jmp rax
+ jmp.push_back(0xFF);
+ jmp.push_back(0xE0);
+ }
+
+ return jmp;
+}
+
+bool RemoteHooker::InstallHook(uintptr_t targetFunctionAddress, const ShellcodeConfig& shellcodeConfig) {
+ if (isHookInstalled) {
+ return false; // 已经安装过Hook
+ }
+
+ targetAddress = targetFunctionAddress;
+
+ // 1. 创建Trampoline
+ if (!CreateTrampoline(targetAddress)) {
+ return false;
+ }
+
+ // 2. 构建Shellcode(需要更新配置以包含正确的trampoline地址)
+ ShellcodeConfig updatedConfig = shellcodeConfig;
+ updatedConfig.trampolineAddress = trampolineAddress;
+
+ ShellcodeBuilder builder;
+ std::vector shellcode = builder.BuildHookShellcode(updatedConfig);
+
+ // 3. 在远程进程中分配Shellcode内存
+ RemoteMemory shellMem;
+ if (!shellMem.allocate(hProcess, shellcode.size(), PAGE_READWRITE)) {
+ trampolineMemory.reset();
+ trampolineAddress = 0;
+ return false;
+ }
+ PVOID remoteShellcode = shellMem.get();
+ remoteShellcodeAddress = (uintptr_t)remoteShellcode;
+
+ // 4. 写入Shellcode
+ if (!RemoteWrite(remoteShellcode, shellcode.data(), shellcode.size())) {
+ trampolineMemory.reset();
+ trampolineAddress = 0;
+ remoteShellcodeAddress = 0;
+ return false;
+ }
+
+ if (!shellMem.protect(PAGE_EXECUTE_READ)) {
+ trampolineMemory.reset();
+ trampolineAddress = 0;
+ remoteShellcodeAddress = 0;
+ return false;
+ }
+ DebugProtectChange("shellcode RX", remoteShellcode, shellcode.size(), PAGE_EXECUTE_READ);
+ shellcodeMemory = std::move(shellMem);
+
+ DWORD shellOldProtect = 0;
+ if (!RemoteProtect(remoteShellcode, shellcode.size(), PAGE_EXECUTE_READ, &shellOldProtect)) {
+ shellcodeMemory.reset();
+ shellcodeMemory = RemoteMemory();
+ trampolineMemory.reset();
+ remoteShellcodeAddress = 0;
+ trampolineAddress = 0;
+ return false;
+ }
+ DebugProtectChange("shellcode RX", remoteShellcode, shellcode.size(), PAGE_EXECUTE_READ);
+
+ // 5. 生成Hook跳转指令
+ std::vector hookJump = GenerateJumpInstruction(targetAddress, remoteShellcodeAddress);
+
+ // 确保有足够的空间
+ if (hookJump.size() > originalBytes.size()) {
+ shellcodeMemory.reset();
+ shellcodeMemory = RemoteMemory();
+ trampolineMemory.reset();
+ remoteShellcodeAddress = 0;
+ trampolineAddress = 0;
+ return false;
+ }
+
+ // 填充NOP
+ while (hookJump.size() < originalBytes.size()) {
+ hookJump.push_back(0x90); // NOP
+ }
+
+ if (useHardwareBreakpoint) {
+ // 硬件断点模式:不写补丁,直接返回,由上层负责设置断点/VEH
+ isHookInstalled = true;
+ return true;
+ } else {
+ // Inline Patch 模式(回退)
+ // 6. 修改目标函数的保护属性
+ DWORD oldProtect;
+ if (!RemoteProtect((PVOID)targetAddress, originalBytes.size(), PAGE_EXECUTE_READWRITE, &oldProtect)) {
+ shellcodeMemory.reset();
+ shellcodeMemory = RemoteMemory();
+ trampolineMemory.reset();
+ remoteShellcodeAddress = 0;
+ trampolineAddress = 0;
+ return false;
+ }
+
+ // 7. 写入Hook跳转指令(原子操作)
+ bool writeSuccess = RemoteWrite((PVOID)targetAddress, hookJump.data(), hookJump.size());
+
+ // 8. 恢复原始保护属性
+ DWORD tempProtect;
+ RemoteProtect((PVOID)targetAddress, originalBytes.size(), oldProtect, &tempProtect);
+
+ if (!writeSuccess) {
+ shellcodeMemory.reset();
+ shellcodeMemory = RemoteMemory();
+ trampolineMemory.reset();
+ remoteShellcodeAddress = 0;
+ trampolineAddress = 0;
+ return false;
+ }
+
+ isHookInstalled = true;
+ return true;
+ }
+}
+
+bool RemoteHooker::UninstallHook() {
+ if (!isHookInstalled) {
+ return true;
+ }
+
+ bool restoreSuccess = true;
+
+ if (!useHardwareBreakpoint) {
+ // Inline patch 模式才需要恢复补丁
+ DWORD oldProtect;
+ if (!RemoteProtect((PVOID)targetAddress, originalBytes.size(), PAGE_EXECUTE_READWRITE, &oldProtect)) {
+ return false;
+ }
+ restoreSuccess = RemoteWrite((PVOID)targetAddress, originalBytes.data(), originalBytes.size());
+ DWORD tempProtect;
+ RemoteProtect((PVOID)targetAddress, originalBytes.size(), oldProtect, &tempProtect);
+ }
+
+ // 4. 释放远程内存
+ shellcodeMemory.reset();
+ remoteShellcodeAddress = 0;
+ trampolineMemory.reset();
+ trampolineAddress = 0;
+
+ isHookInstalled = false;
+ return restoreSuccess;
+}
+
diff --git a/wx_key/src/remote_scanner.cpp b/wx_key/src/remote_scanner.cpp
new file mode 100644
index 0000000..87eef10
--- /dev/null
+++ b/wx_key/src/remote_scanner.cpp
@@ -0,0 +1,245 @@
+#include "../include/remote_scanner.h"
+#include "../include/syscalls.h"
+#include "../include/string_obfuscator.h"
+#include
+#include
+#include
+#include
+
+#pragma comment(lib, "Psapi.lib")
+#pragma comment(lib, "version.lib")
+
+// 版本配置管理器静态成员
+std::vector VersionConfigManager::configs;
+bool VersionConfigManager::initialized = false;
+
+namespace {
+ using VersionArray = std::array;
+
+ bool ParseVersionString(const std::string& version, VersionArray& outParts) {
+ outParts.fill(0);
+ std::stringstream ss(version);
+ std::string segment;
+ size_t index = 0;
+
+ while (std::getline(ss, segment, '.') && index < outParts.size()) {
+ try {
+ outParts[index++] = std::stoi(segment);
+ } catch (const std::exception&) {
+ return false;
+ }
+ }
+
+ return index > 0;
+ }
+
+ int CompareVersions(const VersionArray& lhs, const VersionArray& rhs) {
+ for (size_t i = 0; i < lhs.size(); ++i) {
+ if (lhs[i] < rhs[i]) return -1;
+ if (lhs[i] > rhs[i]) return 1;
+ }
+ return 0;
+ }
+}
+
+void VersionConfigManager::InitializeConfigs() {
+ if (initialized) return;
+
+ // 微信 4.1.4 及以上配置(含 4.1.4.x、4.1.5.x 等)
+ configs.push_back(WeChatVersionConfig(
+ ">=4.1.4",
+ {0x24, 0x08, 0x48, 0x89, 0x6c, 0x24, 0x10, 0x48, 0x89, 0x74, 0x00, 0x18, 0x48, 0x89, 0x7c, 0x00, 0x20, 0x41, 0x56, 0x48, 0x83, 0xec, 0x50, 0x41},
+ "xxxxxxxxxx?xxxx?xxxxxxxx",
+ -3
+ ));
+
+ // 微信 4.1.4 以下(含 4.1.0-4.1.3 与 4.0.x)的通用配置
+ configs.push_back(WeChatVersionConfig(
+ "<4.1.4",
+ {0x24, 0x50, 0x48, 0xc7, 0x45, 0x00, 0xfe, 0xff, 0xff, 0xff, 0x44, 0x89, 0xcf, 0x44, 0x89, 0xc3, 0x49, 0x89, 0xd6, 0x48, 0x89, 0xce, 0x48, 0x89},
+ "xxxxxxxxxxxxxxxxxxxxxxxx",
+ -0xf
+ ));
+
+ initialized = true;
+}
+
+const WeChatVersionConfig* VersionConfigManager::GetConfigForVersion(const std::string& version) {
+ InitializeConfigs();
+
+ if (configs.size() < 2 || version.empty()) {
+ return nullptr;
+ }
+
+ VersionArray parsedVersion;
+ if (!ParseVersionString(version, parsedVersion)) {
+ return nullptr;
+ }
+
+ constexpr VersionArray baseline414 = {4, 1, 4, 0};
+
+ if (CompareVersions(parsedVersion, baseline414) >= 0) {
+ return &configs[0];
+ }
+
+ if ((parsedVersion[0] == 4 && parsedVersion[1] == 1 && parsedVersion[2] < 4) ||
+ (parsedVersion[0] == 4 && parsedVersion[1] == 0)) {
+ return &configs[1];
+ }
+
+ return nullptr;
+}
+
+// RemoteScanner实现
+RemoteScanner::RemoteScanner(HANDLE hProcess)
+ : hProcess(hProcess)
+{
+ // 预分配扫描缓冲区(2MB)
+ scanBuffer.reserve(2 * 1024 * 1024);
+}
+
+RemoteScanner::~RemoteScanner() {
+}
+
+bool RemoteScanner::GetRemoteModuleInfo(const std::string& moduleName, RemoteModuleInfo& outInfo) {
+ // 枚举远程进程的模块
+ HMODULE hMods[1024];
+ DWORD cbNeeded;
+
+ if (!EnumProcessModules(this->hProcess, hMods, sizeof(hMods), &cbNeeded)) {
+ return false;
+ }
+
+ DWORD moduleCount = cbNeeded / sizeof(HMODULE);
+
+ for (DWORD i = 0; i < moduleCount; i++) {
+ char szModName[MAX_PATH];
+
+ if (GetModuleBaseNameA(this->hProcess, hMods[i], szModName, sizeof(szModName) / sizeof(char))) {
+ if (_stricmp(szModName, moduleName.c_str()) == 0) {
+ MODULEINFO modInfo;
+ if (GetModuleInformation(this->hProcess, hMods[i], &modInfo, sizeof(modInfo))) {
+ outInfo.baseAddress = hMods[i];
+ outInfo.imageSize = modInfo.SizeOfImage;
+ outInfo.moduleName = szModName;
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+bool RemoteScanner::MatchPattern(const BYTE* data, const BYTE* pattern, const char* mask, size_t length) {
+ for (size_t i = 0; i < length; i++) {
+ if (mask[i] != '?' && data[i] != pattern[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+uintptr_t RemoteScanner::FindPattern(const RemoteModuleInfo& moduleInfo, const BYTE* pattern, const char* mask) {
+ auto results = FindAllPatterns(moduleInfo, pattern, mask);
+ return results.empty() ? 0 : results[0];
+}
+
+std::vector RemoteScanner::FindAllPatterns(const RemoteModuleInfo& moduleInfo, const BYTE* pattern, const char* mask) {
+ std::vector results;
+
+ size_t patternLength = strlen(mask);
+ uintptr_t baseAddress = (uintptr_t)moduleInfo.baseAddress;
+ SIZE_T imageSize = moduleInfo.imageSize;
+
+ // 分块读取和扫描
+ const SIZE_T CHUNK_SIZE = 1024 * 1024; // 1MB chunks
+ scanBuffer.resize(CHUNK_SIZE + patternLength);
+
+ for (SIZE_T offset = 0; offset < imageSize; offset += CHUNK_SIZE) {
+ SIZE_T readSize = min(CHUNK_SIZE + patternLength, imageSize - offset);
+ SIZE_T bytesRead = 0;
+
+ // 使用间接系统调用读取内存
+ NTSTATUS status = IndirectSyscalls::NtReadVirtualMemory(
+ this->hProcess,
+ (PVOID)(baseAddress + offset),
+ scanBuffer.data(),
+ readSize,
+ &bytesRead
+ );
+
+ if (status != STATUS_SUCCESS || bytesRead == 0) {
+ continue;
+ }
+
+ if (bytesRead < patternLength) {
+ continue;
+ }
+
+ // 在本地缓冲区中搜索特征码
+ for (SIZE_T i = 0; i + patternLength <= bytesRead; ++i) {
+ if (MatchPattern(&scanBuffer[i], pattern, mask, patternLength)) {
+ results.push_back(baseAddress + offset + i);
+ }
+ }
+ }
+
+ return results;
+}
+
+bool RemoteScanner::ReadRemoteMemory(uintptr_t address, void* buffer, SIZE_T size) {
+ SIZE_T bytesRead = 0;
+ NTSTATUS status = IndirectSyscalls::NtReadVirtualMemory(
+ this->hProcess,
+ (PVOID)address,
+ buffer,
+ size,
+ &bytesRead
+ );
+
+ return (status == STATUS_SUCCESS && bytesRead == size);
+}
+
+std::string RemoteScanner::GetWeChatVersion() {
+ std::string weixinDllName = ObfuscatedStrings::GetWeixinDllName();
+
+ RemoteModuleInfo moduleInfo;
+ if (!GetRemoteModuleInfo(weixinDllName, moduleInfo)) {
+ return "";
+ }
+
+ // 读取模块路径
+ WCHAR modulePath[MAX_PATH];
+ if (GetModuleFileNameExW(this->hProcess, moduleInfo.baseAddress, modulePath, MAX_PATH) == 0) {
+ return "";
+ }
+
+ // 获取文件版本信息
+ DWORD handle = 0;
+ DWORD versionSize = GetFileVersionInfoSizeW(modulePath, &handle);
+ if (versionSize == 0) {
+ return "";
+ }
+
+ std::vector versionData(versionSize);
+ if (!GetFileVersionInfoW(modulePath, handle, versionSize, versionData.data())) {
+ return "";
+ }
+
+ VS_FIXEDFILEINFO* fileInfo = nullptr;
+ UINT fileInfoSize = 0;
+ if (VerQueryValueW(versionData.data(), L"\\", (LPVOID*)&fileInfo, &fileInfoSize) && fileInfo) {
+ DWORD major = HIWORD(fileInfo->dwProductVersionMS);
+ DWORD minor = LOWORD(fileInfo->dwProductVersionMS);
+ DWORD build = HIWORD(fileInfo->dwProductVersionLS);
+ DWORD revision = LOWORD(fileInfo->dwProductVersionLS);
+
+ std::stringstream ss;
+ ss << major << "." << minor << "." << build << "." << revision;
+ return ss.str();
+ }
+
+ return "";
+}
+
diff --git a/wx_key/src/remote_veh.cpp b/wx_key/src/remote_veh.cpp
new file mode 100644
index 0000000..557129c
--- /dev/null
+++ b/wx_key/src/remote_veh.cpp
@@ -0,0 +1,283 @@
+#include "../include/remote_veh.h"
+#include "../include/syscalls.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace {
+ constexpr SIZE_T RIP_OFFSET = offsetof(CONTEXT, Rip);
+ constexpr SIZE_T EFLAGS_OFFSET = offsetof(CONTEXT, EFlags);
+
+ struct RemoteData {
+ uint64_t target;
+ uint64_t shellcode;
+ uint64_t vehHandle;
+ };
+
+ void DebugProtectChange(const char* label, void* address, SIZE_T size, DWORD protect) {
+ char buffer[160]{};
+ _snprintf_s(buffer, sizeof(buffer) - 1, _TRUNCATE, "[RemoteVEH] %s addr=%p size=%zu prot=0x%lx\n",
+ label, address, static_cast(size), static_cast(protect));
+ OutputDebugStringA(buffer);
+ }
+
+ bool SetHardwareBreakpointThread(HANDLE hProcess, DWORD tid, uintptr_t address) {
+ HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, tid);
+ if (!hThread) return false;
+
+ CONTEXT ctx{};
+ ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
+ bool ok = false;
+ if (GetThreadContext(hThread, &ctx)) {
+ ctx.Dr0 = address;
+ ctx.Dr7 |= 0x1;
+ ok = SetThreadContext(hThread, &ctx) == TRUE;
+ }
+ CloseHandle(hThread);
+ return ok;
+ }
+
+ bool SetHardwareBreakpointAllThreads(HANDLE hProcess, uintptr_t address) {
+ HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
+ if (snap == INVALID_HANDLE_VALUE) return false;
+ THREADENTRY32 te{};
+ te.dwSize = sizeof(te);
+ DWORD pid = GetProcessId(hProcess);
+ bool ok = true;
+ if (Thread32First(snap, &te)) {
+ do {
+ if (te.th32OwnerProcessID == pid) {
+ if (!SetHardwareBreakpointThread(hProcess, te.th32ThreadID, address)) {
+ ok = false;
+ }
+ }
+ } while (Thread32Next(snap, &te));
+ }
+ CloseHandle(snap);
+ return ok;
+ }
+
+ void ClearHardwareBreakpointAllThreads(HANDLE hProcess) {
+ HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
+ if (snap == INVALID_HANDLE_VALUE) return;
+ THREADENTRY32 te{};
+ te.dwSize = sizeof(te);
+ DWORD pid = GetProcessId(hProcess);
+ if (Thread32First(snap, &te)) {
+ do {
+ if (te.th32OwnerProcessID == pid) {
+ HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
+ if (hThread) {
+ CONTEXT ctx{};
+ ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
+ if (GetThreadContext(hThread, &ctx)) {
+ ctx.Dr0 = 0;
+ ctx.Dr7 = 0;
+ SetThreadContext(hThread, &ctx);
+ }
+ CloseHandle(hThread);
+ }
+ }
+ } while (Thread32Next(snap, &te));
+ }
+ CloseHandle(snap);
+ }
+
+ uintptr_t GetRemoteProcAddress(HANDLE hProcess, const char* moduleName, FARPROC localProc) {
+ if (!moduleName) {
+ return 0;
+ }
+
+ HMODULE localMod = GetModuleHandleA(moduleName);
+ if (!localMod || !localProc) return 0;
+ uintptr_t localBase = reinterpret_cast(localMod);
+ uintptr_t localAddr = reinterpret_cast(localProc);
+ uintptr_t offset = localAddr - localBase;
+
+ HMODULE hMods[512];
+ DWORD cbNeeded = 0;
+ if (!EnumProcessModulesEx(hProcess, hMods, sizeof(hMods), &cbNeeded, LIST_MODULES_ALL)) {
+ return 0;
+ }
+ size_t count = cbNeeded / sizeof(HMODULE);
+ for (size_t i = 0; i < count; ++i) {
+ char modName[MAX_PATH]{};
+ if (GetModuleBaseNameA(hProcess, hMods[i], modName, MAX_PATH)) {
+ if (_stricmp(modName, moduleName) == 0) {
+ uintptr_t remoteBase = reinterpret_cast(hMods[i]);
+ return remoteBase + offset;
+ }
+ }
+ }
+ return 0;
+ }
+
+ bool BuildRemoteCode(HANDLE hProcess, uintptr_t target, uintptr_t shellcode, FARPROC addVeh, FARPROC removeVeh, RemoteVehHandle& outHandle) {
+ std::vector buf;
+ buf.reserve(512);
+
+ // data
+ RemoteData localData{};
+ localData.target = target;
+ localData.shellcode = shellcode;
+ localData.vehHandle = 0;
+ buf.insert(buf.end(), reinterpret_cast(&localData), reinterpret_cast(&localData) + sizeof(localData));
+
+ auto emit = [&](std::initializer_list bytes) {
+ buf.insert(buf.end(), bytes.begin(), bytes.end());
+ };
+
+ auto emitDisp32 = [&](uint32_t disp) {
+ emit({
+ static_cast(disp & 0xFF),
+ static_cast((disp >> 8) & 0xFF),
+ static_cast((disp >> 16) & 0xFF),
+ static_cast((disp >> 24) & 0xFF)
+ });
+ };
+
+ // handler
+ size_t handlerOffset = buf.size();
+ emit({0x48,0x8B,0x01}); // mov rax,[rcx] (ExceptionRecord)
+ emit({0x8B,0x00}); // mov eax,[rax] (ExceptionCode)
+ emit({0x3D,0x04,0x00,0x00,0x80}); // cmp eax,0x80000004
+ emit({0x75,0x2B}); // jne cont
+ emit({0x48,0x8B,0x51,0x08}); // mov rdx,[rcx+8] (Context)
+ emit({0x48,0xB8}); size_t tgtPatch = buf.size(); buf.resize(buf.size()+8); // mov rax,target
+ emit({0x48,0x3B,0x82}); emitDisp32(static_cast(RIP_OFFSET)); // cmp rax,[rdx+RIP]
+ emit({0x75,0x14}); // jne cont
+ emit({0x48,0xB8}); size_t shPatch = buf.size(); buf.resize(buf.size()+8); // mov rax,shellcode
+ emit({0x48,0x89,0x82}); emitDisp32(static_cast(RIP_OFFSET)); // mov [rdx+RIP],rax
+ emit({0x48,0x8B,0x82}); emitDisp32(static_cast(EFLAGS_OFFSET)); // mov rax,[rdx+EFLAGS]
+ emit({0x48,0x25,0xFF,0xFE,0xFF,0xFF}); // and rax,~0x100
+ emit({0x48,0x89,0x82}); emitDisp32(static_cast(EFLAGS_OFFSET)); // mov [rdx+EFLAGS],rax
+ emit({0xB8,0xFF,0xFF,0xFF,0xFF}); // mov eax,-1
+ emit({0xC3}); // ret
+ emit({0x33,0xC0}); // xor eax,eax
+ emit({0xC3}); // ret
+
+ // register stub
+ size_t regOffset = buf.size();
+ emit({0x48,0x83,0xEC,0x28}); // sub rsp,28h
+ emit({0x48,0x31,0xC9}); // xor rcx,rcx
+ emit({0x48,0x83,0xC1,0x01}); // inc rcx (FirstHandler)
+ emit({0x48,0xBA}); size_t hPatch = buf.size(); buf.resize(buf.size()+8); // mov rdx,handler
+ emit({0x48,0xB8}); size_t addPatch = buf.size(); buf.resize(buf.size()+8); // mov rax,AddVEH
+ emit({0xFF,0xD0}); // call rax
+ emit({0x48,0xA3}); size_t storePatch = buf.size(); buf.resize(buf.size()+8); // mov [vehHandle], rax
+ emit({0x48,0x83,0xC4,0x28}); // add rsp,28h
+ emit({0xC3}); // ret
+
+ // unregister stub
+ size_t unregOffset = buf.size();
+ emit({0x48,0x83,0xEC,0x28}); // sub rsp,28h
+ emit({0x48,0xA1}); size_t loadPatch = buf.size(); buf.resize(buf.size()+8); // mov rax,[vehHandle]
+ emit({0x48,0x85,0xC0}); // test rax,rax
+ emit({0x74,0x14}); // je skip
+ emit({0x48,0x89,0xC1}); // mov rcx,rax
+ emit({0x48,0xB8}); size_t remPatch = buf.size(); buf.resize(buf.size()+8); // mov rax,RemoveVEH
+ emit({0xFF,0xD0}); // call rax
+ emit({0x48,0x83,0xC4,0x28}); // add rsp,28h
+ emit({0xC3}); // ret
+
+ // allocate remote
+ RemoteMemory remoteBlock;
+ SIZE_T allocSize = buf.size();
+ if (!remoteBlock.allocate(hProcess, allocSize, PAGE_READWRITE)) {
+ return false;
+ }
+
+ uintptr_t base = reinterpret_cast(remoteBlock.get());
+ uintptr_t handlerAddr = base + handlerOffset;
+ uintptr_t regAddr = base + regOffset;
+ uintptr_t unregAddr = base + unregOffset;
+ uintptr_t vehHandleAddr = base + offsetof(RemoteData, vehHandle);
+
+ // patches
+ memcpy(buf.data() + tgtPatch, &target, sizeof(uint64_t));
+ memcpy(buf.data() + shPatch, &shellcode, sizeof(uint64_t));
+ memcpy(buf.data() + hPatch, &handlerAddr, sizeof(uint64_t));
+ uint64_t addAddr = reinterpret_cast(addVeh);
+ memcpy(buf.data() + addPatch, &addAddr, sizeof(uint64_t));
+ memcpy(buf.data() + storePatch, &vehHandleAddr, sizeof(uint64_t));
+ memcpy(buf.data() + loadPatch, &vehHandleAddr, sizeof(uint64_t));
+ uint64_t remAddr = reinterpret_cast(removeVeh);
+ memcpy(buf.data() + remPatch, &remAddr, sizeof(uint64_t));
+
+ if (!WriteProcessMemory(hProcess, remoteBlock.get(), buf.data(), buf.size(), nullptr)) {
+ return false;
+ }
+
+ // 切换为RX,避免长期RWX
+ if (!remoteBlock.protect(PAGE_EXECUTE_READ)) {
+ return false;
+ }
+ DebugProtectChange("veh block RX", remoteBlock.get(), allocSize, PAGE_EXECUTE_READ);
+
+ HANDLE hThread = CreateRemoteThread(hProcess, nullptr, 0, (LPTHREAD_START_ROUTINE)regAddr, nullptr, 0, nullptr);
+ if (!hThread) {
+ return false;
+ }
+ WaitForSingleObject(hThread, 5000);
+ CloseHandle(hThread);
+
+ outHandle.remoteMemory = std::move(remoteBlock);
+ outHandle.dataBlock = outHandle.remoteMemory.get();
+ outHandle.unregStubAddress = unregAddr;
+ outHandle.vehHandle = reinterpret_cast(vehHandleAddr); // store address for reference
+ outHandle.installed = true;
+ return true;
+ }
+}
+
+RemoteVehHandle InstallRemoteVeh(const RemoteVehConfig& cfg) {
+ RemoteVehHandle handle = {};
+ if (!cfg.hProcess || cfg.targetAddress == 0 || cfg.shellcodeAddress == 0) {
+ return handle;
+ }
+
+ HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
+ if (!hKernel32) {
+ return handle;
+ }
+ FARPROC addVehLocal = GetProcAddress(hKernel32, "AddVectoredExceptionHandler");
+ FARPROC removeVehLocal = GetProcAddress(hKernel32, "RemoveVectoredExceptionHandler");
+ uintptr_t addVeh = GetRemoteProcAddress(cfg.hProcess, "kernel32.dll", addVehLocal);
+ uintptr_t removeVeh = GetRemoteProcAddress(cfg.hProcess, "kernel32.dll", removeVehLocal);
+ if (!addVeh || !removeVeh) {
+ return handle;
+ }
+
+ if (!BuildRemoteCode(cfg.hProcess, cfg.targetAddress, cfg.shellcodeAddress, reinterpret_cast(addVeh), reinterpret_cast(removeVeh), handle)) {
+ return handle;
+ }
+
+ // handler 注册完成后再设置硬件断点,避免未注册时命中异常
+ if (!SetHardwareBreakpointAllThreads(cfg.hProcess, cfg.targetAddress)) {
+ UninstallRemoteVeh(cfg, handle);
+ RemoteVehHandle emptyHandle = {};
+ return emptyHandle;
+ }
+
+ return handle;
+}
+
+void UninstallRemoteVeh(const RemoteVehConfig& cfg, RemoteVehHandle& handle) {
+ ClearHardwareBreakpointAllThreads(cfg.hProcess);
+
+ if (handle.installed && handle.dataBlock) {
+ HANDLE hThread = CreateRemoteThread(cfg.hProcess, nullptr, 0, (LPTHREAD_START_ROUTINE)handle.unregStubAddress, nullptr, 0, nullptr);
+ if (hThread) {
+ WaitForSingleObject(hThread, 5000);
+ CloseHandle(hThread);
+ }
+ handle.remoteMemory.reset();
+ handle.dataBlock = nullptr;
+ }
+ handle.installed = false;
+}
diff --git a/wx_key/src/shellcode_builder.cpp b/wx_key/src/shellcode_builder.cpp
new file mode 100644
index 0000000..72a40ed
--- /dev/null
+++ b/wx_key/src/shellcode_builder.cpp
@@ -0,0 +1,150 @@
+#include "../include/shellcode_builder.h"
+#include "../include/ipc_manager.h"
+#include
+#include
+
+namespace {
+ constexpr size_t kSharedDataSizeOffset = offsetof(SharedKeyData, dataSize);
+ constexpr size_t kSharedKeyBufferOffset = offsetof(SharedKeyData, keyBuffer);
+ constexpr size_t kSharedSequenceOffset = offsetof(SharedKeyData, sequenceNumber);
+}
+
+ShellcodeBuilder::ShellcodeBuilder() {
+ shellcode.reserve(512);
+}
+
+ShellcodeBuilder::~ShellcodeBuilder() {
+}
+
+void ShellcodeBuilder::Clear() {
+ shellcode.clear();
+}
+
+size_t ShellcodeBuilder::GetShellcodeSize() const {
+ return shellcode.size();
+}
+
+// 使用 Xbyak 生成 Hook Shellcode
+std::vector ShellcodeBuilder::BuildHookShellcode(const ShellcodeConfig& config) {
+ shellcode.clear();
+
+ // 只支持 x64
+ if (sizeof(void*) != 8) {
+ return shellcode;
+ }
+
+ const bool enableStackSpoofing = config.enableStackSpoofing && config.spoofStackPointer != 0;
+ uint64_t spoofStackAligned = 0;
+ if (enableStackSpoofing) {
+ spoofStackAligned = static_cast(config.spoofStackPointer) & ~static_cast(0xF);
+ }
+
+ // 生成机器码
+ Xbyak::CodeGenerator code(1024, Xbyak::AutoGrow);
+
+ Xbyak::Label skipCopy;
+
+ auto emitSaveRegs = [&]() {
+ code.pushfq();
+ code.push(code.rax);
+ code.push(code.rcx);
+ code.push(code.rdx);
+ code.push(code.rbx);
+ code.push(code.rbp);
+ code.push(code.rsi);
+ code.push(code.rdi);
+ code.push(code.r8);
+ code.push(code.r9);
+ code.push(code.r10);
+ code.push(code.r11);
+ code.push(code.r12);
+ code.push(code.r13);
+ code.push(code.r14);
+ code.push(code.r15);
+ };
+
+ auto emitRestoreRegs = [&]() {
+ code.pop(code.r15);
+ code.pop(code.r14);
+ code.pop(code.r13);
+ code.pop(code.r12);
+ code.pop(code.r11);
+ code.pop(code.r10);
+ code.pop(code.r9);
+ code.pop(code.r8);
+ code.pop(code.rdi);
+ code.pop(code.rsi);
+ code.pop(code.rbp);
+ code.pop(code.rbx);
+ code.pop(code.rdx);
+ code.pop(code.rcx);
+ code.pop(code.rax);
+ code.popfq();
+ };
+
+ if (enableStackSpoofing) {
+ // 将关键寄存器暂存到真实栈,再切换到对齐后的伪栈
+ code.push(code.rax); // 保存原始 rax
+ code.push(code.r10); // 保存原始 r10
+ code.push(code.r11); // 保存原始 r11
+
+ // rsp + 24 对应切换前的真实栈指针
+ code.lea(code.rax, code.ptr[code.rsp + 24]); // rax = original rsp
+
+ // 切换到伪栈(对齐到16字节),预留一定空间
+ code.mov(code.rsp, spoofStackAligned);
+ code.sub(code.rsp, 0x20);
+
+ // 将真实 RSP 存到伪栈,并构造一个假的返回地址槽位
+ code.push(code.rax); // [rsp] = original rsp
+ code.push(0); // 伪造返回地址,不破坏通用寄存器
+
+ // 恢复 r11/r10/rax 的原始值,确保后续保存寄存器时是原值
+ code.mov(code.r11, code.qword[code.rax - 24]);
+ code.mov(code.r10, code.qword[code.rax - 16]);
+ code.mov(code.rax, code.qword[code.rax - 8]);
+ }
+
+ // ===== 保存寄存器/标志位 =====
+ emitSaveRegs();
+
+ // ===== keySize 检查 =====
+ code.mov(code.rax, code.ptr[code.rdx + 0x10]); // rax = keySize
+ code.cmp(code.rax, 32);
+ code.jne(skipCopy);
+
+ // ===== 拷贝 32 字节密钥到共享内存 =====
+ code.mov(code.rcx, code.ptr[code.rdx + 0x08]); // rcx = pKeyBuffer
+ code.mov(code.rdx, (uint64_t)config.sharedMemoryAddress);
+ code.mov(code.rdi, code.rdx);
+ code.mov(code.dword[code.rdi + static_cast(kSharedDataSizeOffset)], 32); // dataSize = 32
+ code.add(code.rdi, static_cast(kSharedKeyBufferOffset)); // rdi -> keyBuffer
+ code.mov(code.rsi, code.rcx); // rsi = source
+ code.mov(code.rcx, 32); // count
+ code.rep();
+ code.movsb(); // rep movsb
+
+ // ===== 递增序列号 =====
+ code.mov(code.eax, code.dword[code.rdx + static_cast(kSharedSequenceOffset)]); // 读取 sequenceNumber
+ code.inc(code.eax);
+ code.mov(code.dword[code.rdx + static_cast(kSharedSequenceOffset)], code.eax); // 写回递增后的序列号
+
+ code.L(skipCopy);
+
+ // ===== 恢复寄存器/标志位 =====
+ emitRestoreRegs();
+
+ if (enableStackSpoofing) {
+ // 丢弃伪造返回地址并恢复真实 RSP,切回原始栈
+ code.add(code.rsp, 8); // skip fake return slot
+ code.pop(code.rsp);
+ }
+
+ // ===== 跳回 Trampoline =====
+ code.mov(code.rax, (uint64_t)config.trampolineAddress);
+ code.jmp(code.rax);
+
+ // 输出机器码
+ shellcode.assign(code.getCode(), code.getCode() + code.getSize());
+ return shellcode;
+}
diff --git a/wx_key/src/syscalls.cpp b/wx_key/src/syscalls.cpp
new file mode 100644
index 0000000..7ad1b94
--- /dev/null
+++ b/wx_key/src/syscalls.cpp
@@ -0,0 +1,277 @@
+#include "../include/syscalls.h"
+#include "../include/string_obfuscator.h"
+#include
+#include
+#include
+#include
+
+// 静态成员初始化
+bool IndirectSyscalls::initialized = false;
+pNtOpenProcess IndirectSyscalls::fnNtOpenProcess = nullptr;
+pNtReadVirtualMemory IndirectSyscalls::fnNtReadVirtualMemory = nullptr;
+pNtWriteVirtualMemory IndirectSyscalls::fnNtWriteVirtualMemory = nullptr;
+pNtAllocateVirtualMemory IndirectSyscalls::fnNtAllocateVirtualMemory = nullptr;
+pNtFreeVirtualMemory IndirectSyscalls::fnNtFreeVirtualMemory = nullptr;
+pNtProtectVirtualMemory IndirectSyscalls::fnNtProtectVirtualMemory = nullptr;
+pNtQueryInformationProcess IndirectSyscalls::fnNtQueryInformationProcess = nullptr;
+// 直调stub
+pNtOpenProcess IndirectSyscalls::scNtOpenProcess = nullptr;
+pNtReadVirtualMemory IndirectSyscalls::scNtReadVirtualMemory = nullptr;
+pNtWriteVirtualMemory IndirectSyscalls::scNtWriteVirtualMemory = nullptr;
+pNtAllocateVirtualMemory IndirectSyscalls::scNtAllocateVirtualMemory = nullptr;
+pNtFreeVirtualMemory IndirectSyscalls::scNtFreeVirtualMemory = nullptr;
+pNtProtectVirtualMemory IndirectSyscalls::scNtProtectVirtualMemory = nullptr;
+pNtQueryInformationProcess IndirectSyscalls::scNtQueryInformationProcess = nullptr;
+
+template
+bool IndirectSyscalls::ResolveFunction(const char* functionName, T& functionPointer) {
+ std::string ntdllName = ObfuscatedStrings::GetNtdllName();
+ HMODULE hNtdll = GetModuleHandleA(ntdllName.c_str());
+ if (!hNtdll) {
+ return false;
+ }
+
+ functionPointer = reinterpret_cast(GetProcAddress(hNtdll, functionName));
+ return (functionPointer != nullptr);
+}
+
+namespace {
+ // 标准 stub 前缀:mov r10, rcx; mov eax, imm32; syscall; ret
+ bool LooksLikePatchedStub(void* fn) {
+ if (!fn) return true;
+ const uint8_t* code = reinterpret_cast(fn);
+ // 4C 8B D1 B8 xx xx xx xx 0F 05 C3
+ constexpr std::array kPrefix = {0x4C, 0x8B, 0xD1};
+ for (size_t i = 0; i < kPrefix.size(); ++i) {
+ if (code[i] != kPrefix[i]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ FARPROC LoadCleanNtdllFunction(const char* functionName) {
+ wchar_t sysDir[MAX_PATH]{};
+ if (GetSystemDirectoryW(sysDir, MAX_PATH) == 0) {
+ return nullptr;
+ }
+
+ wchar_t ntdllPath[MAX_PATH]{};
+ if (FAILED(StringCchPrintfW(ntdllPath, MAX_PATH, L"%s\\ntdll.dll", sysDir))) {
+ return nullptr;
+ }
+
+ HMODULE hNtdll = LoadLibraryExW(
+ ntdllPath,
+ nullptr,
+ LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE | LOAD_LIBRARY_AS_IMAGE_RESOURCE | LOAD_LIBRARY_SEARCH_SYSTEM32
+ );
+ if (!hNtdll) {
+ return nullptr;
+ }
+
+ FARPROC fn = GetProcAddress(hNtdll, functionName);
+ FreeLibrary(hNtdll);
+ return fn;
+ }
+
+ void* ChooseSyscallSource(const char* functionName) {
+ void* fn = reinterpret_cast(GetProcAddress(GetModuleHandleA(ObfuscatedStrings::GetNtdllName().c_str()), functionName));
+ if (fn && !LooksLikePatchedStub(fn)) {
+ return fn;
+ }
+
+ FARPROC clean = LoadCleanNtdllFunction(functionName);
+ if (clean) {
+ return reinterpret_cast(clean);
+ }
+ return fn;
+ }
+} // namespace
+
+bool IndirectSyscalls::Initialize() {
+ if (initialized) {
+ return true;
+ }
+
+ bool success = true;
+ success &= ResolveFunction("NtOpenProcess", fnNtOpenProcess);
+ success &= ResolveFunction("NtReadVirtualMemory", fnNtReadVirtualMemory);
+ success &= ResolveFunction("NtWriteVirtualMemory", fnNtWriteVirtualMemory);
+ success &= ResolveFunction("NtAllocateVirtualMemory", fnNtAllocateVirtualMemory);
+ success &= ResolveFunction("NtFreeVirtualMemory", fnNtFreeVirtualMemory);
+ success &= ResolveFunction("NtProtectVirtualMemory", fnNtProtectVirtualMemory);
+ success &= ResolveFunction("NtQueryInformationProcess", fnNtQueryInformationProcess);
+
+ // 构建直调 stub,必要时使用干净的 ntdll 提取 SSN
+ scNtOpenProcess = reinterpret_cast(CreateSyscallStub(ExtractSyscallNumber(ChooseSyscallSource("NtOpenProcess"))));
+ scNtReadVirtualMemory = reinterpret_cast(CreateSyscallStub(ExtractSyscallNumber(ChooseSyscallSource("NtReadVirtualMemory"))));
+ scNtWriteVirtualMemory = reinterpret_cast(CreateSyscallStub(ExtractSyscallNumber(ChooseSyscallSource("NtWriteVirtualMemory"))));
+ scNtAllocateVirtualMemory = reinterpret_cast(CreateSyscallStub(ExtractSyscallNumber(ChooseSyscallSource("NtAllocateVirtualMemory"))));
+ scNtFreeVirtualMemory = reinterpret_cast(CreateSyscallStub(ExtractSyscallNumber(ChooseSyscallSource("NtFreeVirtualMemory"))));
+ scNtProtectVirtualMemory = reinterpret_cast(CreateSyscallStub(ExtractSyscallNumber(ChooseSyscallSource("NtProtectVirtualMemory"))));
+ scNtQueryInformationProcess = reinterpret_cast(CreateSyscallStub(ExtractSyscallNumber(ChooseSyscallSource("NtQueryInformationProcess"))));
+
+ initialized = success;
+ return success;
+}
+
+void IndirectSyscalls::Cleanup() {
+ // 清理资源
+ initialized = false;
+}
+
+NTSTATUS IndirectSyscalls::NtOpenProcess(
+ PHANDLE ProcessHandle,
+ ACCESS_MASK DesiredAccess,
+ PMY_OBJECT_ATTRIBUTES ObjectAttributes,
+ PMY_CLIENT_ID ClientId
+) {
+ if (!initialized || !fnNtOpenProcess) {
+ return STATUS_UNSUCCESSFUL;
+ }
+ if (scNtOpenProcess) {
+ return scNtOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
+ }
+ return fnNtOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
+}
+
+NTSTATUS IndirectSyscalls::NtReadVirtualMemory(
+ HANDLE ProcessHandle,
+ PVOID BaseAddress,
+ PVOID Buffer,
+ SIZE_T BufferSize,
+ PSIZE_T NumberOfBytesRead
+) {
+ if (!initialized || !fnNtReadVirtualMemory) {
+ return STATUS_UNSUCCESSFUL;
+ }
+ if (scNtReadVirtualMemory) {
+ return scNtReadVirtualMemory(ProcessHandle, BaseAddress, Buffer, BufferSize, NumberOfBytesRead);
+ }
+ return fnNtReadVirtualMemory(ProcessHandle, BaseAddress, Buffer, BufferSize, NumberOfBytesRead);
+}
+
+NTSTATUS IndirectSyscalls::NtWriteVirtualMemory(
+ HANDLE ProcessHandle,
+ PVOID BaseAddress,
+ PVOID Buffer,
+ SIZE_T BufferSize,
+ PSIZE_T NumberOfBytesWritten
+) {
+ if (!initialized || !fnNtWriteVirtualMemory) {
+ return STATUS_UNSUCCESSFUL;
+ }
+ if (scNtWriteVirtualMemory) {
+ return scNtWriteVirtualMemory(ProcessHandle, BaseAddress, Buffer, BufferSize, NumberOfBytesWritten);
+ }
+ return fnNtWriteVirtualMemory(ProcessHandle, BaseAddress, Buffer, BufferSize, NumberOfBytesWritten);
+}
+
+NTSTATUS IndirectSyscalls::NtAllocateVirtualMemory(
+ HANDLE ProcessHandle,
+ PVOID* BaseAddress,
+ ULONG_PTR ZeroBits,
+ PSIZE_T RegionSize,
+ ULONG AllocationType,
+ ULONG Protect
+) {
+ if (!initialized || !fnNtAllocateVirtualMemory) {
+ return STATUS_UNSUCCESSFUL;
+ }
+ if (scNtAllocateVirtualMemory) {
+ return scNtAllocateVirtualMemory(ProcessHandle, BaseAddress, ZeroBits, RegionSize, AllocationType, Protect);
+ }
+ return fnNtAllocateVirtualMemory(ProcessHandle, BaseAddress, ZeroBits, RegionSize, AllocationType, Protect);
+}
+
+NTSTATUS IndirectSyscalls::NtFreeVirtualMemory(
+ HANDLE ProcessHandle,
+ PVOID* BaseAddress,
+ PSIZE_T RegionSize,
+ ULONG FreeType
+) {
+ if (!initialized || !fnNtFreeVirtualMemory) {
+ return STATUS_UNSUCCESSFUL;
+ }
+ if (scNtFreeVirtualMemory) {
+ return scNtFreeVirtualMemory(ProcessHandle, BaseAddress, RegionSize, FreeType);
+ }
+ return fnNtFreeVirtualMemory(ProcessHandle, BaseAddress, RegionSize, FreeType);
+}
+
+NTSTATUS IndirectSyscalls::NtProtectVirtualMemory(
+ HANDLE ProcessHandle,
+ PVOID* BaseAddress,
+ PSIZE_T RegionSize,
+ ULONG NewProtect,
+ PULONG OldProtect
+) {
+ if (!initialized || !fnNtProtectVirtualMemory) {
+ return STATUS_UNSUCCESSFUL;
+ }
+ if (scNtProtectVirtualMemory) {
+ return scNtProtectVirtualMemory(ProcessHandle, BaseAddress, RegionSize, NewProtect, OldProtect);
+ }
+ return fnNtProtectVirtualMemory(ProcessHandle, BaseAddress, RegionSize, NewProtect, OldProtect);
+}
+
+NTSTATUS IndirectSyscalls::NtQueryInformationProcess(
+ HANDLE ProcessHandle,
+ PROCESSINFOCLASS ProcessInformationClass,
+ PVOID ProcessInformation,
+ ULONG ProcessInformationLength,
+ PULONG ReturnLength
+) {
+ if (!initialized || !fnNtQueryInformationProcess) {
+ return STATUS_UNSUCCESSFUL;
+ }
+ if (scNtQueryInformationProcess) {
+ return scNtQueryInformationProcess(ProcessHandle, ProcessInformationClass, ProcessInformation, ProcessInformationLength, ReturnLength);
+ }
+ return fnNtQueryInformationProcess(ProcessHandle, ProcessInformationClass, ProcessInformation, ProcessInformationLength, ReturnLength);
+}
+
+uint32_t IndirectSyscalls::ExtractSyscallNumber(void* fnAddress) {
+ if (!fnAddress) {
+ return UINT32_MAX;
+ }
+ const uint8_t* code = reinterpret_cast(fnAddress);
+ // 扫描前24字节,寻找 mov eax, imm32
+ for (size_t i = 0; i < 24; ++i) {
+ if (code[i] == 0xB8 && i + 4 < 24) { // mov eax, imm32
+ uint32_t ssn = *reinterpret_cast(code + i + 1);
+ return ssn;
+ }
+ }
+ return UINT32_MAX;
+}
+
+void* IndirectSyscalls::CreateSyscallStub(uint32_t ssn) {
+ if (ssn == UINT32_MAX) {
+ return nullptr;
+ }
+
+ // mov r10, rcx; mov eax, ssn; syscall; ret
+ uint8_t stubTemplate[] = {
+ 0x4C, 0x8B, 0xD1, // mov r10, rcx
+ 0xB8, 0x00, 0x00, 0x00, 0x00, // mov eax, ssn
+ 0x0F, 0x05, // syscall
+ 0xC3 // ret
+ };
+
+ stubTemplate[4] = (uint8_t)(ssn & 0xFF);
+ stubTemplate[5] = (uint8_t)((ssn >> 8) & 0xFF);
+ stubTemplate[6] = (uint8_t)((ssn >> 16) & 0xFF);
+ stubTemplate[7] = (uint8_t)((ssn >> 24) & 0xFF);
+
+ void* mem = VirtualAlloc(nullptr, sizeof(stubTemplate), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
+ if (!mem) {
+ return nullptr;
+ }
+ memcpy(mem, stubTemplate, sizeof(stubTemplate));
+ DWORD oldProt = 0;
+ VirtualProtect(mem, sizeof(stubTemplate), PAGE_EXECUTE_READ, &oldProt);
+ return mem;
+}
+
diff --git a/wx_key/src/veh_hook_manager.cpp b/wx_key/src/veh_hook_manager.cpp
new file mode 100644
index 0000000..c643900
--- /dev/null
+++ b/wx_key/src/veh_hook_manager.cpp
@@ -0,0 +1,156 @@
+#include "../include/veh_hook_manager.h"
+#include
+#include
+
+namespace {
+ VehHookManager* g_instance = nullptr;
+}
+
+VehHookManager::VehHookManager()
+ : targetAddress(0)
+ , vehHandle(nullptr)
+ , installed(false) {
+}
+
+VehHookManager::~VehHookManager() {
+ Uninstall();
+}
+
+bool VehHookManager::SetHardwareBreakpoint(uintptr_t address) {
+ HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
+ if (snapshot == INVALID_HANDLE_VALUE) {
+ return false;
+ }
+
+ THREADENTRY32 te{};
+ te.dwSize = sizeof(te);
+ std::vector> threads;
+
+ if (Thread32First(snapshot, &te)) {
+ DWORD currentPid = GetCurrentProcessId();
+ do {
+ if (te.th32OwnerProcessID == currentPid) {
+ HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
+ if (hThread) {
+ threads.emplace_back(te.th32ThreadID, hThread);
+ }
+ }
+ } while (Thread32Next(snapshot, &te));
+ }
+ CloseHandle(snapshot);
+
+ bool ok = true;
+ for (const auto& entry : threads) {
+ DWORD threadId = entry.first;
+ HANDLE hThread = entry.second;
+ CONTEXT ctx;
+ ZeroMemory(&ctx, sizeof(ctx));
+ ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
+
+ if (!GetThreadContext(hThread, &ctx)) {
+ ok = false;
+ CloseHandle(hThread);
+ continue;
+ }
+
+ originalContexts[threadId] = ctx; // 保存每个线程的原始状态
+ ctx.Dr0 = address;
+ ctx.Dr7 |= 0x1; // 启用 DR0 全局断点
+
+ if (!SetThreadContext(hThread, &ctx)) {
+ ok = false;
+ }
+ CloseHandle(hThread);
+ }
+ return ok;
+}
+
+void VehHookManager::ClearHardwareBreakpoint() {
+ HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
+ if (snapshot == INVALID_HANDLE_VALUE) {
+ return;
+ }
+
+ THREADENTRY32 te{};
+ te.dwSize = sizeof(te);
+ if (Thread32First(snapshot, &te)) {
+ DWORD currentPid = GetCurrentProcessId();
+ do {
+ if (te.th32OwnerProcessID == currentPid) {
+ HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
+ if (hThread) {
+ CONTEXT ctx{};
+ auto it = originalContexts.find(te.th32ThreadID);
+ if (it != originalContexts.end()) {
+ ctx = it->second;
+ } else {
+ ZeroMemory(&ctx, sizeof(ctx));
+ ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
+ }
+ ctx.Dr0 = 0;
+ ctx.Dr7 = 0;
+ SetThreadContext(hThread, &ctx);
+ CloseHandle(hThread);
+ }
+ }
+ } while (Thread32Next(snapshot, &te));
+ }
+ CloseHandle(snapshot);
+}
+
+bool VehHookManager::Install(uintptr_t address, std::function callback) {
+ if (installed) {
+ return false;
+ }
+
+ targetAddress = address;
+ userCallback = std::move(callback);
+
+ if (!SetHardwareBreakpoint(targetAddress)) {
+ return false;
+ }
+
+ vehHandle = AddVectoredExceptionHandler(1, VectoredHandler);
+ if (!vehHandle) {
+ ClearHardwareBreakpoint();
+ return false;
+ }
+
+ g_instance = this;
+ installed = true;
+ return true;
+}
+
+void VehHookManager::Uninstall() {
+ if (!installed) {
+ return;
+ }
+
+ ClearHardwareBreakpoint();
+
+ if (vehHandle) {
+ RemoveVectoredExceptionHandler(vehHandle);
+ vehHandle = nullptr;
+ }
+
+ originalContexts.clear();
+ installed = false;
+ g_instance = nullptr;
+}
+
+LONG CALLBACK VehHookManager::VectoredHandler(EXCEPTION_POINTERS* info) {
+ if (!g_instance || info->ExceptionRecord->ExceptionCode != EXCEPTION_SINGLE_STEP) {
+ return EXCEPTION_CONTINUE_SEARCH;
+ }
+
+ if (info->ContextRecord->Rip == g_instance->targetAddress) {
+ if (g_instance->userCallback) {
+ g_instance->userCallback(info);
+ }
+ // 清除单步标志,继续执行
+ info->ContextRecord->EFlags &= ~0x100;
+ return EXCEPTION_CONTINUE_EXECUTION;
+ }
+
+ return EXCEPTION_CONTINUE_SEARCH;
+}
diff --git a/wx_key/vendor/MinHook_134_lib/include/MinHook.h b/wx_key/vendor/MinHook_134_lib/include/MinHook.h
new file mode 100644
index 0000000..492d83f
--- /dev/null
+++ b/wx_key/vendor/MinHook_134_lib/include/MinHook.h
@@ -0,0 +1,185 @@
+/*
+ * MinHook - The Minimalistic API Hooking Library for x64/x86
+ * Copyright (C) 2009-2017 Tsuda Kageyu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if !(defined _M_IX86) && !(defined _M_X64) && !(defined __i386__) && !(defined __x86_64__)
+ #error MinHook supports only x86 and x64 systems.
+#endif
+
+#include
+
+// MinHook Error Codes.
+typedef enum MH_STATUS
+{
+ // Unknown error. Should not be returned.
+ MH_UNKNOWN = -1,
+
+ // Successful.
+ MH_OK = 0,
+
+ // MinHook is already initialized.
+ MH_ERROR_ALREADY_INITIALIZED,
+
+ // MinHook is not initialized yet, or already uninitialized.
+ MH_ERROR_NOT_INITIALIZED,
+
+ // The hook for the specified target function is already created.
+ MH_ERROR_ALREADY_CREATED,
+
+ // The hook for the specified target function is not created yet.
+ MH_ERROR_NOT_CREATED,
+
+ // The hook for the specified target function is already enabled.
+ MH_ERROR_ENABLED,
+
+ // The hook for the specified target function is not enabled yet, or already
+ // disabled.
+ MH_ERROR_DISABLED,
+
+ // The specified pointer is invalid. It points the address of non-allocated
+ // and/or non-executable region.
+ MH_ERROR_NOT_EXECUTABLE,
+
+ // The specified target function cannot be hooked.
+ MH_ERROR_UNSUPPORTED_FUNCTION,
+
+ // Failed to allocate memory.
+ MH_ERROR_MEMORY_ALLOC,
+
+ // Failed to change the memory protection.
+ MH_ERROR_MEMORY_PROTECT,
+
+ // The specified module is not loaded.
+ MH_ERROR_MODULE_NOT_FOUND,
+
+ // The specified function is not found.
+ MH_ERROR_FUNCTION_NOT_FOUND
+}
+MH_STATUS;
+
+// Can be passed as a parameter to MH_EnableHook, MH_DisableHook,
+// MH_QueueEnableHook or MH_QueueDisableHook.
+#define MH_ALL_HOOKS NULL
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ // Initialize the MinHook library. You must call this function EXACTLY ONCE
+ // at the beginning of your program.
+ MH_STATUS WINAPI MH_Initialize(VOID);
+
+ // Uninitialize the MinHook library. You must call this function EXACTLY
+ // ONCE at the end of your program.
+ MH_STATUS WINAPI MH_Uninitialize(VOID);
+
+ // Creates a hook for the specified target function, in disabled state.
+ // Parameters:
+ // pTarget [in] A pointer to the target function, which will be
+ // overridden by the detour function.
+ // pDetour [in] A pointer to the detour function, which will override
+ // the target function.
+ // ppOriginal [out] A pointer to the trampoline function, which will be
+ // used to call the original target function.
+ // This parameter can be NULL.
+ MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal);
+
+ // Creates a hook for the specified API function, in disabled state.
+ // Parameters:
+ // pszModule [in] A pointer to the loaded module name which contains the
+ // target function.
+ // pszProcName [in] A pointer to the target function name, which will be
+ // overridden by the detour function.
+ // pDetour [in] A pointer to the detour function, which will override
+ // the target function.
+ // ppOriginal [out] A pointer to the trampoline function, which will be
+ // used to call the original target function.
+ // This parameter can be NULL.
+ MH_STATUS WINAPI MH_CreateHookApi(
+ LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal);
+
+ // Creates a hook for the specified API function, in disabled state.
+ // Parameters:
+ // pszModule [in] A pointer to the loaded module name which contains the
+ // target function.
+ // pszProcName [in] A pointer to the target function name, which will be
+ // overridden by the detour function.
+ // pDetour [in] A pointer to the detour function, which will override
+ // the target function.
+ // ppOriginal [out] A pointer to the trampoline function, which will be
+ // used to call the original target function.
+ // This parameter can be NULL.
+ // ppTarget [out] A pointer to the target function, which will be used
+ // with other functions.
+ // This parameter can be NULL.
+ MH_STATUS WINAPI MH_CreateHookApiEx(
+ LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal, LPVOID *ppTarget);
+
+ // Removes an already created hook.
+ // Parameters:
+ // pTarget [in] A pointer to the target function.
+ MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget);
+
+ // Enables an already created hook.
+ // Parameters:
+ // pTarget [in] A pointer to the target function.
+ // If this parameter is MH_ALL_HOOKS, all created hooks are
+ // enabled in one go.
+ MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget);
+
+ // Disables an already created hook.
+ // Parameters:
+ // pTarget [in] A pointer to the target function.
+ // If this parameter is MH_ALL_HOOKS, all created hooks are
+ // disabled in one go.
+ MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget);
+
+ // Queues to enable an already created hook.
+ // Parameters:
+ // pTarget [in] A pointer to the target function.
+ // If this parameter is MH_ALL_HOOKS, all created hooks are
+ // queued to be enabled.
+ MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget);
+
+ // Queues to disable an already created hook.
+ // Parameters:
+ // pTarget [in] A pointer to the target function.
+ // If this parameter is MH_ALL_HOOKS, all created hooks are
+ // queued to be disabled.
+ MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget);
+
+ // Applies all queued changes in one go.
+ MH_STATUS WINAPI MH_ApplyQueued(VOID);
+
+ // Translates the MH_STATUS to its name as a string.
+ const char * WINAPI MH_StatusToString(MH_STATUS status);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/wx_key/vendor/MinHook_134_lib/lib/libMinHook.x64.lib b/wx_key/vendor/MinHook_134_lib/lib/libMinHook.x64.lib
new file mode 100644
index 0000000..e211ad6
Binary files /dev/null and b/wx_key/vendor/MinHook_134_lib/lib/libMinHook.x64.lib differ
diff --git a/wx_key/vendor/MinHook_134_lib/lib/libMinHook.x86.lib b/wx_key/vendor/MinHook_134_lib/lib/libMinHook.x86.lib
new file mode 100644
index 0000000..b15b79b
Binary files /dev/null and b/wx_key/vendor/MinHook_134_lib/lib/libMinHook.x86.lib differ
diff --git a/wx_key/vendor/xbyak/xbyak/xbyak.h b/wx_key/vendor/xbyak/xbyak/xbyak.h
new file mode 100644
index 0000000..81f08d2
--- /dev/null
+++ b/wx_key/vendor/xbyak/xbyak/xbyak.h
@@ -0,0 +1,3470 @@
+#pragma once
+#ifndef XBYAK_XBYAK_H_
+#define XBYAK_XBYAK_H_
+/*!
+ @file xbyak.h
+ @brief Xbyak ; JIT assembler for x86(IA32)/x64 by C++
+ @author herumi
+ @url https://github.com/herumi/xbyak
+ @note modified new BSD license
+ http://opensource.org/licenses/BSD-3-Clause
+*/
+#if (not +0) && !defined(XBYAK_NO_OP_NAMES) // trick to detect whether 'not' is operator or not
+ #define XBYAK_NO_OP_NAMES
+#endif
+
+#include // for debug print
+#include
+#include
+#include
+#include
+#ifndef NDEBUG
+#include
+#endif
+
+// #define XBYAK_DISABLE_AVX512
+
+#if !defined(XBYAK_USE_MMAP_ALLOCATOR) && !defined(XBYAK_DONT_USE_MMAP_ALLOCATOR)
+ #define XBYAK_USE_MMAP_ALLOCATOR
+#endif
+#if !defined(__GNUC__) || defined(__MINGW32__)
+ #undef XBYAK_USE_MMAP_ALLOCATOR
+#endif
+
+#ifdef __GNUC__
+ #define XBYAK_GNUC_PREREQ(major, minor) ((__GNUC__) * 100 + (__GNUC_MINOR__) >= (major) * 100 + (minor))
+#else
+ #define XBYAK_GNUC_PREREQ(major, minor) 0
+#endif
+
+// This covers -std=(gnu|c)++(0x|11|1y), -stdlib=libc++, and modern Microsoft.
+#if ((defined(_MSC_VER) && (_MSC_VER >= 1600)) || defined(_LIBCPP_VERSION) ||\
+ ((__cplusplus >= 201103) || defined(__GXX_EXPERIMENTAL_CXX0X__)))
+ #include
+ #define XBYAK_STD_UNORDERED_SET std::unordered_set
+ #include
+ #define XBYAK_STD_UNORDERED_MAP std::unordered_map
+ #define XBYAK_STD_UNORDERED_MULTIMAP std::unordered_multimap
+
+/*
+ Clang/llvm-gcc and ICC-EDG in 'GCC-mode' always claim to be GCC 4.2, using
+ libstdcxx 20070719 (from GCC 4.2.1, the last GPL 2 version).
+*/
+#elif XBYAK_GNUC_PREREQ(4, 5) || (XBYAK_GNUC_PREREQ(4, 2) && __GLIBCXX__ >= 20070719) || defined(__INTEL_COMPILER) || defined(__llvm__)
+ #include
+ #define XBYAK_STD_UNORDERED_SET std::tr1::unordered_set
+ #include
+ #define XBYAK_STD_UNORDERED_MAP std::tr1::unordered_map
+ #define XBYAK_STD_UNORDERED_MULTIMAP std::tr1::unordered_multimap
+
+#elif defined(_MSC_VER) && (_MSC_VER >= 1500) && (_MSC_VER < 1600)
+ #include
+ #define XBYAK_STD_UNORDERED_SET std::tr1::unordered_set
+ #include
+ #define XBYAK_STD_UNORDERED_MAP std::tr1::unordered_map
+ #define XBYAK_STD_UNORDERED_MULTIMAP std::tr1::unordered_multimap
+
+#else
+ #include
+ #define XBYAK_STD_UNORDERED_SET std::set
+ #include