亿企赢逆向手记(二):验证码不是墙——ddddocr 自动识别闭环
取图/校验被拆成两个接口、只靠一个可重试的 id 兜底,ddddocr 一行识别+重试就能闭环回登录。
「亿企赢逆向手记」第二篇。承接上篇登录第三步那个「弱密码才触发」的图形验证码,把取图、识别、校验、回注登录的完整闭环拆开。自己账号自动化,技术学习向。
上篇留了尾巴:doLogin 遇到弱密码账号会回 检测到您的密码较为常见或复杂度较低,要求先过一个 4 字符图形验证码。这篇讲它为什么好自动化——因为服务端把它设计得太解耦了。
三个接口,用一个 id 串起来
| 方法 | 端点 | 作用 |
|---|---|---|
| GET | ucapi/auth/authImageServlet |
按 id 取一张验证码图(PNG) |
| POST | ucapi/auth/web/checkVerCode |
提交识别结果 + 同一个 id |
| POST | ucapi/auth/v1/oauth2.0/doLogin |
带上已校验的 id 放行 |
关键:取图和校验是两个独立接口,中间靠一个前端自造的 id 绑定。checkVerCode 通过后,这个 id 就「盖了章」,doLogin 认它即放行。这种解耦对用户体验友好(能单独刷新验证码),但对自动化极其有利——我可以单独盯着 checkVerCode 反复重试到过为止。
取图:id 是前端自己造的
_timestamp = str(round(time.time() * 1000))
id_timestamp = _timestamp + str(random.randint(100, 999)) # ← 本次验证码的 id
params = {
'appId': '19060100010000',
'scene': 'login',
'h': '44', 'w': '120', # 服务端按此尺寸出图
't': _timestamp,
'id': id_timestamp, # 取图时就把 id 报给服务端
}
resp_img = self.session.get(
"https://www.17dz.com/ucapi/auth/authImageServlet",
params=params, cookies=self.final_cookies, headers=self.headers_common,
)
# resp_img.content 就是 120x44 的 PNG 字节流
注意 id 不是服务端下发的,是前端用「毫秒时间戳 + 三位随机数」拼的。服务端把「这张图的答案」和这个 id 绑定存起来,等 checkVerCode 拿同一个 id 来核对。所以取图和校验必须用同一个 id——这是整段能闭环的枢纽。
识别:ddddocr 一行
import ddddocr
ocr = ddddocr.DdddOcr(beta=True, show_ad=False)
vercode = ocr.classification(resp_img.content)
这类规整的 4 位数字/字母验证码,ddddocr 的通用模型识别率已经很高,不用自己训模型。beta=True 用它第二套模型,对这种字符往往更准。识别本身就这一行——真正要工程处理的是识别会错,所以要重试。
校验 + 重试:每轮换新图新 id
for attempt in range(1, max_retries + 1): # 默认 3 轮
# 1) 取图(每轮都重新取,id 也每轮新生成)
# 2) OCR 得到 vercode
data = {
'verCode': vercode,
'appId': '19060100010000',
'scene': 'login',
'id': id_timestamp, # ← 和取图同一个 id
'redirectUri': redirect_uri,
'_t': round(time.time() * 1000),
}
result = self.session.post(
"https://www.17dz.com/ucapi/auth/web/checkVerCode",
cookies=self.final_cookies, data=data, headers=self.headers_common,
).json()
if result.get("data") is True: # 校验通过
return {'id': id_timestamp, 'vercode': vercode, ...}
time.sleep(1.5) # 没过,下一轮换新图新 id
raise RuntimeError("验证码连续校验失败")
返回极简:{"data": true} 即通过。为什么每轮要重新取图——一旦 checkVerCode 失败,那个 id 通常就作废了,不能拿同一张图反复猜,必须换新 id 重来。识别错不可怕,max_retries 轮里只要一轮对上,就拿到一个「已校验」的 id。
回注登录
拿到盖了章的 id,回到上篇的登录第二轮,把它塞进 doLogin:
verification_id = cap_result['id']
# ... 第二轮 doLogin ...
if verification_id:
login_data['id'] = verification_id # 服务端认这个 id → 放行
于是完整的「带验证码登录」是一个两轮循环:第一轮 doLogin 撞到弱密码提示 → 去过验证码拿 id → 第二轮 doLogin 带 id 成功。
逆向视角的两点
① 验证码的触发条件本身就是情报。 这里是「弱密码账号」才触发,是一种风险补偿而非通用门槛。搞清楚触发条件(账号维度、IP 维度、还是行为维度),比无脑每次都过一遍更省事,也更不容易因为「不该出验证码时你却去取图」而露馅。
② 一个校验环节的强度,取决于它和上下文的耦合度。 取图/校验/登录三段解耦、只靠一个可重试的 id 兜底——这意味着识别率不用高,重试就能弥补;也意味着校验和登录之间没有更强的绑定(比如没有把图片答案的哈希和会话、时间强绑)。真正难绕的验证码,会把「识别」和「使用」紧紧焊死,让你没法把它单拎出来暴力重试。 这道恰恰相反。
下一篇:每个请求头都挂着的 X-Irc-Data——解开是一段 base64 的浏览器指纹,怎么伪造、怎么随机化、为什么它这么好绕。
评论 0