验证码识别与处理细节总结

验证码的破解虽然整体难度不大,但某些细节处理不当可能会导致各种错误。在本文中,我们总结了一些常见问题及其解决方法,包括验证码识别方案和轨迹处理等。

关于 w 值
在某些验证码系统(如第三代系统)中,有几个请求接口会包含 w 值。需要特别注意的是,除了最后一个校验接口 ajax.php 外,其他接口的 w 值可以为空,但在某些模式下,如无感验证模式,在请求 get.php 接口获取 c 和 s 值时,也会校验 w 值。

常见错误:

json

{'status': 'error', 'error': 'param decrypt error', 'user_error': '网络不给力', 'error_code': 'error_03'}
关于时间间隔
验证码系统通常要求请求之间有一定的时间间隔。如果请求太快,验证会失败。例如,在第三代系统中,如果生成 w 值后没有随机停留 2 秒左右,验证可能会失败。

常见错误:

json

{'status': 'success', 'data': {'result': 'fail', 'msg': ['duration short']}}
关于 challenge
在第三代系统中,challenge 值会参与多次请求。第一次获取到一个 challenge,后续的 get.php 请求返回的数据中会包含一个新的 challenge,新 challenge 比第一次多了两位数。所有后续请求都必须使用新的 challenge。

常见错误:

json

{'success': 0, 'message': 'fail'}
关于 c 和 s
在第三代系统中,c 和 s 值会参与 w 的计算。点选和滑块验证码类型中,第一次 get.php 请求返回的 c 和 s 值,第二次 get.php 请求返回的 s 值会变化。生成 w 时需要使用第二次返回的 s 值。

常见错误:

json

{'success': 0, 'message': 'forbidden'}
关于两次 get.php 和 ajax.php 请求
在第三代系统中,点选和滑块验证码会有两次以 get.php 和 ajax.php 结尾的请求。第一次 get.php 返回主题、域名、提示文字等信息,第一次 ajax.php 返回验证码类型。这两次请求返回的数据虽然对我们没太大用处,但仍需发起请求,否则后续请求将失败。

关于智能组合验证
智能组合验证可以处理多种验证码类型。四代系统更为简洁,通过 load 接口的 captcha_type 字段直接告诉你验证码类型,如滑块、点选(及其子类型)、五子棋或九宫格等。

关于 w 值的算法
w 值生成算法中涉及一些细节,包括 passtime、pow_sign 和 pow_msg。

passtime
passtime 值在生成 w 时参与计算。对于滑块验证码,passtime 是滑动花费的时间,直接取轨迹的最后一个时间值。其他情况下,该值可设为一个随机值。

pow_sign 和 pow_msg
这两个参数是四代系统独有的。pow_msg 的格式如下:

text

1|0|md5|datetime|captcha_id|lot_number||随机字符串
pow_sign 是 pow_msg 经 MD5 加密后的值。需要注意,pow_sign 的生成必须符合特定条件,否则会导致验证失败。下面是一个处理示例:

javascript

var CryptoJS = require("crypto-js");

function getRandomString(){
    function e(){
        return (65536 * (1 + Math.random()) | 0).toString(16).substring(1);
    }
    return e() + e() + e() + e();
}

