Skip to content

Latest commit

 

History

History
164 lines (120 loc) · 7.35 KB

File metadata and controls

164 lines (120 loc) · 7.35 KB

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),循环调用 PollKeyDataGetStatusMessage

  • 注意:不要在 UI 线程直接做死循环,也不要设置过长的等待时间。
  • PollKeyData 每次返回后会清空共享缓冲;若需要去重,可对 sequenceNumber 进行本地缓存比对。

第五步:清理 (Cleanup)

在程序关闭或不再需要功能时,必须调用 CleanupHook()

  • 如果不清理,残留在微信进程内的 Shellcode 可能会在微信后续运行时导致崩溃。

4. 代码集成示例 (C#)

以下代码展示了如何通过 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(); // 确保线程退出时清理环境
            }
        });
    }
}

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. 共享内存结构(供参考)

    typedef struct {
        DWORD dataSize;        // 固定 32
        BYTE  keyBuffer[32];   // 32 字节密钥
        DWORD sequenceNumber;  // 每次写入 +1,用于去重
    } SharedKeyData;

    Shellcode 会在 dataSize == 32 时写入并递增 sequenceNumber。上层可以持有上次读到的 sequenceNumber 来避免重复处理同一条数据。

  6. 多线程安全: 虽然导出函数内部是线程安全的,但为了逻辑清晰,建议仅在一个专用的 Monitor 线程中进行轮询操作。