吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 5581|回复: 43
上一主题 下一主题
收起左侧

[Web逆向] 某x滑块补环境分析

  [复制链接]
跳转到指定楼层
#
BinYoooo 发表于 2025-8-9 18:22 回帖奖励
本帖最后由 BinYoooo 于 2025-8-9 18:25 编辑

声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!若有侵权,请联系作者删除。

研究对象

目标

  • 补环境代码与实现
  • 提交参数验证成功

抓包分析

获取验证码接口

通过刷新验证码结果可以发现带有 cap_union_prehandle 的接口返回值含有验证码相关参数:带缺口的背景图、滑块等参数。

由于图片大小问题,仅展示部分参数图片。

返回值分析:

  • sess : 该参数不做处理,用于提交时携带
  • data :
    • comm_captcha_cfg:
      • pow_cfg  : md5prefix 参数会用于后续计算,相当重要!!!
      • tdc_path : js 文件,每次验证码请求时都会刷新,其中有两个重要参数在提交接口用到,也是本次补环境的目标,后续会说明。
    • dyn_show_info:
      • bg_elem_cfg : 这里背景图的相关参数,关注 img_url 即可。
      • sprite_url  : 滑块图片,但其中还含有拖动条等,需要裁剪出滑块图片。
验证码提交接口

手动提交时,会请求一个验证接口,参数如下:

请求参数分析:

  • collect      : tdc.js 文件产生,也是本次的目标。包含轨迹和环境的校验。
  • tlg          : collect 的长度
  • eks          : 也是通过 tdc.js 文件产生。当collect参数能正常产出,该值也能正常产出。
  • sess         : 获取验证码接口返回的值
  • ans          : 滑块的缺口值
  • pow_answer   : md5prefix计算所产生的值,后续说明。
  • pow_calc_time: md5prefix计算完成时所消耗的时间。

补环境

这里仅给出部分补环境代码与思路,按照思路来是没有问题的。

堆栈分析

通过验证请求接口分析,查看请求堆栈:


发现有关键字样:e.verify,这里是请求组装完成的地方。点击进入,并添加断点。

重新发起请求,进入断点,发现c参数已经生成好了。

往上分析,发现c参数在这里生成,同时可以发现collect参数通过o.getTdcData生成

进入o.getTdcData方法,可以发现最终的返回值是由window.TDC.getData(!0)产生,添加断点,并重新请求。

进入断点,鼠标放在window.TDC.getData(!0)上面,可以发现,该方法的实现在tdc.js文件中。

进入该js文件,会发现是jsvmp。至于什么是jsvmp,问问度娘吧~

开始之前

在开始之前,可能会有问题:补环境,该怎么补?该补哪些属性?这个问题的答案跟着本文走一遍,相信你应该会有答案。

首先需要一个代理器(Proxy),这是ChatGPT5对于Proxy的解释:

Proxy 是 JavaScript 中一个强大的内建对象,它用于创建一个 代理对象,通过它可以拦截并定义基本操作(如属性读取、赋值、函数调用等)的自定义行为。

简单的理解就是:通过Proxy可以知道对象调用什么了方法、属性等信息。

下面是本次目标需要的代理器实现:

const printLog = true;
// 控制是否输出
function log() {
    if (printLog) {
        console.log(...arguments);
    }
}
function watch(obj, name) {
    return new Proxy(obj, {
        get: function (target, property, receiver) {
            try {
                if (typeof target[property] === "function") {
                    log(`监控对象 get => ${name} ,读取属性:`, property + `,值为:` + 'function' + `,类型为:` + (typeof target[property]));
                } else {
                    log(`对象 => ${name} ,读取属性:`, property + `,值为:` + target[property] + `,类型为:` + (typeof target[property]));
                }
            } catch (e) { }
            return Reflect.get(...arguments);
        },
        set: function (target, property, newValue, receiver) {
            try {
                log(`监控对象 set => ${name} ,设置属性:`, property + `,值为:` + newValue + `,类型为:` + (typeof newValue));
            } catch (e) { }
            return Reflect.set(target, property, newValue, receiver);
        },
        // 监控 `in` 操作符
        has: function (target, prop) {
            log(`监控对象 has => ${name} , 属性 => ${prop} , 是否存在 => ${prop in target}`);
            return Reflect.has(target, prop);;  // 如果属性存在,返回 true,否则返回 false
        },
        getPrototypeOf: function (target) {
            log(`监控对象 prototype => ${name}`, `Object.getPrototypeOf(${name})`);
            return Reflect.getPrototypeOf(target);
        },
        ownKeys: function (target) {
            log("监控对象 ownKeys =>", name);
            return Reflect.ownKeys(target);
        },
        getOwnPropertyDescriptor: function (target, prop) {
            log("监控对象 getOwnPropertyDescriptor =>", name, "属性值:", prop, `Object.getOwnPropertyDescriptor(${name},"${prop}")`);
            return Reflect.getOwnPropertyDescriptor(target, prop);
        },
    });
}

有了上述代码,来实现一个简单的补环境。

document = {
    cookie: "",
};
screen = {
    height: 100,
};

