验证码案例(极验3滑动模式)

验证码案例 【极验3滑动模式】

1 声明

本案例中所有内容仅供个人学习交流,抓包内容、敏感网址、数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

2 案例目标

2.1 网站

aHR0cHM6Ly93d3cuZ2VldGVzdC5jb20vZGVtby9zbGlkZS1mbG9hdC5odG1s

2.2 接口

aHR0cHM6Ly9hcGl2Ni5nZWV0ZXN0LmNvbS9nZXQucGhw
aHR0cHM6Ly9hcGkuZ2VldGVzdC5jb20vZ2V0LnBocA==
aHR0cHM6Ly9hcGkuZ2VldGVzdC5jb20vYWpheC5waHA=

2.3 参数

生成w以及相关参数

2.4 算法

RSAAESMD5

3 验证流程分析

3.1 抓包分析

1 register-slide?
	返回challenge gt

2 gettype.php   
    返回一堆JS文件

3 get.php
    请求参数:gt   challenge  w
    返回一堆数据

4 ajax.php  获取滑块类型
    请求参数:gt   challenge  w
    返回滑块标记 {"status": "success", "data": {"result": "slide"}}

5 get.php
     请求参数:gt   challenge w
     返回了验证码信息
    

6 ajax.php  提交验证
    请求参数:gt   challenge  w
    成功  message: "success"
    失败  message: "fail"

通过分析我们只需要请求 1===>3===>4===>5===>6 (后面验证后3其实也可以省略)

3.2 获取验证码图片

3.2.1 大致流程

  • 请求register-slide?接口,拿到gtchallenge
  • 带上gt、challenge、w可置空请求get.php? 返回c、s
  • 带上gt、challenge、w置空 请求ajax.php接口,返回验证码类型slide
  • 带上gt、challeng再次请求get.php接口,验证码信息(包含图片背景图、缺口背景图),从json中取出新的c、s、challenge值 (c没变)

3.2.2 详细实现

​ 首先,要拿到gt和challenge,直接请求获取

Query String Parameters、Form Data 和 Request Payload 都是 HTTP 请求中传递数据的方式,主要区别在于数据的格式和传递方式。

1. Query String Parameters:是通过 URL 的查询字符串传递数据的一种方式。查询字符串是 URL 中 ? 后面的部分,它包含了若干个键值对,用 & 分隔。例如,https://www.example.com/search?q=keyword&sort=desc 中的 q 和 sort 都是 Query String Parameters。这种方式适合于传递简单的数据,如搜索关键字、分页等。

2. Form Data:是通过 HTML 表单提交数据的一种方式。表单数据被编码为一系列键值对,通过 POST 请求发送到服务器。这种方式适合于传递更复杂的数据,如用户注册信息、文章发布等。

3. Request Payload:是通过 HTTP 请求主体传递数据的一种方式。它适用于传递复杂的结构化数据,如 JSON 或 XML 格式的数据。Request Payload 通常与 RESTful API 和 Ajax 交互中使用。

Query String Parameters 适合传递简单的数据,Form Data 适合传递表单数据,而 Request Payload 适合传递结构化数据,具有更大的灵活性和可扩展性。
	第一个get.php,带上gt、challenge、callback是geetest+时间戳、w可置空请求get.php? 返回c、s

image-20230512113107504

	ajax.php:

​ 带上gt、challenge、w置空 请求接口,返回验证码类型slide
image-20230512113441999

	第二次请求get.php

​ 带上gt、challeng再次请求接口,验证码信息(包含图片背景图、缺口背景图),从json中取出新的c、s、challenge值 (c没变)

坑在这个返回的challenge,比之前的值后面多了两字符

image-20230512113838961

看到获取的图片是乱码的,接下来需要对图片进行还原。

image-20230512114110715

3.3底图还原

由于某验验证码是用canvas绘图的(F12查看验证码元素可以看出)

第一种方法:打上canvas断点,画布创建时会短住


for (var a = r / 2, _ = 0; _ < 52; _ += 1) {
    // 取数组 Ut[_]对 26 取模,乘以 12 再加 1,结果赋值给变量 c
    var c = Ut[_] % 26 * 12 + 1;
    var u = 25 < Ut[_] ? a : 0;
    // 在画布上将像素数据绘制到指定位置
    var l = o['putImageData'](c, u, 10, a);

    // 获取指定矩形区域内的像素数据 ImageData 对象
    s['getImageData'](l, _ % 26 * 10, 25 < _ ? a : 0);
}

image-20230512142640120

第二种方法

​ 可以hook创建canvas对象的地方,直接百度参考一个:

// hook canvas
(function() {
    'use strict';
    let create_element = document.createElement.bind(document);

    document.createElement = function (_element) {
        console.log("create_element:",_element);
        if (_element === "canvas") {
            debugger;
        }
        return create_element(_element);
    }
})();

