[Asm] 纯文本查看 复制代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
纯 HTTP完成 媒介盒子 登录 + 腾讯滑块 TCaptcha v2
"""
import base64
import hashlib
import http.cookiejar
import io
import json
import random
import re
import time
import urllib.parse
import urllib.request
import numpy as np
from PIL import Image
SITE = "https://vip.meijiehezi.com"#媒介盒子的主站点域名
API = "https://turing.captcha.qcloud.com"#腾讯云验证码(TCaptcha)服务域名
AID = "190824934"#媒介盒子在腾讯验证码后台申请的项目 ID
LOGIN_PAGE = SITE + "/index/login/login.html"
LOGIN_API = SITE + "/index/login/login_by_username_p.html"
UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36")
#简易版 requests
class Client:
def __init__(self):
self.cj = http.cookiejar.CookieJar()
self.opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(self.cj))
def request(self, url, data=None, headers=None, method=None):
h = {"User-Agent": UA, "Accept-Language": "zh-CN,zh;q=0.9"}
if headers:
h.update(headers)
body = None
if data is not None:
body = urllib.parse.urlencode(data).encode()
h.setdefault("Content-Type", "application/x-www-form-urlencoded")
req = urllib.request.Request(url, data=body, headers=h, method=method)
with self.opener.open(req, timeout=25) as r:
return r.read()
def get(self, url, headers=None):
return self.request(url, None, headers)
def post(self, url, data, headers=None):
return self.request(url, data, headers)
#图像算法,自动找出滑块验证码里缺口的横向坐标 X
def solve_gap(sprite_img, bg_img, sprite_pos, tile_size, init_pos):
sx, sy = sprite_pos
tw, th = tile_size
px, py = init_pos
sp = np.asarray(sprite_img.convert("RGBA"))
alpha = sp[sy:sy + th, sx:sx + tw, 3]
mask = alpha > 128
# 轮廓点(4 邻域里有背景像素的掩膜像素)
inside = mask
nb = np.zeros_like(mask)
nb[1:, :] |= ~mask[:-1, :]
nb[:-1, :] |= ~mask[1:, :]
nb[:, 1:] |= ~mask[:, :-1]
nb[:, :-1] |= ~mask[:, 1:]
outline = np.argwhere(inside & nb) # [(v,u), ...]
vs, us = outline[:, 0], outline[:, 1]
gray = np.asarray(bg_img.convert("L")).astype(np.float64)
H, W = gray.shape
gx = np.zeros_like(gray)
gy = np.zeros_like(gray)
gx[:, 1:-1] = (gray[:, 2:] - gray[:, :-2]) / 2.0
gy[1:-1, :] = (gray[2:, :] - gray[:-2, :]) / 2.0
edge = np.hypot(gx, gy)
ys = py + vs
ok = (ys >= 0) & (ys < H)
ys, us = ys[ok], us[ok]
max_x = W - tw
scores = np.zeros(max_x + 1)
for x0 in range(max_x + 1):
xs = x0 + us
sel = (xs >= 0) & (xs < W)
scores[x0] = edge[ys[sel], xs[sel]].sum() / max(1, len(us))
best = int(np.argmax(scores))
# 交叉验证:掩膜内平均亮度最低
dark = np.full(max_x + 1, 1e9)
inner = np.zeros_like(mask)
inner[4:-4, 4:-4] = mask[4:-4, 4:-4]
iv, iu = np.nonzero(inner)
iy = py + iv
ok2 = (iy >= 0) & (iy < H)
iy, iu = iy[ok2], iu[ok2]
for x0 in range(max_x + 1):
xs = x0 + iu
sel = (xs >= 0) & (xs < W)
if sel.sum():
dark[x0] = gray[iy[sel], xs[sel]].mean()
dark_best = int(np.argmin(dark))
top = sorted(range(max_x + 1), key=lambda i: -scores[i])[:5]
return best, dark_best, [(int(i), round(float(scores[i]), 2)) for i in top], len(us)
#处理PoW,暴力破解 MD5
def solve_pow(prefix, target, limit=50_000_000):
t0 = time.time()
for n in range(limit):
if hashlib.md5((prefix + str(n)).encode()).hexdigest() == target:
return prefix + str(n), int((time.time() - t0) * 1000)
return None, None
# 主流程
def main():
user = "aaaaaaaaa"
pwd = "bbbbbbbb"
c = Client()
# ---- 1. 登录页拿 __token__ ----
html = c.get(LOGIN_PAGE).decode("utf-8", "replace")
m = re.search(r'id="__token__"\s+value="([0-9a-f]{32})"', html)
token = m.group(1) if m else ""
print("[1] __token__ =", token, " cookies =", {k.name: k.value for k in c.cj})
# -访问腾讯验证码接口获取验证码会话 ID,滑块位置、尺寸信息,PoW 算力题参数,背景图、滑块图片 URL
cb = "_aq_%d" % random.randint(10 ** 5, 10 ** 6)
tkid = str(random.randint(10 ** 8, 2 * 10 ** 9))
q = {
"aid": AID, "protocol": "https", "accver": "1", "showtype": "popup",
"ua": base64.b64encode(UA.encode()).decode(),
"noheader": "1", "fb": "1", "aged": "0", "enableAged": "0", "enableDarkMode": "0",
"grayscale": "1", "clientype": "2", "cap_cd": "", "uid": "", "lang": "zh-cn",
"entry_url": LOGIN_PAGE, "elder_captcha": "0",
"js": "/tcaptcha-frame.ba387dd1.js", "login_appid": "", "wb": "1",
"tkid": tkid, "subsid": "1", "callback": cb, "sess": "",
}
raw = c.get(API + "/cap_union_prehandle?" + urllib.parse.urlencode(q),
{"Referer": SITE + "/"}).decode("utf-8", "replace")
j = json.loads(re.search(r"^\s*[^(]*\((.*)\)\s*;?\s*$", raw, re.S).group(1))
if not j.get("data"):
print("[2] prehandle 失败:", raw[:300]); return
info = j["data"]["dyn_show_info"]
sess = j["sess"]
print("[2] sess =", sess[:20] + "…", " subcapclass =", j.get("subcapclass"),
" sid =", j.get("sid"))
piece = next(e for e in info["fg_elem_list"]
if e.get("move_cfg", {}).get("data_type", []) and not e.get("type"))
pow_cfg = j["data"]["comm_captcha_cfg"]["pow_cfg"]
print(" 拼图块 sprite_pos=%s size_2d=%s init_pos=%s" %
(piece["sprite_pos"], piece["size_2d"], piece["init_pos"]))
# ---- 3. 取图 ----
def grab(u):
data = c.get(API + u, {"Referer": "https://turing.captcha.gtimg.com/"})
return Image.open(io.BytesIO(data))
bg_url = info["bg_elem_cfg"]["img_url"] + "&tkid=" + tkid
sp_url = info["sprite_url"] + "&tkid=" + tkid
bg_img = grab(bg_url)
sp_img = grab(sp_url)
print("[3] 背景图 %s %s 精灵图 %s %s" % (bg_img.size, bg_img.format, sp_img.size, sp_img.format))
# ---- 缺口计算
rate = bg_img.size[0] / 672.0
best, dark_best, top, n_out = solve_gap(sp_img, bg_img,
piece["sprite_pos"], piece["size_2d"], piece["init_pos"])
print("[4] 缺口 x = %d (压暗法 %d, 轮廓点 %d) top: %s" % (best, dark_best, n_out, top))
# ---- 算PoW
pow_answer, pow_time = solve_pow(pow_cfg["prefix"], pow_cfg["md5"])
print("[5] PoW nonce 命中: pow_answer = %s 耗时 %sms" % (pow_answer, pow_time))
# ---- 滑块坐标答案、PoW 结果打包提交,获取到返回的ticket
answer = [{"elem_id": piece["id"], "type": "DynAnswerType_POS",
"data": "%d,%d" % (best, piece["init_pos"][1])}]
#浏览器 JS 采集的设备、鼠标、环境指纹,先置空
collect = ""
eks = ""
form = {
"collect": collect, "tlg": str(len(collect)), "eks": eks,
"sess": sess, "ans": json.dumps(answer, separators=(",", ":")),
"tkid": tkid, "pow_answer": pow_answer, "pow_calc_time": str(pow_time),
}
resp = c.post(API + "/cap_union_new_verify", form, {
"Referer": "https://turing.captcha.gtimg.com/1/template/drag_ele.html",
"Origin": "https://turing.captcha.gtimg.com",
}).decode("utf-8", "replace")
print("[6] verify ->", resp[:300])
try:
vr = json.loads(resp)
except Exception:
print(" 响应不可解析"); return
print(" errorCode =", vr.get("errorCode"))
# ---- 7. 登录 ----
lr = c.post(LOGIN_API, {
"username": user, "password": pwd,
"ticket": vr["ticket"], "randstr": vr["randstr"], "__token__": token,
}, {"Referer": LOGIN_PAGE, "Origin": SITE}).decode("utf-8", "replace")
print("[7] login ->", lr[:300])
try:
lj = json.loads(lr)
print("\n结论:纯 HTTP 登录 %s code=%s msg=%s" %
("成功" if lj.get("code") == 200 else "失败", lj.get("code"), lj.get("msg")))
except Exception:
pass
if __name__ == "__main__":
main()