// Proxy代理
document = watch(document, "document");
screen = watch(screen, "screen");

document.cookie;
screen.width;

在终端运行命令

node test.js > test.log

test.log文件中查看日志,会发现document.cookie;screen.width;操作都被记录下来了。

正式开始

将所有对象开始监听,下列是主要代码:

window = globalThis;
document = {};
location = {};
navigator = {};
window = watch(window, "window");
location = watch(location, "location");
document = watch(document, "document");
navigator = watch(navigator, "navigator");

require("./tdc.js");

function getCollect(){
    return decodeURIComponent(window.TDC.getData(!0));
}
const collect = getCollect();
log("输出结果",collect);
log("输出长度",collect.length);

运行代码,会发现日志输出中有些是undefined,这个时候就需要注意了,与浏览器进行对比,浏览器有才补,没有不补。



这里添上screen,但同时不要忘记使用Proxy,只要是对象就使用,防止错过某些重要的值!

window.screen = {};
window.screen = watch(window.screen,"window.screen");

接着运行代码,查看日志,随着环境补的越来越多,就需要仔细分析日志。


还有一个小问题,会发现有需要地方调用了toString

这里需要拿本地运行的效果与浏览器控制台运行的效果看是否一致。


不难发现,本地运行的返回[object global],浏览器运行的返回[object Window],明显不一样,这里需要重写toString方法。

window.toString = function toString(){
    return '[object Window]';
}

在此运行代码,发现返回的与浏览器一致了,但是衍生出一个新问题,如果运行下面代码会发生什么呢?

log(window.toString.toString());



这里与浏览器运行的结果又不一样了,是不是也可以拿来作为检测点对不对!不用怕,也有应对方法。

function safeFunction(func) {
    Function.prototype.$call = Function.prototype.call;
    const $toString = Function.toString;
    const myFunction_toString_symbol = Symbol('('.concat('', ')'));
    const myToString = function myToString() {
        return typeof this === 'function' && this[myFunction_toString_symbol] || $toString.$call(this);
    }
    const set_native = function set_native(func, key, value) {
        Object.defineProperty(func, key, {
            "enumerable": false,
            "configurable": true,
            "writable": true,
            "value": value
        });
    }
    delete Function.prototype['toString'];
    set_native(Function.prototype, "toString", myToString);
    set_native(Function.prototype.toString, myFunction_toString_symbol, "function toString() { [native code] }");
    const safe_Function = function safe_Function(func) {
        set_native(func, myFunction_toString_symbol, "function" + (func.name ? " " + func.name : "") + "() { [native code] }");
    }
    return safe_Function(func)
}

通过safeFunction函数来保护window.toString方法,应用代码并在此运行检测,发现已经与浏览器的一致了。

safeFunction(window.toString);

这里在讲解一下原型的补法,执行下列代码并分析。

document.__proto__.toString();
document.__proto__.__proto__.toString();



发现本地环境这边直接报错了,因为在创建document对象时,给的就是{},这种方式创建的对象默认在Object下,已经是最顶层了,所以无法在调用__proto__.__proto__,所以这里需要补原型了。
document的原型是HTMLDocument,下面给出代码:

class HTMLDocument{};
safeFunction(HTMLDocument);
HTMLDocument.prototype.toString = function toString(){
    return "[object HTMLDocument]";
}
safeFunction(HTMLDocument.prototype.toString);
document = new HTMLDocument();

按照这种思路一边看日志分析一边与浏览器进行对比,按照这个思路来慢慢补就行了,我补了大概500多行左右,就可以正常出值了。下面给出部分补好的环境代码,剩下的仿照继续补。

class EventTarget {
    constructor() {
        safeFunction(this.addEventListener);
        safeFunction(this.dispatchEvent);
        this.listeners = {}; // 存储事件监听器
    }

    // 模拟 addEventListener 方法
    addEventListener(type, callback) {
        if (!this.listeners[type]) {
            this.listeners[type] = [];
        }
        log("注册监听器", type, callback.toString());
        this.listeners[type].push(callback);
    }

    // 模拟 dispatchEvent 方法
    dispatchEvent(event) {
        const listeners = this.listeners[event.type];
        if (listeners) {
            listeners.forEach(listener => listener(event));
        }
    }
}
window = global;
window.top = window;
window.self = window;
window.Buffer = Buffer;
delete window.navigator;
delete global
delete Buffer
delete process;
delete __dirname;
delete __filename;
class HTMLDocument extends EventTarget { };
HTMLDocument.prototype.characterSet = "UTF-8";
HTMLDocument.prototype.charset = "UTF-8";
HTMLDocument.prototype.cookie = "";
HTMLDocument.prototype.URL = "https://turing.captcha.gtimg.com/1/template/drag_ele.html";
document = new HTMLDocument();
screen = {};
screen.toString = function toString() {
    return "[object Screen]";
};
safeFunction(screen.toString);
location = {
    href: "https://turing.captcha.gtimg.com/1/template/drag_ele.html"
};
location.toString = function toString() {
    return "https://turing.captcha.gtimg.com/1/template/drag_ele.html";
}
safeFunction(location.toString);
class Navigator { };
Navigator.prototype.platform = "MacIntel";
Navigator.prototype.languages = ["zh-CN", "zh"];
Navigator.prototype.vendor = "Google Inc.";
Navigator.prototype.appName = "Netscape";
Navigator.prototype.hardwareConcurrency = 10;
Navigator.prototype.webdriver = false;
Navigator.prototype.cookieEnabled = true;
Navigator.prototype.appVersion = "";
Navigator.prototype.userAgent = "";
Navigator.prototype.serviceWorker = watch({}, "navigator.serviceWorker");
Navigator.prototype.requestMIDIAccess = function requestMIDIAccess() {
    debugger;
};
safeFunction(Navigator.prototype.requestMIDIAccess);
Navigator.prototype.toString = function toString() {
    return "[object Navigator]";
};
safeFunction(Navigator.prototype.toString);
navigator = new Navigator();