function get_pow(pow_detail, captcha_id, lot_number) {
    var n = pow_detail.hashfunc;
    var i = pow_detail.version;
    var r = pow_detail.bits;
    var s = pow_detail.datetime;
    var o = "";

var a = r % 4;
    var u = parseInt(r / 4, 10);
    var c = function g(e, t) {
        return new Array(t + 1).join(e);
    }("0", u);
    var _ = i + "|" + r + "|" + n + "|" + s + "|" + captcha_id + "|" + lot_number + "|" + o + "|";

while (1) {
        var h = getRandomString()
          , l = _ + h
          , p = void 0;
        switch (n) {
            case "md5":
            p = CryptoJS.MD5(l).toString();
            break;
        case "sha1":
            p = CryptoJS.SHA1(l).toString();
            break;
        case "sha256":
            p = CryptoJS.SHA256(l).toString();
        }
        if (0 == a) {
            if (0 === p.indexOf(c))
                return {
                    "pow_msg": _ + h,
                    "pow_sign": p
                };
        } else if (0 === p.indexOf(c)) {
            var f = void 0
              , d = p[u];
            switch (a) {
            case 1:
                f = 7;
                break;
            case 2:
                f = 3;
                break;
            case 3:
                f = 1;
            }
            if (d <= f)
                return {
                    "pow_msg": _ + h,
                    "pow_sign": p
                };
        }
    }
}
随机变化的字符串
在验证码生成过程中,通常会有一个 16 位随机字符串参与 w 的加密计算。这个字符串一般会用到两次,且两次必须相同。

常见错误:

json

{'status': 'error', 'error': 'param decrypt error', 'user_error': '网络不给力', 'error_code': 'error_03'}
随机变化的键值对
在三代和四代系统中,w 值的生成过程中会有一个随机键值对。以三代滑块为例,以下是一个动态获取该键值对的示例:

python

import re
import execjs
import requests

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36",
}

gct_path = "https://static.geetest.com/static/js/gct.b71a9027509bc6bcfef9fc6a196424f5.js"
gct_js = requests.get(gct_path, headers=headers).text
function_name = re.findall(r")){return (.*?)(", gct_js)[0]
break_position = gct_js.find("return function(t){")
gct_js_new = gct_js[:break_position] + "window.gct=" + function_name + ";" + gct_js[break_position:]
gct_js_new = "window = global;" + gct_js_new + """
function getGct(){
    var e = {"lang": "zh", "ep": "test data"};
    window.gct(e);
    delete e["lang"];
    delete e["ep"];
    return e;
}"""
gct = execjs.compile(gct_js_new).call("getGct")
print(gct)

补环境中可能用到的方法
window.crypto.getRandomValues
在某些情况下,如三代滑块验证中,可能会用到 window.crypto.getRandomValues() 方法。

javascript

window = global;
window.crypto = {
    getRandomValues: getRandomValues_
}

function randoms(min, max) {
    return Math.floor(Math.random() * (max - min + 1) + min)
}

function getRandomValues_(buf) {
    var min = 0,
    max = 255;
    if (buf.length > 65536) {
        var e = new Error();
        e.code = 22;
        e.message = 'Failed to execute 'getRandomValues' : The ' + 'ArrayBufferView's byte length (' + buf.length + ') exceeds the ' + 'number of bytes of entropy available via this API (65536).';
        e.name = 'QuotaExceededError';
        throw e;
    }
    if (buf instanceof Uint16Array) {
        max = 65535;
    } else if (buf instanceof Uint32Array) {
        max = 4294967295;
    }
    for (var element in buf) {
        buf[element] = randoms(min, max);
    }
    return buf;
}

// 测试
// var a = new Uint32Array(256);
// console.log(window.crypto.getRandomValues(a))
window.performance.timing
某些性能指标需要 window.performance.timing 方法来获取。例如:

javascript

window = global;
window.performance = {
    timing: {
        navigationStart: Date.parse(new Date()),
        unloadEventStart: 0,
        unloadEventEnd: 0,
        redirectStart: 0,
        redirectEnd: 0,
        fetchStart: 0,
        domainLookupStart: 0,
        domainLookupEnd: 0,
        connectStart: 0,
        connectEnd: 0,
        secureConnectionStart: 0,
        requestStart: 0,
        responseStart: 0,
        responseEnd: 0,
        domLoading: 0,
        domInteractive: 0,
        domContentLoadedEventStart: 0,
        domContentLoadedEventEnd: 0,
        domComplete: 0,
        loadEventStart: 0,
        loadEventEnd: 0,
    }
}
完整的环境搭建代码示例
javascript