image-20230512142321806

这里逻辑还是很清晰的,但如果扣js的话相对来说要麻烦一些,还要依赖第三方canvas库,不如直接用python复写逻辑(用到了PIL库) Ut是定值

image-20230512143911286

还原之后类似下图:

image-20230512143740136

3.4 获取缺口距离生成轨迹

缺口距离识别,可以手动,也可以第三方平台,这里选择ddddocr识别效果还不错 直接上代码

    def get_x(self):
        slide = ddddocr.DdddOcr(det=False, ocr=False)
        with open('bg.jpg', 'rb') as f:
            target_bytes = f.read()
        with open('full.jpg', 'rb') as f:
            background_bytes = f.read()
        res = slide.slide_comparison(target_bytes, background_bytes)

        return res.get('target')[0]

通过缓动函数生成轨迹,参考https://easings.net/zh-cn#easeOutExpo

    def __ease_out_expo(self,sep):
        """
        缓动函数 easeOutExpo
        参考:https://easings.net/zh-cn#easeOutExpo
        """
        if sep == 1:
            return 1
        else:
            return 1 - pow(2, -10 * sep)

    def get_slide_track(self,distance):
        """
        根据滑动距离生成滑动轨迹
        :param distance: 需要滑动的距离
        :return: 滑动轨迹<type 'list'>: [[x,y,t], ...]
            x: 已滑动的横向距离
            y: 已滑动的纵向距离, 除起点外, 均为0
            t: 滑动过程消耗的时间, 单位: 毫秒
        """

        if not isinstance(distance, int) or distance < 0:
            raise ValueError(f"distance类型必须是大于等于0的整数: distance: {distance}, type: {type(distance)}")
        # 初始化轨迹列表
        slide_track = [
            [random.randint(-50, -10), random.randint(-50, -10), 0],
            [0, 0, 0],
        ]
        # 共记录count次滑块位置信息
        count = 30 + int(distance / 2)
        # 初始化滑动时间
        t = random.randint(50, 100)
        # 记录上一次滑动的距离
        _x = 0
        _y = 0
        for i in range(count):
            # 已滑动的横向距离
            x = round(self.__ease_out_expo(i / count) * distance)
            # 滑动过程消耗的时间
            t += random.randint(10, 20)
            if x == _x:
                continue
            slide_track.append([x, _y, t])
            _x = x
        slide_track.append(slide_track[-1])
        return slide_track

3.5 加密轨迹并提交

  • ​ 可以看到最后ajax.php带上新的gt,chanllenge,w参数想都不用想肯定是包含轨迹内容的。

image-20230513150707245

直接搜索w是搜索不到的,JS里面是被编码的,可以把W值编码,进行查找,在JS里面搜索u0077":

或者通过解混淆,还原后搜索w,参考JS逆向:AST还原极验混淆JS实战 (qq.com)

然后搜索"w",可以看到"w": h + u ,h在5980行,u在5978行 还原如下:

var u = r[$_CAHJe(785)]()
, l = V['encrypt'](gt['stringify'](o), r[$_CAIAj(761)]())
, h = m[$_CAIAj(783)](l)

image-20230513151449940

3.5.1 u参数

先解决u这个函数,滑动滑块之后短住,进去看看

image-20230513152405672

进去之后发现是return的e **this$_CBFJv(761)**是生成的随机数 然后经过加密得到的

image-20230513153653314

进去之后发现就在上面 返回的 **rt()**是16位随机数

image-20230513154535601

里面又是由4个随机数**t()**相加生成的

image-20230513154702257

还原之后Math.random() 是随机选取大于等于 0.0 且小于 1.0 的伪随机 double 值,toString(16) 为十六进制字符串

(65536 * (1 + Math["random"]()) | 0)["toString"](16)["substring"](1)

通过js复现,后续传入16位随机数可以直接通过python写死!方便调试

function random() {
    var random_str = "";
    for (var index = 0; index < 4; index++) {
        random_str += (65536 * (1 + Math["random"]()) | 0)["toString"](16)["substring"](1);
    }
    return random_str;
}

image-20230513154829103

接下来看U里面包括了三个方法 很显然是RSA加密 需要设置公钥

image-20230513181500599

进入new U()[‘encrypt’] 来到lt()方法 2853行 往上找 ut方法 在2850打上断点重新拉滑块 发现了Invalid RSA public key字样

image-20230513181739734

image-20230513181047031

ut 函数传入了两个值,t 为公钥值,e 为公钥模数,都是固定值

