首页
社区
课程
招聘
[原创]macOS企业微信5.0.7逆向分析(二):登录链路分析
发表于: 1天前 866

[原创]macOS企业微信5.0.7逆向分析(二):登录链路分析

1天前
866

说明:这篇主要记录 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_1field_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 -> 10242 是登录完成后的风控/环境侧动作。

这次比较有用的 hook 点:

我的对齐顺序一般是:

如果这几层的时间、cmdid、长度都能对上,基本就能确定这包是怎么出去的。

最后列几个坑,都是调的时候真实踩过的。

HeadHex 名字很迷惑,它是 Base64。先 Base64 decode,再按 WWReqHead protobuf 解。

cmd=34cmd=10 的 payload 都是 78 9c 开头,说明做了 zlib。WWReqHead.field_7 是压缩后业务 payload 长度,不是原始 proto 长度。

cmd=35 payload 直接 raw decode 就行,别看到短链就一股脑 zlib。


[招生]科锐逆向工程师培训(2026年7月3日实地,远程教学同时开班, 第56期)!

最后于 1天前 被xiaoxinre编辑 ,原因:
收藏
免费 0
打赏
分享
最新回复 (7)
雪    币: 264
活跃值: (1005)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
帖子非常有用,感谢分享!
1天前
0
雪    币: 9172
活跃值: (7602)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢分享
1天前
0
雪    币: 3000
活跃值: (5836)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
666
1天前
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
学习一下
20小时前
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
强得可怕
16小时前
0
雪    币: 158
活跃值: (5206)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
学习
14小时前
0
雪    币: 230
活跃值: (1691)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
1
12小时前
0
游客
登录 | 注册 方可回帖
返回