本文基于当前项目源码,系统拆解「微信 PC/Mac 端滑块验证自动通过」的完整实现思路:
从 UUID 生成协议还原、到 微信 110 安全校验流程仿真,再到核心的 腾讯滑块识别算法(图像模板匹配 + 多尺度 + 去噪 + 并行加速),最后给出一套较完整的 工程优化建议。
代码结构主要文件:
:程序入口,HTTP 服务启动
main.go:HTTP API 封装,对外暴露接口
internal/login/server.go:微信 UUID 生成算法(协议还原 + RSA + 自制 protobuf)
internal/login/uuid.go:完整滑块流程 + 核心图像识别算法
internal/login/slider.go
一、整体架构:从 HTTP 服务到滑块通过
1.1 服务入口与配置
的逻辑非常简洁:
main.go
从 或命令行参数加载 HTTP 监听端口启动登录模块的 HTTP Server
config.json
核心代码:
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
在 中,HTTP Server 主要暴露了 3 类接口:
internal/login/server.go
:一键完成整个微信滑块验证流程
/api/solve:仅根据 deviceId 生成微信 UUID
/api/generate-uuid:仅调用腾讯滑块识别(给 appid / aid)
/api/slider
路由注册:
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 校验(仅允许 )入参校验:防止空字符串或 Swagger 默认示例
POST 被误传代理配置支持:允许通过 SOCKS5 代理访问外网(
"string")
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: 为 16 进制字符串,转成
deviceId可选 authKey 解码:支持 Base64 或 Hex,自适应解析构造内部 PB 消息(ULDDInfo)使用微信的 RSA 公钥对内部 PB 进行加密构造外层 PB 消息(ULSRequest)整体 Base64 + URL 编码,输出最终 UUID
[]byte
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 加密内部 PB。
rsa.EncryptPKCS1v15
这里的 modulus 与 exponent 必须与微信服务器一致,否则服务器无法解密。
2.3.2 softtype XML:构造逼真的设备指纹
用随机生成的 UUID 和 MAC 地址,构造一份看起来逼真的软硬件信息:
CreateSoftTypeXML
:系统版本(如 macOS 15)
<k3>:设备名称(如 MacBook Pro)
<k9>:CPU 核心数(如 24)
<k10>:随机 UUID
<k19>:随机 MAC 地址
<k24>:应用名(微信)
<k33>:Bundle ID(
<k51>)
com.tencent.xin:微信版本(4.1.0)
<k54>
意义:
提升 UUID 的“真实性”,模拟真实设备可通过调整字段,实现 设备指纹随机化,降低批量封禁风险
三、微信 110 安全流程仿真:从 ticket 到 verify_capt
完整流程在 中实现,它仿真了 PC/Mac 微信在进行设备登录安全校验时的 HTTP 调用链。
solveSliderFlowWithClient
3.1 输入与整体流程
输入:
ticketUrl:来自微信页面(包含 参数)uuid:通过前文算法生成client:可带代理的
ticket
*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:获取滑块验证码相关参数(包括 appid)调用腾讯滑块服务:计算出滑块坐标并获取
get_capt_info、
rand_str
ticket:将滑块结果回填给微信 110
verify_capt
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,body 中对应字段名称动态填充。
next_step
返回中关键字段为:
:后续验证码接口统一基于此 ID
data.verify_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 三种 JSON 类型,避免浮点导致的科学计数法问题。
float64/int/string
3.5 调用腾讯滑块:tencentSliderWithClient
拿到 后,调用:
appid
randStr, hkTicket, err := tencentSliderWithClient(cookieClient, ua, appid)
内部完成:
tencentSliderWithClient
获取验证码图片及参数(大图、小图、sess、prefix、y 坐标)图像模板匹配,找到缺口的 x 坐标调验证接口,获取 和
rand_str在必要时自动重试(例如 errorCode=50:识别不准确)
ticket
这是核心算法部分,详见下一节。
3.6 回填结果:verify_capt
最后一步,将 、
rand_str、
ticket 等信息回填给微信 110:
app_id
{
"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
向 发送 GET 请求,主要参数:
https://t.captcha.qq.com/cap_union_prehandle
:应用 ID(来自
aid)
appid:先 Base64 再 URL 编码的 UA 字符串其他诸如
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 格式 ,需要先截取出 JSON 再解析。
_aq_978959({...})
解析后依次读取:
:状态正常
state == 1、
sess:会话标识
sid:必须等于
data.dyn_show_info.instruction
"拖动下方滑块完成拼图":初始 y 坐标
dyn_show_info.fg_elem_list[1].init_pos[1]
inity:
data.comm_captcha_cfg.pow_cfg.prefix 用的前缀
pow_answer:大图相对 URL
dyn_show_info.bg_elem_cfg.img_url:小图相对 URL
dyn_show_info.sprite_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}
对每个 启动一个 goroutine:
scale
按比例缩放裁剪坐标与尺寸,再从小图中裁出模板用 将大图整体缩放大图、小图都转为灰度图,再进行中值滤波去噪调
imaging.Resize 做模板匹配,计算相关系数将在缩放图上的
matchTemplate 坐标折返成原始坐标:
xScaled
originalX = int(float64(xScaled) / s)
所有结果用 channel 收集,最后选取 相关系数最高 的结果:
:最佳尺度
bestScale:对应的相关系数
bestCorr:对应的原图 x 坐标
bestX
并在结尾进行边界裁剪,保证 落在
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}):协方差
实现细节:
先计算模板的均值与标准差使用 决定 worker 数量,按行切分任务进行并行计算每个 worker 扫描若干行,计算对应窗口的相关系数,写入共享数组(通过
runtime.NumCPU() 互斥锁保护)扫描完后在结果矩阵中找出 最大相关系数 对应的
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:根据
errorCode == "50" 返回详细错误信息
errMessage
五、工程与算法优化建议
下面从 稳定性、性能、可维护性、安全性 四个维度,给出一些实际可落地的优化建议。
5.1 HTTP 与错误处理层面
细粒度超时控制:
目前以 为主,可以进一步拆分:
http.Client.Timeout
超时(连接建立)
DialContext
TLSHandshakeTimeout 等
ResponseHeaderTimeout
可以通过自定义 实现精细控制。
Transport
智能重试策略:
对不同错误类型采用不同策略:
DNS/TLS/临时网络错误:可以快速重试HTTP 4xx:一般不重试,直接返回业务错误(errorCode=50):保留当前多次重试逻辑
日志分级与开关:
大量 适合 debug,但生产环境建议:
fmt.Printf
使用结构化日志库(如 /
zap)支持
logrus 级别提供开关关闭详细 debug 输出
DEBUG/INFO/WARN/ERROR
5.2 图像识别算法优化
模板区域自动搜索:
当前模板裁剪坐标 是经验值,对特定版本素材有效。
(140, 490, 120x120)
可以考虑:
在小图中通过 透明度 + 边缘检测 自动找出滑块块区域或做一次类似 的有效区域检测,自动确定模板位置
findValidBounds
这样在腾讯调整 UI 时可减少人工维护。
多尺度策略调参:
当前使用 三个尺度:
{1.0, 0.8, 1.2}
若实测缩放差异不大,可只保留 ,提升性能若素材在不同机型/缩放下变化大,可增加
1.0 等细粒度尺度建议将 scale 列表抽到配置中,方便在线调参
{0.9, 1.1}
并发度控制:
每个 scale 开 1 个 goroutine, 内部又按 CPU 核心数开多 worker:
matchTemplate
并发度约为 ,在高并发请求下可能压满 CPU建议:
len(scales) * NumCPU
引入 /worker pool,对“并行匹配任务”做全局限流通过配置项提供“快速模式/精细模式”切换(可调 scales 与 kernelSize)
semaphore
5.3 代码结构与可维护性
按职责拆分文件/包:
:只负责微信 110 + 腾讯滑块 HTTP 流程
captcha/httpflow:专门负责图像预处理与模板匹配
captcha/imageproc:单独放 UUID 相关逻辑
captcha/uuid
便于做单元测试与独立演进,例如更换为深度学习识别等。
单元测试建议:
为 、
matchTemplate、
denoiseImage 等纯算法函数准备固定的输入图与期望输出为
cropImage 做“协议兼容性测试”:如果有官方客户端抓包结果,可对比生成值是否一致
BuildWeChatUUID
5.4 安全与合规性注意
代理凭据保护:
/
ProxyUsername 绝不应直接输出到日志可以仅记录「是否配置了认证」或记录散列值,避免泄露
ProxyPassword
服务侧频控与配额:
建议在 HTTP 层实现简单的 QPS/IP 限制外加任务配额控制,防止被恶意刷请求导致 IP 封禁
指纹随机化策略:
在 softtype XML、UA 字符串中加入多套模板,根据任务/租户随机切换统一规划“设备池”,而不是每次完全随机,方便排查问题与控制风险
六、教学总结与实践路线
6.1 学习路径建议
基于本项目,推荐给工程师的学习路径:
HTTP Client 与代理:理解 、自定义
http.Client、SOCKS5 代理配置协议还原与编码:手写 protobuf、RSA 加密、Base64 + URL 编码这一整条数据链路图像基础与模板匹配:
Transport
图像格式(RGBA/Gray)中值滤波去噪归一化互相关(NCC)模板匹配
并发模型:goroutine + channel + waitgroup 的组合,以及如何控制并发度与资源占用
实际操作上,可以:
为每个子模块写一个最小 demo:
只做 UUID 生成输出的 CLI 工具只做模板匹配的小程序,将两张本地图输入,输出匹配坐标与得分
最终把这些模块组合成一套完整的“自动过滑块服务”
6.2 项目未来的升级方向
服务层面:
增加任务队列 + worker 池,支撑更高并发集成 Prometheus 指标(成功率、平均耗时、errorCode 分布)加入调用频控与多租户隔离
算法层面:
尝试使用更高级的图像特征(边缘检测、HOG 等)提升鲁棒性在有数据积累的前提下,引入轻量级深度学习模型做缺口定位
通过这份源码,我们看到一个完整的「逆向协议 + 工程实现 + 图像算法 + 并发优化」的实战案例。
无论是想深入 图像算法/机器学习,还是想打磨 高并发分布式服务,这个项目都可以作为一个非常好的起点与教材。