t='00C1E3934D1614465B33053E7F48EE4EC87B14B95EF88947713D25EECBFF7E74C7977D02DC1D9451F79DD5D1C10C29ACB6A9B4D6FB7D0A0279B6719E1772565F09AF627715919221AEF91899CAE08C0D686D748B20A3603BE2318CA6BC2B59706592A9219D0BF05C9F65023A21D2330807252AE0066D59CEEFA5F2748EA80BAB81'

e='10001'

这里我们直接利用python RSA实现,通过rsa加密随机数就是U

    def rsa_encrypt(self,random):
        """
        rsa加密
        :param random: 随机数
        :return: 加密后的随机数
        """
        #公钥模数
        n ='00C1E3934D1614465B33053E7F48EE4EC87B14B95EF88947713D25EECBFF7E74C7977D02DC1D9451F79DD5D1C10C29ACB6A9B4D6FB7D0A0279B6719E1772565F09AF627715919221AEF91899CAE08C0D686D748B20A3603BE2318CA6BC2B59706592A9219D0BF05C9F65023A21D2330807252AE0066D59CEEFA5F2748EA80BAB81'
        #公钥指数
        e = '10001'
        #构造公钥
        key = rsa.PublicKey(e = int(e, 16), n = int(n, 16))
        # print('key:',key)
        #加密
        message = rsa.encrypt(random.encode('utf-8'), key)
        #转换成16进制
        encrypt_data  = message.hex()
        return encrypt_data

3.5.2 L参数

l = V['encrypt'](gt['stringify'](o), r[$_CAIAj(761)]())

可以先看里面的参数 o是有一个参数对象组成 r$_CAIAj(761)为之前的16位随机数 有stringify方法所以gt肯定为JSON V为一个加密方法

3.5.2.1 o参数

这里先解决o参数,通过两次对比箭头所指的四个参数在变化 需要分析

image-20230513192443370

  • aa: “a()(T!!LKwswstssss*tstttsstssssts!*x(!!( ) 9 / 9 − − / 010 − . − 01 q 2 , 2 , − . − , 7 / 5 − 5 )9/9--/010-.-01q2,2,-.-,7/5-5 )9/9/010.01q2,2,.,7/55*R” //轨迹加密
  • ep: {v: ‘7.9.0’, $_BIo: false, me: true, tm: {…}, td: -1} //可以随机或者写死
  • h9s9: “1816378497” //可以写死
  • imgload: 257 //图片加载时间可写死
  • lang: “zh-cn”
  • passtime: 406 //轨迹滑动时间
  • rp: “bcb6b32f4b49185db3212719c93a9bef” //gt + 32 位 challenge + passtime,再经过 MD5 加密
  • userresponse: “66666be4” //滑动距离x + challenge 的值

image-20230513185548030

aa

往上找到o 在5917打上断点 aa是有e生成的

image-20230513192713272

直接跳过了 发现在上边没有e 跟上个栈

image-20230513192826999

跳第2个栈8066行、查看第2个参数l 就是这个参数image-20230513192946037

