吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 910|回复: 7
上一主题 下一主题
收起左侧

[Python 原创] 腾讯天御过滑块

  [复制链接]
跳转到指定楼层
楼主
wabc666 发表于 2026-10-3 14:52 回帖奖励


目标站点,https://vip.meijiehezi.com/index/login/login



页面登陆校验逻辑

打开登录页 → 页面加载验证码相关 JS、采集电脑 / 浏览器指纹 → 弹出滑块 → 拖动滑块到位置 → 浏览器算出算力证明、上报给腾讯天御服务器校验 → 天御返回合法票据(ticket) → 把账号密码 + 票据 + CSRF 令牌一起提交网站登录接口 → 服务器校验通过,下发 Cookie 会话,跳转后台首页,登录成功





实现流程


  • c.get(LOGIN_PAGE):访问登录页,拿到__token__和会话 Cookie
  • cap_union_prehandle:初始化腾讯验证码会话,拿到图片地址、滑块参数、PoW 题目
  • 下载滑块图、背景图
  • solve_gap():图像算法算出滑块缺口 X 坐标
  • solve_pow():穷举算出 PoW 答案
  • cap_union_new_verify:提交滑块坐标 + PoW 答案给腾讯,校验通过拿到ticket
  • 当前代码:提交账号密码 + ticket 给媒介盒子,登录账号



协议方式过滑块直接实现登陆




[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()









免费评分

参与人数 4吾爱币 +10 热心值 +3 收起 理由
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
liuxuming3303 + 1 + 1 谢谢@Thanks!
Good11 + 1 谢谢@Thanks!
Cz99999 + 1 + 1 我很赞同!

查看全部评分

发帖前要善用【论坛搜索】功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
sht199 发表于 2026-10-3 15:23
代码收下了
3#
ttttyyyy 发表于 2026-10-3 18:31
4#
lengkeyu 发表于 2026-10-3 21:13
5#
zgrm1000 发表于 2026-10-3 21:46
太感谢,我来学习一下代码
6#
wo94pojie 发表于 2026-10-3 21:53
这个厉害了,谢谢楼主。
7#
dork 发表于 2026-10-4 10:31
这个网站本身的设计也存在逻辑漏洞
8#
nullable 发表于 2026-10-7 09:59
感谢提供思路
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-10-16 18:07

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表