滑块裁剪

滑块的原本图像为这样


这里就需要裁剪,裁剪的代码,

img = cv2.imread("slide.png", cv2.IMREAD_UNCHANGED)
x_start, y_start = 140, 490
width, height = 120, 120  # 假设你要切割 100x100 的区域
cropped_image = img[y_start : y_start + height, x_start : x_start + width]

缺口位置识别

接着提取滑块的缺口位置,这里使用ddddocr来实现

from ddddocr import DdddOcr
det = DdddOcr(det=False, ocr=False)
res = det.slide_match(
    open("slide.png", "rb").read(), 
    open("bg.png", "rb").read(), 
    simple_target=True
)["target"]
ans = f'[{{"elem_id":1,"type":"DynAnswerType_POS","data":"{res[0]},{res[1]}"}}]'

pow_answerpow_calc_time 实现

pow_answer本质上是通过 验证码接口返回的 prefix 加上数字在经过哈希算法的结果与返回的md5的值一致就是。下面是通过ChatGPT5生成的计算代码。

def get_workload_result(nonce, target, timeout_ms=30000):
    """
    通过暴力枚举,找到一个整数 u 使得 MD5(nonce + u) == target
    :param nonce: 字符串前缀
    :param target: 目标 MD5 值
    :param timeout_ms: 超时限制,单位毫秒
    :return: 字典 {'ans': u, 'duration': 耗时毫秒}
    """
    start_time = time.time()
    u = 0

    while True:
        # 计算 MD5
        hash_result = md5_hash(f"{nonce}{u}")
        # 如果匹配,返回结果
        if hash_result == target:
            return {
                "ans": f"{nonce}{u}",
                "duration": int((time.time() - start_time) * 1000),
            }
        # 检查超时
        if (time.time() - start_time) * 1000 > timeout_ms:
            break
        # 增加 u
        u += 1
    # 如果超时,返回当前 u 和已消耗时间
    return {"ans": f"{nonce}#{u}", "duration": (time.time() - start_time) * 1000}

注意有个问题,如果这里的pow_answer计算随机的话,接口也会给你返回正确的参数,但是!将该参数提交过去验证,就会提示验证码错误!


正确计算的结果

最后

补环境一定要仔细,耐心,中间不会的问问AI,再结合实践,你也是可以的!


免费评分

参与人数 17吾爱币 +16 热心值 +16 收起 理由
qwq2233 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
MenH + 1 谢谢@Thanks!
ghdisjdgdijd + 1 + 1 我很赞同!
425529 + 1 + 1 谢谢@Thanks!
xioabo + 1 热心回复!
gpchhao + 1 + 1 谢谢@Thanks!
玖卿 + 2 + 1 我很赞同!
user23456 + 1 + 1 谢谢@Thanks!
zxzx307 + 1 + 1 谢谢@Thanks!
fengbolee + 2 + 1 用心讨论,共获提升!
ioyr5995 + 1 + 1 热心回复!
Eddie-sjm + 1 + 1 我很赞同!
allspark + 1 + 1 用心讨论,共获提升!
liuxuming3303 + 1 + 1 谢谢@Thanks!
mjhwzwg6 + 1 + 1 谢谢@Thanks!
fiscivaj + 1 谢谢@Thanks!
helian147 + 1 + 1 热心回复!

查看全部评分

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

44#
bigsuperplum 发表于 2026-2-9 18:50
感谢分享,收获颇多
43#
Kazusaaa 发表于 2026-1-5 18:30
42#
775673603 发表于 2025-11-10 09:38
41#
K23 发表于 2025-11-5 20:45
思路清晰,学习到了,感谢大佬
40#
netfox007 发表于 2025-11-3 09:01
不错,思路清晰,值得参考
39#
AjiaJiShu 发表于 2025-9-10 16:03
大佬,六宫格的验证这个环境也能过吗
38#
NXXX13 发表于 2025-9-9 19:03
最近也在搞 写的不错
37#
zhoujin1 发表于 2025-9-9 09:22
非常不错,就是很难阿
36#
7001 发表于 2025-9-5 12:00
算出模拟滑块的轨迹,还是模拟滑块移动更好?
35#
rustc 发表于 2025-8-29 20:11
又是学习新知识的一天
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2026-2-14 10:35

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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