l = n['$_CICa']['$_BBEI'](n['$_CICa']['$_FDL'](), n['$_CJV']['c'], n['$_CJV']['s');

后面两个参数是前面get.php返回的新的cs

image-20230513193918792

image-20230513193948882

外层** F D L ∗ ∗ 和 ∗ ∗ _FDL**和** FDL_BBEI ** 直接进去全扣就行了

image-20230513194256839image-20230513194354339

$_FDL转到3990行4030这里是轨迹

image-20230514122318992

ct里面的**$_CAd**对轨迹进行了做差运算

image-20230514122924449

image-20230513194554653

image-20230513194705244

passtime

去轨迹的最后一个列表的的最后一位数

passtime = track[-1][-1] #滑动时间
userresponse

t为滑动距离x **i[‘challenge’]**为get.php返回的新chanllenge

H(t, i['challenge'])

image-20230513195516191

H()在668行全口还原一下就行

image-20230513195705162

image-20230513195902741

rp

image-20230513200319883

这个rp为32位盲猜一波 MD5 控制台输出一下
image-20230513201000214

还原之后 rp :gt + 32 位 challenge + passtime,再经过 MD5 加密

o['rp'] = X(i['gt'] + i['challenge']['slice'](0, 32) + o['passtime'])
3.5.2.2 V方法

进入V方法 控制台还原一下参数 很明显是AES加密 初始向量 iv 值为 “0000000000000000”:

image-20230513201802141

这里通过引库实现

var CryptoJS = require('crypto-js')
function aesV(o_text, random_str) {
    var key = CryptoJS.enc.Utf8.parse(random_str);
    var iv = CryptoJS.enc.Utf8.parse("0000000000000000");
    var srcs = CryptoJS.enc.Utf8.parse(o_text);
    var encrypted = CryptoJS.AES.encrypt(srcs, key, {
        iv: iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    });
    for (var r = encrypted, o = r.ciphertext.words, i = r.ciphertext.sigBytes, s = [], a = 0; a < i; a++) {
        var c = o[a >>> 2] >>> 24 - a % 4 * 8 & 255;
        s.push(c);
    }
    return s;
};

通过对比结果一致

image-20230513202141617

3.5.3 h参数

通过**m[‘$_FEE’]**对l参数就行加密

h = m['$_FEE'](l)

进入$_FEE 跳转到1609行 发现最后是返回的e里面的两个参数

image-20230513202822412

进去e里面看看 跳转**$_FCc**到1564行 返回的也是res:n 和 end:r

image-20230513202943050

混淆还原之后改写一下方法

function $_FCc(t) {
    // var o = this;
    // i || (i = o);
    for (var e = function(t, e) {
        for (var n = 0, r = 24 - 1; 0 <= r; r -= 1)
            1 === $_FBv(e, r) && (n = (n << 1) + $_FBv(t, r));
        return n;
    }, n = '', r = '', s = t['length'], a = 0; a < s; a += 3) {
        var _;
        if (a + 2 < s)
            _ = (t[a] << 16) + (t[a + 1] << 8) + t[a + 2],
            n += $_EJu(e(_, 7274496)) + $_EJu(e(_, 9483264)) + $_EJu(e(_, 19220)) + $_EJu(e(_, 235));
        else {
            var c = s % 3;
            2 == c ? (_ = (t[a] << 16) + (t[a + 1] << 8),
            n += $_EJu(e(_, 7274496)) + $_EJu(e(_, 9483264)) + $_EJu(e(_, 19220)),
            r = ".") : 1 == c && (_ = t[a] << 16,
            n += $_EJu(e(_, 7274496)) + $_EJu(e(_, 9483264)),
            r = '.' + '.');
        }
    }
    return n+r;
}

image-20230513203855688

3.5.4 w

w = h+u

到此轨迹加密就完成了!!!!

4 结果展示

image-20230513204236315

5 总结

在里面也遇到了很多坑,这种扣算法+js好处就在于不用不用补环境缺点就在于如果没有经验是不知道他的算法的,还有一种全扣的方法需要补环境可以上proxy()拦截环境进行补充比较简单,推荐新手尝试。

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
以下是一个简单的 Java 图片滑动验证码的示例代码,使用 Java Swing 库实现: ```java import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.util.Random; public class ImageSliderVerificationCode extends JFrame { private static final long serialVersionUID = 1L; private final int WIDTH = 400; private final int HEIGHT = 300; private final int BLOCK_SIZE = 50; private Image bgImage; private Image blockImage; private int blockX; private int blockY; private int mouseX; private int mouseY; private boolean isDragging; public ImageSliderVerificationCode() { setTitle("Image Slider Verification Code"); setSize(WIDTH, HEIGHT); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setResizable(false); setLocationRelativeTo(null); bgImage = new ImageIcon("background.jpg").getImage(); blockImage = new ImageIcon("block.png").getImage(); addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { if (e.getX() >= blockX && e.getX() <= blockX + BLOCK_SIZE && e.getY() >= blockY && e.getY() <= blockY + BLOCK_SIZE) { isDragging = true; mouseX = e.getX(); mouseY = e.getY(); } } public void mouseReleased(MouseEvent e) { isDragging = false; if (blockX >= WIDTH - BLOCK_SIZE) { JOptionPane.showMessageDialog(null, "Verification passed!"); System.exit(0); } else { blockX = 0; blockY = new Random().nextInt(HEIGHT - BLOCK_SIZE); repaint(); } } }); addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged(MouseEvent e) { if (isDragging) { blockX += e.getX() - mouseX; blockY += e.getY() - mouseY; if (blockX < 0) { blockX = 0; } if (blockX > WIDTH - BLOCK_SIZE) { blockX = WIDTH - BLOCK_SIZE; } if (blockY < 0) { blockY = 0; } if (blockY > HEIGHT - BLOCK_SIZE) { blockY = HEIGHT - BLOCK_SIZE; } mouseX = e.getX(); mouseY = e.getY(); repaint(); } } }); } public void paint(Graphics g) { g.drawImage(bgImage, 0, 0, null); g.drawImage(blockImage, blockX, blockY, null); } public static void main(String[] args) { ImageSliderVerificationCode verCode = new ImageSliderVerificationCode(); verCode.setVisible(true); } } ``` 此代码将生成一个包含背景图片和滑块图片的窗口。当用户按住滑块图片并拖动它时,窗口将显示移动后的滑块图片。如果用户将滑块图片拖动到窗口的右边缘,则显示验证通过的消息并退出程序。否则,滑块图片将被重置到窗口的左侧,并随机在窗口的垂直方向上移动。
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值