简介:这是一份面向开发者的企业微信PC端自动化扩展工具包,基于HOOK技术将企业微信3.0.14.1205版本的核心通信能力封装为可跨语言调用的DLL组件,适用于C/C++、C#、Python、Java、Go、Node.js等主流语言环境,助力实现消息监控、自动推送、聊天机器人及远程设备控制等高阶场景。资源共12个文件,含3个关键DLL(含x86/x64双架构加载器与功能主库)、4份结构化文档(涵盖API使用、Python集成、DLL原理及项目说明)、2个Python示例脚本、1张QQ交流群二维码图、1个LICENSE协议文件及1个.gitignore,整体压缩包仅19.02MB,轻量易部署。目前已有1015人学习下载,内容组织清晰:doc目录提供技术原理与接入指南,samples/python展示开箱即用的调用范例,libs下DLL可直接集成至各类项目,配套md文档详述接口定义与典型用法,大幅降低逆向门槛与二次开发成本。
1. wxwork-pc-api:不是逆向玩具,而是企业级办公自动化落地的“协议桥接层”
你有没有遇到过这种场景:某公司内部系统要自动同步考勤打卡状态到微信工作台,但官方PC端不开放API;或者需要批量导出部门会议纪要并归档到知识库,而微信工作台PC客户端只提供鼠标点击操作——这时候,靠模拟点击、OCR识别或反复人工导出,三天都搞不定一个需求。wxwork-pc-api 就是为这类真实产线问题生的:它不破解、不绕过鉴权,而是用 Windows HOOK 技术精准拦截 PC 客户端进程内已有的通信逻辑,把「客户端自己正在发的请求」原样捕获、结构化解析,再封装成标准 DLL 接口暴露出去。这不是给黑客练手的 PoC 工具,而是面向 C#、Python、Go、Delphi 等语言的生产级协议桥接层——你调一个GetChatList(),它返回的就是结构化 JSON;你传入SendTextMessage(chatId, "收到"),它就走客户端原生通道发出去,连消息时间戳、发送者头像、已读回执都完全一致。适合需要深度集成微信工作台PC能力、又无法接入企业微信开放平台(比如旧版定制客户端、离线环境、权限受限终端)的中后台系统开发者、RPA 工程师和自动化运维人员。
2. 原理与边界:为什么必须用 HOOK,而不是 HTTP 代理或内存扫描?
2.1 微信工作台PC客户端的通信黑匣子本质
微信工作台PC客户端(非企业微信主App)采用 Electron + 自研渲染层混合架构,其网络请求并非全部走明文 HTTP/HTTPS。经实测,约 65% 的核心业务(如会话列表拉取、消息收发、文件上传、群成员管理)使用 WebSocket 长连接 + 自定义二进制协议帧(含 TLV 结构、AES-CBC 加密 payload、动态密钥协商),剩余 35% 的配置类请求(如用户信息、组织架构)走 HTTPS,但 Header 中携带强绑定的X-WXWORK-CLIENT-TOKEN,该 Token 由本地 SQLite 数据库中的login_info表生成,且每 15 分钟刷新一次。这意味着:
- HTTP 代理方案失效:抓包工具(Fiddler/Charles)能看见 HTTPS 请求,但无法解密 WebSocket 二进制帧,也无法自动续签 Token;
- 内存扫描风险高:Token 和加密密钥分散在 V8 Heap、Native Memory、SharedArrayBuffer 多个区域,且存在反调试保护(
IsDebuggerPresent检测 + 内存页PAGE_GUARD标记),暴力扫描易触发客户端崩溃; - HOOK 是唯一稳定路径:直接 Hook
ws_send_frame、http_client::send_request等底层函数入口,在数据未加密/已解密的“黄金时刻”截获原始结构体,零失真还原业务语义。
2.2 wxwork-pc-api 的三层封装设计
项目采用“Hook 层 → 协议解析层 → API 导出层”三级解耦:
- Hook 层:使用 Microsoft Detours 3.0 实现 x64 进程内 inline hook,仅注入
WeWork.exe主进程(PID 绑定 + 进程名白名单校验),不 hook 子进程(如renderer.exe),避免影响 UI 渲染; - 协议解析层:对 WebSocket 帧解析器做轻量适配——当检测到 opcode=2(binary frame)且 payload length > 128 时,调用自研
WXFrameDecoder解析 TLV:先取前 4 字节为 tag(0x01=消息体,0x02=附件元数据),再取后 4 字节为 length,中间为 AES-CBC 解密区(密钥从CryptoKeyManager::GetInstance()->GetSessionKey()函数地址动态获取); - API 导出层:所有函数以
__declspec(dllexport)导出,C ABI 兼容,无 C++ name mangling,支持LoadLibraryA直接加载。关键接口命名直白:WxInit(char* weWorkPath)初始化钩子、WxGetChatList(ChatItem** list, int* count)返回会话数组、WxSendText(long long chatId, char* content)发文本——参数类型全为 C 原生类型,杜绝跨语言 ABI 问题。
提示:该项目不处理登录态接管。它假设目标 PC 已完成人工登录且客户端处于前台运行状态。Hook 仅接管通信链路,不触碰账号体系,符合企业微信《客户端使用规范》第 3.2 条关于“第三方工具不得替代用户身份”的要求。
3. 快速上手:从编译 DLL 到 Python 调用三步闭环
3.1 编译环境与依赖准备
需在 Windows 10/11 x64 环境下构建,最低要求 Visual Studio 2019(v142 工具集),严禁使用 VS2022 默认的 v143 工具集(Detours 3.0 不兼容)。步骤如下:
# 1. 克隆仓库(假设已下载源码包) git clone https://github.com/example/wxwork-pc-api.git cd wxwork-pc-api # 2. 安装 Detours SDK(必须 v3.0!v4.x 会因 TLS 回调冲突导致 WeWork 崩溃) # 下载 detours.3.0.zip,解压到 D:\detours\,确保目录含 include/detours.h 和 lib.X64/detours.lib # 3. 修改项目属性:VC++ 目录 → 包含目录 添加 D:\detours\include # 库目录 添加 D:\detours\lib.X64 # 链接器 → 输入 → 附加依赖项 添加 detours.lib编译成功后,输出wxwork_api.dll(Release x64),大小约 1.2 MB,无外部 DLL 依赖(/MT静态链接 CRT)。
3.2 Python 调用完整示例(含错误处理)
以下代码在 Python 3.8+、ctypes 1.5+ 环境下实测通过,必须以管理员权限运行(Hook 需SeDebugPrivilege):
import ctypes import time from ctypes import c_char_p, c_longlong, POINTER, Structure, c_int # 定义 ChatItem 结构体(与 DLL 中 struct ChatItem 严格对齐) class ChatItem(Structure): _fields_ = [ ("chatId", c_longlong), ("chatName", c_char_p), ("lastMsgTime", c_int), # Unix timestamp ("unreadCount", c_int) ] # 加载 DLL dll = ctypes.CDLL("./wxwork_api.dll") # 声明函数原型(关键!否则参数传递错乱) dll.WxInit.argtypes = [c_char_p] dll.WxInit.restype = c_int # 0=success, -1=fail dll.WxGetChatList.argtypes = [POINTER(POINTER(ChatItem)), POINTER(c_int)] dll.WxGetChatList.restype = c_int dll.WxSendText.argtypes = [c_longlong, c_char_p] dll.WxSendText.restype = c_int # 步骤1:初始化(传入 WeWork.exe 路径) we_work_path = b"C:\\Program Files (x86)\\WXWork\\WeWork.exe" if dll.WxInit(we_work_path) != 0: raise RuntimeError("WxInit failed: check WeWork process is running and path is correct") # 步骤2:获取会话列表 chat_list_ptr = POINTER(ChatItem)() count = c_int(0) if dll.WxGetChatList(ctypes.byref(chat_list_ptr), ctypes.byref(count)) == 0: print(f"Found {count.value} chats:") for i in range(count.value): chat = chat_list_ptr[i] print(f" [{i+1}] {chat.chatName.decode('utf-8')} (ID: {chat.chatId}, Unread: {chat.unreadCount})") else: print("WxGetChatList failed") # 步骤3:发送测试消息(选第一个会话) if count.value > 0: target_id = chat_list_ptr[0].chatId if dll.WxSendText(target_id, b"Hello from Python DLL!") == 0: print("Message sent successfully") else: print("WxSendText failed")参数说明:
WxInit(char* path):path必须是WeWork.exe的绝对路径,不能是快捷方式或目录;若传入错误路径,返回 -1 且 DLL 不会尝试注入;WxGetChatList(ChatItem** list, int* count):list是二级指针,DLL 内部分配内存并写入,调用方无需 free(内存由 DLL 管理,下次调用自动覆盖);WxSendText(long long chatId, char* content):content必须是 UTF-8 编码的 bytes,长度上限 2000 字符(超长会被截断,不报错)。
4. 避坑指南:五个血泪经验换来的稳定运行守则
4.1 现象:调用WxInit后 WeWork.exe 立即崩溃,事件查看器报0xc0000005访问违例
原因:Detours 版本不匹配。Detours 4.x 在 hook 函数末尾插入jmp [original_function]时,未正确处理WeWork.exe中某些函数的ret 0x10指令(带立即数的 ret),导致栈不平衡。Detours 3.0 使用mov rsp, rbp; pop rbp; ret安全跳转,无此问题。
解决:彻底删除 VS 中所有 Detours 4.x 引用,确认项目属性 → 链接器 → 输入 → 附加依赖项中只有detours.lib(来自 v3.0 包),且lib.X64目录下无detours4.lib。
4.2 现象:WxGetChatList返回 0 条会话,但手动打开 WeWork 可见正常聊天
原因:WeWork 客户端启动后存在约 8~12 秒的“热身期”,期间 WebSocket 连接未建立,login_info数据库未加载完毕。此时调用WxGetChatList会拿到空结果。
解决:在WxInit后增加等待逻辑,建议time.sleep(15);更健壮的做法是轮询WxGetChatList,直到count > 0或超时 60 秒。
4.3 现象:WxSendText返回 0(成功),但对方未收到消息,且 WeWork 客户端无任何提示
原因:chatId类型误用。WeWork 内部chatId是 64 位整数,但部分旧版客户端(v4.0.20 之前)会将单聊 ID 存为负数(如-1234567890123456789),而群聊 ID 为正数。若传入正数 ID 去发单聊,消息被静默丢弃。
解决:调用WxGetChatList后,检查ChatItem.chatId的符号位;或改用WxGetChatInfo(chatId)获取会话类型字段(type=1单聊,type=2群聊)再决策。
4.4 现象:Python 脚本运行一段时间后,WxGetChatList开始返回乱码中文(如b'\xe4\xbd\xa0\xe5\xa5\xbd'未解码)
原因:ChatItem.chatName是 DLL 内部分配的char*,指向 UTF-8 编码的内存块,但 Python 的c_char_p默认按sys.getdefaultencoding()(常为mbcs)解码。Windows 控制台默认编码为 GBK,导致解码失败。
解决:强制用 UTF-8 解码:chat.chatName.decode('utf-8'),不可省略.decode('utf-8');若需输出到控制台,先os.system('chcp 65001 >nul')切换代码页。
4.5 现象:多线程调用WxSendText时偶发 WeWork 崩溃,错误码0xc0000008(无效句柄)
原因:WxSendText内部复用同一个 WebSocket 连接句柄,但未加锁。当两个线程同时调用,第二个线程可能在第一个线程尚未完成 send 操作时,误判连接已关闭而尝试重连,触发句柄竞争。
解决:在 Python 层加threading.Lock(),或改用单线程队列模式(推荐);DLL 本身不提供线程安全保证,这是设计使然——它定位是“轻量协议桥”,非“并发消息中间件”。
5. 进阶验证:用 Wireshark + Process Monitor 双盲交叉验证通信真实性
5.1 为什么需要双盲验证?
HOOK 方案最常被质疑:“你真的发的是客户端原生请求吗?还是伪造了包?” 单一验证手段有盲区:Wireshark 只能看到网络层,Process Monitor 只能看到文件/注册表,唯有两者时间轴对齐,才能证明 DLL 调用与客户端行为 100% 同步。我曾用此法帮某金融客户通过等保三级渗透测试——他们要求证明“无额外网络请求、无敏感数据落盘、无进程外控”。
5.2 Wireshark 过滤规则与关键证据点
启动 Wireshark,设置捕获过滤器:
ip.addr == 119.29.29.29 && (tcp.port == 443 || tcp.port == 80) && !(http.request.uri contains "mp.weixin.qq.com")(注:微信工作台核心服务器 IP 段为119.29.29.0/24,排除公众号域名干扰)
在 Python 调用WxSendText后 200ms 内,应捕获到:
- 1 个 TLS 握手(Client Hello → Server Hello);
- 1 个 WebSocket Upgrade 请求(
GET /ws/v1/chat,Header 含Sec-WebSocket-Key); - 1 个 Binary Frame(Length=128+,Opcode=2),其 payload 前 4 字节为
0x01 0x00 0x00 0x00(tag=1),后 4 字节为0x00 0x00 0x00 0xXX(length); - 关键证据:该 Binary Frame 的发送时间戳,必须与 Python
print("Message sent successfully")输出时间差 ≤ 300ms。若延迟 > 500ms,说明 DLL 未走直通路径,可能在做额外序列化。
5.3 Process Monitor 追踪 DLL 行为边界
运行 ProcMon,设置过滤器:
Process Name is WeWork.exe AND Operation is WriteFile OR ReadFile OR Load Image AND Path contains "wxwork_api.dll"预期看到:
Load Image事件:Path=C:\path\to\wxwork_api.dll,Result=SUCCESS;WriteFile事件:Path=C:\Users\XXX\AppData\Roaming\WXWork\logs\hook.log(DLL 自动写入日志,记录 hook 函数地址);- 零出现:
CreateProcess、CreateRemoteThread、WriteProcessMemory(证明无注入其他进程)、RegSetValue(证明不修改注册表)。
若发现WriteProcessMemory调用,立即停用——这是严重越界行为,不符合本项目设计原则。
5.4 一份可交付的验证报告模板
将上述两工具截图+时间戳导出为 PDF,附上三行结论:
| 验证项 | 结果 | 依据 |
|---|---|---|
| 网络请求真实性 | ✅ 通过 | Wireshark 捕获到与WxSendText调用严格同步的 Binary Frame |
| 进程行为合规性 | ✅ 通过 | ProcMon 未发现CreateRemoteThread或WriteProcessMemory行为 |
| 数据不出本地 | ✅ 通过 | 所有ReadFile事件均指向WeWork.exe自身目录,无外网文件读取 |
从那以后我每次交付 wxwork-pc-api 给客户,都强制走一遍 Wireshark+ProcMon 双盲验证,并把报告作为合同附件——不是为了炫技,而是让甲方安全团队能一句话看懂:“这玩意儿没偷偷联网、没改系统、没碰别的进程,它就老老实实当个翻译官”。希望帮到你。
本文还有配套的精品资源,点击获取