极验反爬虫防护分析之slide验证方式下图片的处理及滑动轨迹的生成思路

查看 158|回复 10
作者:datochan   
本文要分享的内容是去年为了抢鞋而分析 极验(GeeTest)反爬虫防护的笔记,由于篇幅较长(为了多混点CB)我会按照我的分析顺序,分成如下四个主题与大家分享:
[ol]
  • 极验反爬虫防护分析之交互流程分析
  • 极验反爬虫防护分析之接口交互的解密方法
  • 极验反爬虫防护分析之接口交互的解密方法补遗
  • 极验反爬虫防护分析之slide验证方式下图片的处理及滑动轨迹的生成思路
    [/ol]
    本文是第四篇, 也是最后一篇,网上大部分针对极验的绕过方法大都是模拟手工滑动滑块的方式,但是通过上面几篇文章的分析,我们是能知道Geetest已经对目前市面上大多自动化测试的工具进行了监测,包括 Selenium甚至electron等。所以基于这些工具的破解不是不行,只是人家官方没有严查,不长久的,稳妥之计还是要直接从封包入手。下面进入正文~
    背景图片乱序的还原
    如《极验反爬虫防护分析之交互流程分析》第五步的分析,得到的 bg和fullbg图片都是乱序处理后的图片,要判断滑动的距离及轨迹需要将图片进行还原。如下图:


    1.jpg (392.1 KB, 下载次数: 2)
    下载附件
    2020-4-22 17:29 上传

    还原后的代码为:
    function SEQUENCE() {
            var e = "6_11_7_10_4_12_3_1_0_5_2_9_8".split("_");
        for (var t, n = [], r = 0; r
    至此,我们知道它是通过两次折叠构建出来52个元素的散列表。通过固定的公式将图片上下、左右互换并根据散列表的值进行乱序。通过分析代码中的字符串常亮6_11_7_10_4_12_3_1_0_5_2_9_8是在slide.7.6.0.js文件中,一开始的方法中定义的:$_DAEAF = decodeURI('N-%60%13)nN-%60%1C%1...,decodeURI解码后的数组第911位就是此字符串常量, 如下图:


    2.jpg (310.06 KB, 下载次数: 1)
    下载附件
    2020-4-22 17:30 上传

    继续跟进绘图的代码:


    3.jpg (178.33 KB, 下载次数: 2)
    下载附件
    2020-4-22 17:32 上传

    将混淆的代码还原之后,如下:
    function $_GEN(t, e) {
        var $_CJDIX = $_AB.$_Ei()[4][26];
        for (; $_CJDIX !== $_AB.$_Ei()[8][24];) {
            switch ($_CJDIX) {
                case $_AB.$_Ei()[16][26]:
                    t = t[$_DEAo(65)], e = e[$_DDJm(65)];
                    var n = t["width"], r = t["height"], i = document[$_DDJm(27)]($_DEAo(91));
                    i["width"] = n, i["height"] = r;
                    var CanvasRenderingContext2D = i["getContext"]("2d");
                    $_CJDIX = $_AB.$_Ei()[8][25];
                    break;
                case $_AB.$_Ei()[8][25]:
                    CanvasRenderingContext2D["drawImage"](t, 0, 0);
                    var CanvasRenderingContext2D = e["getContext"]("2d");
                    e["height"] = r, e["width"] = WIDTH;
                    for (var a = r / 2, u = 0; u
    将以上JS编写为还原图片的Python代码如下:
    import numpy as np
    from PIL import Image
    import matplotlib.pyplot as plt
    def sequence():
        t = 0
        n = []
        e = "6_11_7_10_4_12_3_1_0_5_2_9_8".split("_")
        for r in range(0, 52):
            t = 2 * int(e[int(r%26/2)]) + r % 2
            if 0 == int(r/2)%2:
                t += -1 if (r%2) else 1
            t += 26 if (r
    找一个待处理的图片:https://static.geetest.com/pictures/gt/6edec3cc1/6edec3cc1.jpg,测试结果如下图:


    4.jpg (221.1 KB, 下载次数: 2)
    下载附件
    2020-4-22 17:35 上传

    至此,图片乱序还原的问题搞定。
    滑动轨迹的加密方法
    同样的方法跟踪滑块失败后的请求,分析回溯来到如下代码:
    "$_CHBV": function (t, e, n) {
        var $_CABJD = $_AB.$_Ds, $_CABIQ = ['$_CACCE'].concat($_CABJD), $_CACAM = $_CABIQ[1];
        $_CABIQ.shift();
        var $_CACBm = $_CABIQ[0];
        var r = this, i = r[$_CABJD(78)];
        var o = {
            "lang": i[$_CABJD(172)] || $_CACAM(161),       // 语言固定为 zh-hk || zh-cn
            "userresponse": $_CEI(t, i[$_CABJD(139)]),     // t=滑动的距离,用户响应的内容, $_CABJD(139) = "challenge" 的值
            "passtime": n,   // 滑块消耗的时间=鼠标轨迹每个点耗时相加
            "imgload": r[$_CABJD(744)],      
            "aa": e,   // 滑动轨迹的加密字符
            "ep": r[$_CABJD(764)]()
        };
        i[$_CABJD(118)] && (o[$_CABJD(221)] = t);
        o["rp"] = $_DCj(i[$_CACAM(159)] + i[$_CABJD(139)][$_CACAM(151)](0, 32) + o[$_CABJD(736)]);
        var s = r[$_CACAM(791)]();  // rsa加密的aes密钥
        var a = AES[$_CABJD(389)](gjson[$_CACAM(160)](o), r[$_CABJD(751)]()); // 将上面的json用aes加密
        var u = Base64[$_CACAM(739)](a), c = {
            "gt": i[$_CABJD(159)],
            "challenge": i[$_CACAM(139)],
            "lang": o[$_CABJD(172)],
            "pt": r[$_CACAM(686)],
            "w": u + s
        };
        ...
    }
    至此,我们找到了移动滑块后提交的参数w的来历。根据以往经验s是aes加密用到密钥,用rsa加密后的密文。重点分析u的来历。向上回溯可知u的组成,将代码还原为:
    /**
    * 用于计算 rp 的hash值
    */
    function $_DCj(t) {
            function u(t, e) {
                    return t >> 32 - e;
            }
            function c(t, e) {
                    var n, r, i, o, s;
                    return i = 2147483648 & t, o = 2147483648 & e, s = (1073741823 & t) + (1073741823 & e), (n = 1073741824 & t) & (r = 1073741824 & e) ? 2147483648 ^ s ^ i ^ o : n | r ? 1073741824 & s ? 3221225472 ^ s ^ i ^ o : 1073741824 ^ s ^ i ^ o : s ^ i ^ o;
            }
            function e(t, e, n, r, i, o, s) {
                    return c(u(t = c(t, c(c(function a(t, e, n) {
                            return t & e | ~t & n;
                    }(e, n, r), i), s)), o), e);
            }
            function n(t, e, n, r, i, o, s) {
                    return c(u(t = c(t, c(c(function a(t, e, n) {
                            return t & n | e & ~n;
                    }(e, n, r), i), s)), o), e);
            }
            function r(t, e, n, r, i, o, s) {
                    return c(u(t = c(t, c(c(function a(t, e, n) {
                            return t ^ e ^ n;
                    }(e, n, r), i), s)), o), e);
            }
            function i(t, e, n, r, i, o, s) {
                    return c(u(t = c(t, c(c(function a(t, e, n) {
                            return e ^ (t | ~n);
                    }(e, n, r), i), s)), o), e);
            }
            function o(t) {
                    var n = "", r = "";
                    for (var e = 0; e >> 8 * e & 255)["toString"](16))["substr"](r["length"] - 2, 2);
                    }
                    return n;
            }
            var s, a, _, l, f, h, d, p, g, m;
            for (s = function v(t) {
                    var e, n = t["length"], r = n + 8, i = 16 * (1 + (r - r % 64) / 64), o = Array(i - 1), s = 0, a = 0;
                    while (a >> 29, o;
            }(t = function w(t) {
                    t = t["replace"](/\r\n/g, "\n");
                    for (var e = "", n = 0; n > 6 | 192) : (e += String["fromCharCode"](r >> 12 | 224), e += String["fromCharCode"](r >> 6 & 63 | 128)), e += String["fromCharCode"](63 & r | 128));
                    }
                    return e;
            }(t)), d = 1732584193, p = 4023233417, g = 2562383102, m = 271733878, a = 0; a
    其中 SlideObject["$_CEAQ"]方法依赖于浏览器的windows环境,如下图:


    5.jpg (105.76 KB, 下载次数: 2)
    下载附件
    2020-4-22 17:47 上传

    继续跟进,发现"ep"值,是windows的Performance.timing中的值。


    6.jpg (330.97 KB, 下载次数: 1)
    下载附件
    2020-4-22 17:47 上传

    因此,可以根据 Performance.timing 时间产生的先后顺序以及时间间隔,用当前的时间戳减去相应的值来模拟。由于相关代码比较简单,为了节省篇幅就不给出了。
    滑动轨迹生成的思路
    由于极验采用人工智能的方式对滑动的轨迹进行的验证,因此如果我们比较随意的生成鼠标滑动轨迹基本是肯定被封的,因此我们要详细分析一下鼠标轨迹的规律,
    通之前介绍的调试手段,手工滑动滑块,获取到鼠标滑动轨迹的集合数组如下:

    [[-37,-41,0], [0,0,0], [3,0,251], [6,0,266], [9,0,283], [13,0,300], [15,0,316], [17,0,333], [19,0,349], [20,0,366], [20,0,383], [20,0,400], [20,0,430]]

    每个点的组成为:[x, y, timestamp],含义如下:
  • x 坐标: 从0开始,一直到滑动结束, 每个坐标间隔越大说明滑动越快,静止不动就不变。
  • y 坐标: 可以为正,也可以为负数,都是个位数。取值范围: [-2, 2), 多取值0,次之是-1,极少的-2和正1
  • timestamp: 鼠标在当前点停留的时间(毫秒)

    经反复测试得知还有如下规律:
  • 滑动轨迹第一个坐标点(X,Y)是负数,其取值范围在(-40, -18)
  • 第二个坐标点是0,0,0,从第三甚至第四个坐标点开始
  • y坐标的取值范围比较简单,人手横向滑动的轨迹一般负责先减少,在快速增加,再慢慢减少的轨迹。

    因为y的取值比较简单,只考虑x坐标与z坐标的关系,将手工调试取10个坐标,以时间为X坐标,滑动距离为Y坐标,打印出来绘图为:


    7.jpg (35.66 KB, 下载次数: 2)
    下载附件
    2020-4-22 17:49 上传

    图像的轨迹有点儿像 tanh 和 arctan的混合体,如下图:


    8.jpg (20.1 KB, 下载次数: 3)
    下载附件
    2020-4-22 17:51 上传

    我们将两个图像整合、移动并添加一些噪点,最终生成的图像为:


    9.jpg (18.75 KB, 下载次数: 2)
    下载附件
    2020-4-22 18:03 上传

    这样的图像很像我们之前采集的鼠标轨迹图像了。至此,鼠标滑动轨迹的X坐标生成方法就告一段落。剩下的是滑动时间的分配。
    对上面十次滑动的坐标集合,计算出每个坐标点消耗的时间,对时间进行汇总,如下表:
    [table]
    [tr]
    [td]序号[/td]
    [td]0-15(ms)[/td]
    [td]15-20(ms)[/td]
    [td]20-200(ms)[/td]
    [td]200-400(ms)[/td]
    [/tr]
    [tr]
    [td]1

    轨迹, 鼠标

  • allenluo   

    非常的优秀,感谢作者费更多的精力来一点点讲述整个破解思路。我也曾跟踪 js 以还原加密解密,不过基本是抠出 js 代码后直接在 python 中执行,更别说用 ptyhon 重构算法了。最后的滑动轨迹生成思路相当的棒,也需要一定的数学基础。
    苏晓宇c   

    过分优秀。
    zhengpengxin   

    优秀,大佬大佬
    pppjie   

    看着就比较难
    生有涯知无涯   

    这是真正的大佬
    夜饮   

    跟着大佬学习学习
    sifeng   

    膜拜大佬  大佬牛逼
    xiao智可以不帅   

    非常优秀
    q6378561   

    干货很充足 学习了。
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部