说明:这篇主要记录 macOS 企业微信 5.0.7 arm64 的二维码登录链路。内容来自本地 IDA、Frida hook 日志和 protobuf 重建结果,只做协议和逆向思路整理,不涉及绕过鉴权或线上攻击。
上一篇主要把企业微信 mac 5.0.7 里 protobuf 序列化、反序列化的 hook 点定位了一遍,也顺手整理了 generated-lite 消息怎么从 IDA 里补出字段号和 wire type。光能抓到 proto 还不够,下一步自然就是把这些零散的请求串起来,看它们在登录流程里分别干什么。
所以这篇继续往二维码登录链路走。整体看下来,二维码登录本身还是短链 CGI,但中间会穿插 ST 票据、AES-CBC 包裹、登录签名、wcsecurity 环境上报这些东西。如果只盯一个 proto 或一个 cmd,很容易看得一头雾水。
我这边最后把它拆成了几块:
目标版本:
主要工具:
这次比较依赖两类日志:
这里要先说清楚:抓到 SSL_write 前明文,不代表 TLS 没有加密。只是 hook 点在 TLS 加密之前,所以能看到应用层已经组好的 HTTP / protobuf 数据。
企业微信里不是所有 proto 都有完整 descriptor。能直接扫到的 descriptor 只覆盖一部分,登录相关不少消息是 generated-lite 风格,只剩序列化/反序列化代码,没有原始字段名。
我的做法是三步:
generated-lite 这块的核心规律还挺稳定:
比如 CRTX.GetQrcodeReq:
字段号主要看 serializer 里 writer 调用前的立即数。注意 presence bit 不能直接当字段号,比如 TBNZ Wxx,#9 只表示 has-bit,不表示字段 9。真正字段号还是要看 writer 前面的 MOV W0,#n。
最后恢复出来的结构只是“能解码”的骨架,不等于官方原始 proto。字段名我一般先写 field_1、field_2,再结合运行时样本慢慢补语义。
几个登录相关结构如下。
登录短链几个请求都发到:
HTTP 头里有一个容易误会的字段:
名字叫 HeadHex,但内容不是 hex,而是 Base64 编码后的 WWReqHead。
HTTP body 的通用格式是:
也就是:
IDA 里公共封包点在:
这个函数里能看到:
普通 TLS 写入点:
关键调用就是:
所以这里 hook 到的是 TLS 加密前的 HTTP 明文。
最终梳理出来的主流程如下:
几个 cmd 对应关系:
IDA 里二维码入口:
DoOnInterval 里有两条路径:
一次真实网络包的切分:
GetQrcodeReq 里面比较重要的字段:
字段 16 容易和字段 4 搞混。字段 4 是带横线 UUID,后面很多请求也会带;字段 16 是本地配置里的 DeviceIdKey,形态是 32 位小写 hex。
IDA 里能看到字段 16 的来源:
如果本地没有 DeviceIdKey,初始化路径会从 /dev/urandom 取 16 字节,设置 UUID v4 相关位,再格式化成 32 位 hex 保存。所以它不是二维码 key 算出来的,也不是服务端临时下发的。
字段 18 是设备画像。样本里能看到:
其中“机器序列号 + 去横线 GUID”这一项,后半段更像首次生成后持久化的 UUID v4,不是拿序列号再 hash 一遍。
CheckQrcodeReq 很简单,核心就是:
真实包切分:
qrcode_key 是服务端给的二维码会话 key,常见形态:
这个值不是本地生成,不是 gap token,也不是设备 id。客户端只是从 GetQrcodeRsp.key 或后续 CheckQrcodeRsp.qrcode_key 保存下来,然后每隔一段时间回填。
扫码确认后,CheckQrcodeRsp.status 会变成 2。这时响应里会出现后续登录需要的材料:
紧接着就会进入 GetStReq(cmd=3004)。
这一步开始就不是单纯明文 proto 了。
流程:
一句话规则:
真实网络包:
GetStReqPlain 解出来大概是:
注意这里 appid 变成了 "8",和二维码阶段的 "6" 不一样。
响应 GetStRspPlain 里拿到:
后面 LoginReq 就靠这三个继续走。
LoginReq 的组包函数在:
反编译里有两个很好认的日志字符串:
所以它的行为基本可以确定:
一句话规则:
真实网络包:
这里有个很实际的坑:
也就是说线上发的是压缩后的 LoginReq,不能直接拿未压缩长度填 bodylen。
LoginReqPlain 里主要是:
响应处理函数在:
里面有这些日志字符串:
逻辑也比较直:
cmd=10 的 HTTP 头里还多了:
Base64 解出来是 SignInfo:
这个 sign 在样本里验证为:
IDA 证据链:
sub_1069F8260 里能看到先构造原始 sign protobuf,再走 Base64。0x1076270B0 这个函数名我这边已经重命名成 wework_hmac_sha1_obf,里面会写出:
这个点如果没补,LoginReq 的外形看着对,服务端也可能不认。
登录成功后很快会出现 wcsecurity 上报:
这块不是主登录必需的四个请求之一,但它对设备环境、风控判断比较重要。
spambuff 外层大概是:
inner 里比较关键:
当前样本确认的 key / iv:
解开后的环境字段很多,包括:
wcsecurity 相关 IDA 点:
这块我目前更倾向于把它当“登录后环境上报”单独分析,不强行并入二维码登录主链路。主链路是 34 -> 35 -> 3004 -> 10,242 是登录完成后的风控/环境侧动作。
这次比较有用的 hook 点:
我的对齐顺序一般是:
如果这几层的时间、cmdid、长度都能对上,基本就能确定这包是怎么出去的。
最后列几个坑,都是调的时候真实踩过的。
HeadHex 名字很迷惑,它是 Base64。先 Base64 decode,再按 WWReqHead protobuf 解。
cmd=34 和 cmd=10 的 payload 都是 78 9c 开头,说明做了 zlib。WWReqHead.field_7 是压缩后业务 payload 长度,不是原始 proto 长度。
cmd=35 payload 直接 raw decode 就行,别看到短链就一股脑 zlib。
[招生]科锐逆向工程师培训(2026年7月3日实地,远程教学同时开班, 第56期)!
最后于 1天前
被xiaoxinre编辑
,原因: