ipad协议最新uuid算法逆向分析学习

内容分享1天前发布
1 0 0

本文基于当前项目源码,系统拆解「微信 PC/Mac 端滑块验证自动通过」的完整实现思路:
UUID 生成协议还原、到 微信 110 安全校验流程仿真,再到核心的 腾讯滑块识别算法(图像模板匹配 + 多尺度 + 去噪 + 并行加速),最后给出一套较完整的 工程优化建议

代码结构主要文件:


main.go
:程序入口,HTTP 服务启动
internal/login/server.go
:HTTP API 封装,对外暴露接口
internal/login/uuid.go
:微信 UUID 生成算法(协议还原 + RSA + 自制 protobuf)
internal/login/slider.go
:完整滑块流程 + 核心图像识别算法


一、整体架构:从 HTTP 服务到滑块通过

1.1 服务入口与配置


main.go
的逻辑非常简洁:


config.json
或命令行参数加载 HTTP 监听端口启动登录模块的 HTTP Server

核心代码:


cfg := loadConfig("config.json")
addr := cfg.HTTP
if *flagHTTP != "" {
    addr = *flagHTTP
}
if err := login.StartHTTPServer(addr); err != nil {
    log.Fatal(err)
}

设计要点:

配置优先级:命令行
-http
覆盖配置文件,便于运维动态调整端口模块化:具体业务逻辑全部下沉到
internal/login
模块,入口文件保持极简

1.2 对外暴露的 3 类 HTTP API


internal/login/server.go
中,HTTP Server 主要暴露了 3 类接口:


/api/solve
一键完成整个微信滑块验证流程
/api/generate-uuid
仅根据 deviceId 生成微信 UUID
/api/slider
仅调用腾讯滑块识别(给 appid / aid)

路由注册:


func StartHTTPServer(addr string) error {
    http.HandleFunc("/", handleRoot)
    http.HandleFunc("/api/solve", handleSolve)
    http.HandleFunc("/api/generate-uuid", handleGenerateUUID)
    http.HandleFunc("/api/slider", handleSlider)
    http.HandleFunc("/swagger.json", handleSwagger)
    http.HandleFunc("/docs", handleDocs)
    log.Printf("HTTP listening on %s", addr)
    return http.ListenAndServe(addr, nil)
}

每个接口都做了:

