本文档旨在帮助开发者快速理解并集成 wx_key.dll。该组件封装了微信逆向工程的核心逻辑,让你无需关心底层的内存扫描与 Hook 实现,即可在 C#、Flutter 或 C++ 等上层应用中获取微信数据库密钥与图片密钥。
简单来说,wx_key.dll 充当了宿主程序与微信进程之间的桥梁。
- 注入与扫描:当你的程序加载此 DLL 并调用初始化后,它会通过
RemoteScanner扫描微信进程内存,利用特征码定位密钥获取函数的入口(支持 4.x 多个版本)。PID 由外部传入,DLL 不再枚举进程。 - 拦截与共享:定位成功后,DLL 会写入一段 Shellcode 进行 Hook。当微信尝试读取数据库时,Shellcode 会拦截 32 字节的密钥,将其拷贝到共享内存缓冲中,并递增一个
sequenceNumber。 - 轮询机制:DLL 内部使用事件唤醒 + 轮询监听线程。你的程序只需定时检查共享内存是否有新的
sequenceNumber,即可拿到密钥。
⚠️ 环境硬性要求:
- 架构:仅支持 x64 系统与 64 位微信客户端(Shellcode 为 x64 汇编)。
- 权限:调用进程若失败可能需要 管理员身份(Administrator) 运行。
所有导出函数均为 C 风格接口,声明文件可见 wx_key/include/hook_controller.h。
| 接口函数 | 参数说明 | 详细描述 |
|---|---|---|
InitializeHook |
DWORD targetPid (微信进程ID) |
启动入口。执行远程扫描、分配共享内存并注入 Shellcode。成功返回 true,失败请调 GetLastErrorMsg。PID 需要调用方自行获取。 |
PollKeyData |
char* keyBufint size (建议 >= 65) |
获取密钥。非阻塞检查。如果捕获到密钥,会将其格式化为 64 位 HEX 字符串写入缓冲区并返回 true。一次读取后自动清空。 |
GetStatusMessage |
char* msgBufint sizeint* outLevel |
获取日志。读取 DLL 内部运行日志(如“扫描成功”、“特征码未找到”等)。outLevel 对应:0=Info, 1=Success, 2=Error。 |
CleanupHook |
无 | 清理资源。卸载远程 Hook、释放共享内存并关闭句柄。程序退出前务必调用。 |
GetLastErrorMsg |
无 | 错误诊断。返回最近一次操作失败的具体原因。 |
无论使用哪种语言,集成步骤都应该要遵循以下流程:
自行查找 Weixin.exe 的 PID(进程 ID),DLL 不再替你枚举进程。
将 wx_key.dll 加载到当前进程空间。
- Flutter:
DynamicLibrary.open('assets/dll/wx_key.dll') - C#:
NativeLibrary.Load("wx_key.dll")
调用 InitializeHook(pid)。
- 如果返回
false,通常是因为权限不足或微信版本不支持(特征码失效),请立即打印GetLastErrorMsg()排查。
启动一个后台线程或定时器(建议间隔 100ms),循环调用 PollKeyData 和 GetStatusMessage。
- 注意:不要在 UI 线程直接做死循环,也不要设置过长的等待时间。
PollKeyData每次返回后会清空共享缓冲;若需要去重,可对sequenceNumber进行本地缓存比对。
在程序关闭或不再需要功能时,必须调用 CleanupHook()。
- 如果不清理,残留在微信进程内的 Shellcode 可能会在微信后续运行时导致崩溃。
以下代码展示了如何通过 P/Invoke 封装一个健壮的调用类:
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(); // 确保线程退出时清理环境
}
});
}
}在实际集成中,你可能会遇到这些问题:
-
缓冲区溢出:
PollKeyData返回的是 Hex 字符串,加上结束符至少需要 65 字节。C# 的StringBuilder或 C++ 的char[]分配小了会导致内存踩踏,建议给 128 字节 以防万一。 -
单例原则: 同一个微信进程只能被 Hook 一次。如果需要重启扫描,请先调用
CleanupHook彻底释放资源,再重新InitializeHook。 -
版本兼容性: 如果遇到微信更新导致无法获取密钥,通常是特征码偏移变了。此时无需修改上层代码,只需更新 DLL 源码中的
RemoteScanner特征码库并重新编译 DLL 即可。 -
日志前缀: DLL 返回的
GetStatusMessage字符串不再附加[INFO]/[SUCCESS]等标签,UI 层可依据outLevel自行添加前缀。 -
共享内存结构(供参考):
typedef struct { DWORD dataSize; // 固定 32 BYTE keyBuffer[32]; // 32 字节密钥 DWORD sequenceNumber; // 每次写入 +1,用于去重 } SharedKeyData;
Shellcode 会在
dataSize == 32时写入并递增sequenceNumber。上层可以持有上次读到的sequenceNumber来避免重复处理同一条数据。 -
多线程安全: 虽然导出函数内部是线程安全的,但为了逻辑清晰,建议仅在一个专用的 Monitor 线程中进行轮询操作。