const { v4: uuidv4 } = require('uuid');
const axios = require('axios');
const CryptoJS = require('crypto-js');

function getRandomString(){
    function e(){
        return (65536 * (1 + Math.random()) | 0).toString(16).substring(1);
    }
    return e() + e() + e() + e();
}

function get_pow(pow_detail, captcha_id, lot_number) {
    var n = pow_detail.hashfunc;
    var i = pow_detail.version;
    var r = pow_detail.bits;
    var s = pow_detail.datetime;
    var o = "";

var a = r % 4;
    var u = parseInt(r / 4, 10);
    var c = function g(e, t) {
        return new Array(t + 1).join(e);
    }("0", u);
    var _ = i + "|" + r + "|" + n + "|" + s + "|" + captcha_id + "|" + lot_number + "|" + o + "|";

while (1) {
        var h = getRandomString()
          , l = _ + h
          , p = void 0;
        switch (n) {
            case "md5":
            p = CryptoJS.MD5(l).toString();
            break;
        case "sha1":
            p = CryptoJS.SHA1(l).toString();
            break;
        case "sha256":
            p = CryptoJS.SHA256(l).toString();
        }
        if (0 == a) {
            if (0 === p.indexOf(c))
                return {
                    "pow_msg": _ + h,
                    "pow_sign": p
                };
        } else if (0 === p.indexOf(c)) {
            var f = void 0
              , d = p[u];
            switch (a) {
            case 1:
                f = 7;
                break;
            case 2:
                f = 3;
                break;
            case 3:
                f = 1;
            }
            if (d <= f)
                return {
                    "pow_msg": _ + h,
                    "pow_sign": p
                };
        }
    }
}

window = global;
window.crypto = {
    getRandomValues: getRandomValues_
}

function randoms(min, max) {
    return Math.floor(Math.random() * (max - min + 1) + min)
}

function getRandomValues_(buf) {
    var min = 0,
    max = 255;
    if (buf.length > 65536) {
        var e = new Error();
        e.code = 22;
        e.message = 'Failed to execute 'getRandomValues' : The ' + 'ArrayBufferView's byte length (' + buf.length + ') exceeds the ' + 'number of bytes of entropy available via this API (65536).';
        e.name = 'QuotaExceededError';
        throw e;
    }
    if (buf instanceof Uint16Array) {
        max = 65535;
    } else if (buf instanceof Uint32Array) {
        max = 4294967295;
    }
    for (var element in buf) {
        buf[element] = randoms(min, max);
    }
    return buf;
}

window.performance = {
    timing: {
        navigationStart: Date.parse(new Date()),
        unloadEventStart: 0,
        unloadEventEnd: 0,
        redirectStart: 0,
        redirectEnd: 0,
        fetchStart: 0,
        domainLookupStart: 0,
        domainLookupEnd: 0,
        connectStart: 0,
        connectEnd: 0,
        secureConnectionStart: 0,
        requestStart: 0,
        responseStart: 0,
        responseEnd: 0,
        domLoading: 0,更多内容联系1436423940
        domInteractive: 0,更多内容访问ttocr.com或联系1436423940
        domContentLoadedEventStart: 0,
        domContentLoadedEventEnd: 0,
        domComplete: 0,
        loadEventStart: 0,
        loadEventEnd: 0,
    }
}

async function getCaptchaData() {
    const response = await axios.get('https://api.example.com/captcha');
    const captchaData = response.data;
    return captchaData;
}

(async () => {
    try {
        const captchaData = await getCaptchaData();
        const powResult = get_pow(captchaData.pow_detail, captchaData.captcha_id, captchaData.lot_number);
        console.log('POW Result:', powResult);
    } catch (error) {
        console.error('Error fetching captcha data:', error);
    }
})();

posted @   ttocr、com  阅读(40)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示