严格的 Method 校验(仅允许
POST
入参校验:防止空字符串或 Swagger 默认示例
"string"
被误传代理配置支持:允许通过 SOCKS5 代理访问外网(
ProxyIP/ProxyUsername/ProxyPassword

这部分主要是工程包装层,真正有技术含量的核心算法在
uuid.go

slider.go
中。


二、UUID 协议还原:伪装成真实微信客户端

2.1 目标:构造与微信官方一致的 UUID

微信 PC/Mac 客户端在安全验证时,会携带一个「复杂编码过的 UUID」,本项目通过逆向还原其构造逻辑,在
BuildWeChatUUID
中完整复现:


func BuildWeChatUUID(
    deviceIDHex, deviceInfo, authKeyText string,
    uin, cliVer uint32,
    deviceType string,
) (string, error)

整体流程:

解析设备 ID
deviceId
为 16 进制字符串,转成
[]byte
可选 authKey 解码:支持 Base64 或 Hex,自适应解析构造内部 PB 消息(ULDDInfo)使用微信的 RSA 公钥对内部 PB 进行加密构造外层 PB 消息(ULSRequest)整体 Base64 + URL 编码,输出最终 UUID

2.2 手写 protobuf:内部与外部 PB 结构

项目没有用官方 protobuf 库,而是直接用位运算拼字段,模拟 proto 的编码规则:


const (
    wireVarint = 0
    wireBytes  = 2
)

func appendKey(dst []byte, fieldNum int, wireType int) []byte {
    key := uint64(uint64(fieldNum)<<3 | uint64(wireType))
    return appendVarint(dst, key)
}
2.2.1 内部 PB:ULDDInfo(设备 + 授权信息)

func encodeULDDInfo(did []byte, ddstr string, ak []byte, uin uint32) []byte {
    var out []byte
    if len(did) > 0 {
        out = appendBytesField(out, 1, did)     // DeviceID, 设备ID
    }
    if ddstr != "" {
        out = appendStringField(out, 2, ddstr)  // SoftType, 设备类型(XML)
    }
    if len(ak) > 0 {
        out = appendBytesField(out, 3, ak)      // authkey, 登录后可获取
    }
    if uin != 0 {
        out = appendUint32Field(out, 4, uin)    // uin
    }
    return out
}

字段含义:

1:设备 ID(二进制)2:SoftType(XML 字符串,包含系统、MAC、UUID 等)3:authkey(可空)4:uin(一般为 0)

2.2.2 外层 PB:ULSRequest(请求封装)

func encodeULSRequest(cliVer, certVer uint32, deviceType string, encryptBuff []byte) []byte {
    var out []byte
    if cliVer != 0 {
        out = appendUint32Field(out, 1, cliVer) // 客户端版本
    }
    if certVer != 0 {
        out = appendUint32Field(out, 2, certVer) // 证书版本
    }
    if deviceType != "" {
        out = appendStringField(out, 3, deviceType) // 设备类型,如 "UnifiedPCMac 11 x86_64"
    }
    if len(encryptBuff) > 0 {
        out = appendBytesField(out, 4, encryptBuff) // RSA 加密后的内部 PB
    }
    return out
}

外层 PB 的作用是告诉服务器:

我是哪个版本的客户端(
cliVer
)使用哪一版公钥证书(
certVer
)当前设备类型(
deviceType
)以及真实的设备信息密文(
encryptBuff

2.3 RSA 公钥与 softtype 伪装

2.3.1 微信 RSA 公钥

const (
    wechatRSAModulusHex = "DE40F877600A44..." // 省略
    wechatRSAExponent   = 65537
)

通过
math/big
构造出
rsa.PublicKey
,再用标准库
rsa.EncryptPKCS1v15
加密内部 PB。
这里的 modulusexponent 必须与微信服务器一致,否则服务器无法解密。

2.3.2 softtype XML:构造逼真的设备指纹


CreateSoftTypeXML
用随机生成的 UUID 和 MAC 地址,构造一份看起来逼真的软硬件信息:


<k3>
:系统版本(如 macOS 15)
<k9>
:设备名称(如 MacBook Pro)
<k10>
:CPU 核心数(如 24)
<k19>
:随机 UUID
<k24>
:随机 MAC 地址
<k33>
:应用名(微信)
<k51>
:Bundle ID(
com.tencent.xin

<k54>
:微信版本(4.1.0)

意义:

提升 UUID 的“真实性”,模拟真实设备可通过调整字段,实现 设备指纹随机化,降低批量封禁风险


三、微信 110 安全流程仿真:从 ticket 到 verify_capt

完整流程在
solveSliderFlowWithClient
中实现,它仿真了 PC/Mac 微信在进行设备登录安全校验时的 HTTP 调用链。

3.1 输入与整体流程

输入:

ticketUrl:来自微信页面(包含
ticket
参数)uuid:通过前文算法生成client:可带代理的
*http.Client

先从 URL 中解析出
ticket


u, err := url.Parse(ticketURL)
q := u.Query()
ticket := q.Get("ticket")
if ticket == "" {
    return errors.New("ticketUrl 中缺少 ticket 参数")
}

之后按微信协议依次执行:


precheck
:预检查是否允许继续验证
get_verify_method
:获取验证方式和
verify_id

get_capt_info
:获取滑块验证码相关参数(包括 appid)调用腾讯滑块服务:计算出滑块坐标并获取
rand_str

ticket

verify_capt
:将滑块结果回填给微信 110

3.2 step=precheck

请求 URL(简化展示):


https://weixin110.qq.com/security/acct/extdevauthslavecgi
    ?t=extdevsignin/slaveverify
    &ticket=...
    &step=precheck

请求体:


{
  "uuid": "生成的uuid",
  "precheck": {
    "ticket": "ticket值"
  }
}

通过
postJSONResp
发送,并检查:


ret == 0
:表示成功
next_step
:一般为
get_verify_method

3.3 获取 verify_id:get_verify_method

类似的 POST 请求,只是
step
改为服务端返回的
next_step
,body 中对应字段名称动态填充。
返回中关键字段为:


data.verify_id
:后续验证码接口统一基于此 ID

若没有
verify_id
,流程立即中止。

3.4 获取滑块信息:get_capt_info

请求 URL(简化):


https://weixin110.qq.com/security/acct/commverifycodecgi
    ?secverifyid=verify_id
    &step=get_capt_info

请求体:


{"uuid": "uuid", "ccdata": ""}

关键返回字段:


appid
:后续腾讯滑块识别用到的
aid

项目对
appid
做了健壮处理,兼容
float64/int/string
三种 JSON 类型,避免浮点导致的科学计数法问题。

3.5 调用腾讯滑块:tencentSliderWithClient

拿到
appid
后,调用:


randStr, hkTicket, err := tencentSliderWithClient(cookieClient, ua, appid)


tencentSliderWithClient
内部完成:

获取验证码图片及参数(大图、小图、sess、prefix、y 坐标)图像模板匹配,找到缺口的 x 坐标调验证接口,获取
rand_str

ticket
在必要时自动重试(例如 errorCode=50:识别不准确)

这是核心算法部分,详见下一节。

3.6 回填结果:verify_capt

最后一步,将
rand_str

ticket

app_id
等信息回填给微信 110:


{
  "uuid": "uuid",
  "rand_str": "滑块返回的randStr",
  "app_id": "appid",
  "ticket": "滑块返回的ticket",
  "ccdata": ""
}

返回:


ret == 0
:滑块验证成功,整个安全流程结束其他:根据
err_msg
打印错误信息


四、腾讯滑块识别算法:图像模板匹配 + 并行多尺度

这一部分是项目的算法核心,主要集中在
slider.go
中:


tencentSliderWithClient
:滑块识别总控逻辑
getCaptchaImageURLs
:获取大图、小图与相关参数
findGapPositionSimple
:缺口位置查找算法
matchTemplate
:归一化互相关模板匹配(多线程)
postCaptcha
:提交坐标到腾讯验证接口

4.1 总控逻辑:最多 16 次重试

简化后的核心循环:


for attempt := 1; attempt <= 16; attempt++ {
    // 获取验证码参数(大图/小图 URL、sess、prefix、初始 y 等)
    bgURL, spriteURL, sess, _, prefix, inity, err := getCaptchaImageURLs(directClient, ua, appid)
    ...

    // 图像识别:找缺口 x 坐标
    xPosition, err := findGapPositionSimple(bgURL, spriteURL, directClient)
    ...

    // y 坐标来自 JSON 的 init_pos
    yPosition := inity

    // 提交验证码验证请求
    randStr, ticket, err = postCaptcha(directClient, sess, prefix, xPosition, yPosition)

    if err != nil {
        // 若为「识别不准确」错误(errorCode=50),继续重试
        // 其他错误直接返回
    }
}

容错策略:

网络/解析/图像错误:只要未到第 16 次,继续尝试业务 errorCode=50:说明坐标接近但不精确,继续重试,提高成功率16 次仍失败则返回整体失败

4.2 获取验证码参数:getCaptchaImageURLs


https://t.captcha.qq.com/cap_union_prehandle
发送 GET 请求,主要参数:


aid
:应用 ID(来自
appid

ua
先 Base64 再 URL 编码的 UA 字符串其他诸如
captype=7

clientype=1

举例:


uaBase64 := base64.StdEncoding.EncodeToString([]byte(ua))
uaEncoded := url.QueryEscape(uaBase64)

url := fmt.Sprintf(
  "https://t.captcha.qq.com/cap_union_prehandle?aid=%s&...&ua=%s&...",
  url.QueryEscape(appid),
  uaEncoded,
)

返回为 JSONP 格式
_aq_978959({...})
,需要先截取出 JSON 再解析。
解析后依次读取:


state == 1
:状态正常
sess

sid
:会话标识
data.dyn_show_info.instruction
:必须等于
"拖动下方滑块完成拼图"

dyn_show_info.fg_elem_list[1].init_pos[1]
:初始 y 坐标
inity

data.comm_captcha_cfg.pow_cfg.prefix

pow_answer
用的前缀
dyn_show_info.bg_elem_cfg.img_url
:大图相对 URL
dyn_show_info.sprite_url
:小图相对 URL

最终拼接大图/小图完整 URL:


bgImgURL = "https://t.captcha.qq.com" + bgImgURL
spriteURL = "https://t.captcha.qq.com" + getString(dynShowInfo, "sprite_url")

4.3 缺口位置查找:findGapPositionSimple

4.3.1 图像下载与裁剪

下载大图、小图:


bigImg, err := loadImageFromURL(bgImgURL, client)
smallImg, err := loadImageFromURL(spriteURL, client)

转为
*image.RGBA
,方便后续像素操作:


bigImgRGBA := imageTo24Bit(bigImg)
smallImgRGBA := imageTo24Bit(smallImg)

在小图中 从固定坐标裁剪模板区域(作者经验值):
起点
(140, 490)
尺寸
120 x 120


cropX, cropY, cropSize := 140, 490, 120
croppedSmallImg, err := cropImage(smallImgRGBA, cropX, cropY, cropSize, cropSize)

对尺寸进行一系列检查,防止越界或模板比大图更大。

这一块属于根据现有滑块图素材“总结出的经验参数”,未来若腾讯改了素材布局,这个区域可能需要重新标定。

4.3.2 多尺度匹配 + 并行计算

为了适配可能的缩放变化,使用了多尺度策略:


scales := []float64{1.0, 0.8, 1.2}

对每个
scale
启动一个 goroutine:

按比例缩放裁剪坐标与尺寸,再从小图中裁出模板用
imaging.Resize
将大图整体缩放大图、小图都转为灰度图,再进行中值滤波去噪调
matchTemplate
做模板匹配,计算相关系数将在缩放图上的
xScaled
坐标折返成原始坐标:
originalX = int(float64(xScaled) / s)

所有结果用 channel 收集,最后选取 相关系数最高 的结果:


bestScale
:最佳尺度
bestCorr
:对应的相关系数
bestX
:对应的原图 x 坐标

并在结尾进行边界裁剪,保证
bestX
落在
[0, bigWidth]
范围内。

4.3.3 匹配实现:归一化互相关(NCC)

核心思想:在大图上滑动模板窗口,对每个位置计算:

[
ext{corr} = frac{mathrm{Cov}(T, W)}{sigma_T cdot sigma_W}
]

其中:

(T):模板像素灰度(W):当前窗口区域的灰度(sigma_T, sigma_W):标准差(mathrm{Cov}):协方差

实现细节:

先计算模板的均值与标准差使用
runtime.NumCPU()
决定 worker 数量,按行切分任务进行并行计算每个 worker 扫描若干行,计算对应窗口的相关系数,写入共享数组(通过
mu
互斥锁保护)扫描完后在结果矩阵中找出 最大相关系数 对应的
(maxX, maxY)

maxCorr < 0.3
(阈值),认为匹配不可靠,返回错误

4.4 提交验证:postCaptcha

一旦得到坐标
(x, y)
,通过表单请求提交给腾讯:


data := url.Values{}
data.Set("sess", sess)
data.Set("pow_answer", powAnswer)
data.Set("ans", fmt.Sprintf(
  `[{"elem_id":1,"type":"DynAnswerType_POS","data":"%d,%d"}]`,
  x, y,
))

resp, err := client.PostForm("https://t.captcha.qq.com/cap_union_new_verify", data)

返回 JSON 中关键字段:


errorCode == "0"
:成功,取出
randstr

ticket

errorCode == "50"
:识别不准确,交由上层逻辑重试其他 errorCode:根据
errMessage
返回详细错误信息


五、工程与算法优化建议

下面从 稳定性、性能、可维护性、安全性 四个维度,给出一些实际可落地的优化建议。

5.1 HTTP 与错误处理层面

细粒度超时控制
目前以
http.Client.Timeout
为主,可以进一步拆分:


DialContext
超时(连接建立)
TLSHandshakeTimeout

ResponseHeaderTimeout

可以通过自定义
Transport
实现精细控制。

智能重试策略
对不同错误类型采用不同策略:

DNS/TLS/临时网络错误:可以快速重试HTTP 4xx:一般不重试,直接返回业务错误(errorCode=50):保留当前多次重试逻辑

日志分级与开关
大量
fmt.Printf
适合 debug,但生产环境建议:

使用结构化日志库(如
zap
/
logrus
)支持
DEBUG/INFO/WARN/ERROR
级别提供开关关闭详细 debug 输出

5.2 图像识别算法优化

模板区域自动搜索
当前模板裁剪坐标
(140, 490, 120x120)
是经验值,对特定版本素材有效。
可以考虑:

在小图中通过 透明度 + 边缘检测 自动找出滑块块区域或做一次类似
findValidBounds
的有效区域检测,自动确定模板位置
这样在腾讯调整 UI 时可减少人工维护。

多尺度策略调参
当前使用
{1.0, 0.8, 1.2}
三个尺度:

若实测缩放差异不大,可只保留
1.0
,提升性能若素材在不同机型/缩放下变化大,可增加
{0.9, 1.1}
等细粒度尺度建议将 scale 列表抽到配置中,方便在线调参

并发度控制
每个 scale 开 1 个 goroutine,
matchTemplate
内部又按 CPU 核心数开多 worker:

并发度约为
len(scales) * NumCPU
,在高并发请求下可能压满 CPU建议:
引入
semaphore
/worker pool,对“并行匹配任务”做全局限流通过配置项提供“快速模式/精细模式”切换(可调 scales 与 kernelSize)

5.3 代码结构与可维护性

按职责拆分文件/包


captcha/httpflow
:只负责微信 110 + 腾讯滑块 HTTP 流程
captcha/imageproc
:专门负责图像预处理与模板匹配
captcha/uuid
:单独放 UUID 相关逻辑
便于做单元测试与独立演进,例如更换为深度学习识别等。

单元测试建议


matchTemplate

denoiseImage

cropImage
等纯算法函数准备固定的输入图与期望输出为
BuildWeChatUUID
做“协议兼容性测试”:如果有官方客户端抓包结果,可对比生成值是否一致

5.4 安全与合规性注意

代理凭据保护


ProxyUsername
/
ProxyPassword
绝不应直接输出到日志可以仅记录「是否配置了认证」或记录散列值,避免泄露

服务侧频控与配额

建议在 HTTP 层实现简单的 QPS/IP 限制外加任务配额控制,防止被恶意刷请求导致 IP 封禁

指纹随机化策略

在 softtype XML、UA 字符串中加入多套模板,根据任务/租户随机切换统一规划“设备池”,而不是每次完全随机,方便排查问题与控制风险


六、教学总结与实践路线

6.1 学习路径建议

基于本项目,推荐给工程师的学习路径:

HTTP Client 与代理:理解
http.Client
、自定义
Transport
、SOCKS5 代理配置协议还原与编码:手写 protobuf、RSA 加密、Base64 + URL 编码这一整条数据链路图像基础与模板匹配
图像格式(RGBA/Gray)中值滤波去噪归一化互相关(NCC)模板匹配
并发模型:goroutine + channel + waitgroup 的组合,以及如何控制并发度与资源占用

实际操作上,可以:

为每个子模块写一个最小 demo:
只做 UUID 生成输出的 CLI 工具只做模板匹配的小程序,将两张本地图输入,输出匹配坐标与得分
最终把这些模块组合成一套完整的“自动过滑块服务”

6.2 项目未来的升级方向

服务层面

增加任务队列 + worker 池,支撑更高并发集成 Prometheus 指标(成功率、平均耗时、errorCode 分布)加入调用频控与多租户隔离

算法层面

尝试使用更高级的图像特征(边缘检测、HOG 等)提升鲁棒性在有数据积累的前提下,引入轻量级深度学习模型做缺口定位


通过这份源码,我们看到一个完整的「逆向协议 + 工程实现 + 图像算法 + 并发优化」的实战案例。
无论是想深入 图像算法/机器学习,还是想打磨 高并发分布式服务,这个项目都可以作为一个非常好的起点与教材。

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...