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

image.png (167.65 KB, 下载次数: 6)
下载附件
2025-8-9 18:16 上传
目标补环境代码与实现
提交参数验证成功
抓包分析 获取验证码接口通过刷新验证码结果可以发现带有 cap_union_prehandle 的接口返回值含有验证码相关参数:带缺口的背景图、滑块等参数。
由于图片大小问题,仅展示部分参数图片。

image-1.png (115.47 KB, 下载次数: 6)
下载附件
1
2025-8-9 18:14 上传
返回值分析:
sess : 该参数不做处理,用于提交时携带
data :
comm_captcha_cfg:
pow_cfg : md5 和 prefix 参数会用于后续计算,相当重要!!!
tdc_path : js 文件,每次验证码请求时都会刷新,其中有两个重要参数在提交接口用到,也是本次补环境的目标,后续会说明。
dyn_show_info:
bg_elem_cfg : 这里背景图的相关参数,关注 img_url 即可。
sprite_url : 滑块图片,但其中还含有拖动条等,需要裁剪出滑块图片。
验证码提交接口手动提交时,会请求一个验证接口,参数如下:

image-2.png (222.47 KB, 下载次数: 7)
下载附件
2
2025-8-9 18:14 上传
请求参数分析:
collect : tdc.js 文件产生,也是本次的目标。包含轨迹和环境的校验。
tlg : collect 的长度
eks : 也是通过 tdc.js 文件产生。当collect参数能正常产出,该值也能正常产出。
sess : 获取验证码接口返回的值
ans : 滑块的缺口值
pow_answer : md5 和 prefix计算所产生的值,后续说明。
pow_calc_time: md5 和 prefix计算完成时所消耗的时间。
补环境这里仅给出部分补环境代码与思路,按照思路来是没有问题的。
堆栈分析通过验证请求接口分析,查看请求堆栈:

image-3.png (78.48 KB, 下载次数: 5)
下载附件
3
2025-8-9 18:14 上传

image-4.png (58.84 KB, 下载次数: 4)
下载附件
4
2025-8-9 18:14 上传

image-5.png (111.9 KB, 下载次数: 6)
下载附件
5
2025-8-9 18:14 上传

image-6.png (75.59 KB, 下载次数: 6)
下载附件
6
2025-8-9 18:14 上传

image-7.png (41.03 KB, 下载次数: 6)
下载附件
7
2025-8-9 18:14 上传

image-8.png (50.11 KB, 下载次数: 4)
下载附件
8
2025-8-9 18:14 上传
在开始之前,可能会有问题:补环境,该怎么补?该补哪些属性?这个问题的答案跟着本文走一遍,相信你应该会有答案。
首先需要一个代理器(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;操作都被记录下来了。

image-9.png (39.07 KB, 下载次数: 6)
下载附件
9
2025-8-9 18:14 上传
正式开始将所有对象开始监听,下列是主要代码:
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,这个时候就需要注意了,与浏览器进行对比,浏览器有才补,没有不补。

image-10.png (109.17 KB, 下载次数: 6)
下载附件
10
2025-8-9 18:14 上传

image-11.png (69.6 KB, 下载次数: 4)
下载附件
11
2025-8-9 18:14 上传
接着运行代码,查看日志,随着环境补的越来越多,就需要仔细分析日志。

image-12.png (166.74 KB, 下载次数: 6)
下载附件
12
2025-8-9 18:14 上传

image-13.png (153 KB, 下载次数: 7)
下载附件
13
2025-8-9 18:14 上传

image-14.png (28.44 KB, 下载次数: 7)
下载附件
14
2025-8-9 18:14 上传

image-15.png (13.69 KB, 下载次数: 7)
下载附件
15
2025-8-9 18:14 上传
在此运行代码,发现返回的与浏览器一致了,但是衍生出一个新问题,如果运行下面代码会发生什么呢?
log(window.toString.toString());

image-16.png (29.61 KB, 下载次数: 5)
下载附件
16
2025-8-9 18:14 上传

image-17.png (18.47 KB, 下载次数: 8)
下载附件
17
2025-8-9 18:14 上传
通过safeFunction函数来保护window.toString方法,应用代码并在此运行检测,发现已经与浏览器的一致了。
safeFunction(window.toString);

image-18.png (25.81 KB, 下载次数: 6)
下载附件
18
2025-8-9 18:14 上传
这里在讲解一下原型的补法,执行下列代码并分析。
document.__proto__.toString(); document.__proto__.__proto__.toString();

image-19.png (99.8 KB, 下载次数: 7)
下载附件
19
2025-8-9 18:14 上传

image-20.png (26.89 KB, 下载次数: 7)
下载附件
20
2025-8-9 18:14 上传
按照这种思路一边看日志分析一边与浏览器进行对比,按照这个思路来慢慢补就行了,我补了大概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(); 滑块裁剪滑块的原本图像为这样

image-21.png (67.19 KB, 下载次数: 5)
下载附件
21
2025-8-9 18:14 上传
接着提取滑块的缺口位置,这里使用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_answer 和 pow_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计算随机的话,接口也会给你返回正确的参数,但是!将该参数提交过去验证,就会提示验证码错误!

image-22.png (57.65 KB, 下载次数: 7)
下载附件
22
2025-8-9 18:14 上传

image-23.png (57.44 KB, 下载次数: 6)
下载附件
23
2025-8-9 18:14 上传
最后补环境一定要仔细,耐心,中间不会的问问AI,再结合实践,你也是可以的!
















查看全部评分