美文网首页
JS逆向: js 逆向方式破解某验滑块验证码

JS逆向: js 逆向方式破解某验滑块验证码

作者: dex0423 | 来源:发表于2021-12-23 11:31 被阅读0次

    1. 抓包分析

    打开某验的 demo,点出验证码图片,分析 network 中的请求以及参数变化
    请求与参数:

    1、  Request URL: https://www.geetest.com/demo/gt/register-slide?t=1640096834809
        返回: challenge: "5bd76b0b1c9388a667bba39af5cfd71e"
                gt: "019924a82c70bb123aae90d483087f94"
                
    2、  Request URL: https://apiv6.geetest.com/gettype.php?gt=019924a82c70bb123aae90d483087f94&callback=geetest_1640096837076
        返回: fullpage: "/static/js/fullpage.9.0.8.js"
                slide: "/static/js/slide.7.8.6.js"
                
    3、  Request URL: https://apiv6.geetest.com/get.php?gt=019924a82c70bb123aae90d483087f94&challenge=5bd76b0b1c9388a667bba39af5cfd71e&lang=zh-cn&pt=0&client_type=web&w=7(Pr5uTDzYQIrCym5Psn(W7fvv7K3ji2dFDhp...
        提交: gt: 019924a82c70bb123aae90d483087f94
                challenge: 5bd76b0b1c9388a667bba39af5cfd71e
                w: 7(Pr5uTDzYQIrCym5Psn(W7fvv7K3ji2dFDhpCxfIsWhd...
        返回: status: "success"
        
    4、  Request URL: https://api.geetest.com/ajax.php?gt=019924a82c70bb123aae90d483087f94&challenge=5bd76b0b1c9388a667bba39af5cfd71e&lang=zh-cn&pt=0&client_type=web&w=7ZMNp(76n(MlO6aTxrUCxNw(7Jj5kEFnmodPZj2Nid...
        提交: w: 7ZMNp(76n(MlO6aTxrUCxNw(7Jj5kEFnmodPZj2NidffUg3PXYPHS(65Up0jO2X...
                callback: geetest_1640096847867
    
    5、  Request URL: https://api.geetest.com/get.php?is_next=true&type=slide3&gt=019924a82c70bb123aae90d483087f94&challenge=5bd76b0b1c9388a667bba39af5cfd71e&lang=zh-cn&https=true&protocol=https%3A%2F%2F&offline=false&product=embed&api_server=api.geetest.com&isPC=true&autoReset=true&width=100%25&callback=geetest_1640096847383
        提交: gt: 019924a82c70bb123aae90d483087f94
                challenge: 5bd76b0b1c9388a667bba39af5cfd71e
        返回: fullbg: "pictures/gt/b9694f3e8/b9694f3e8.jpg"
                slice: "pictures/gt/b9694f3e8/slice/f35f84584.png"
                bg: "pictures/gt/b9694f3e8/bg/f35f84584.jpg"
    

    拖动滑块,完成验证,继续分析请求和参数。

    6、  Request URL: https://api.geetest.com/ajax.php?gt=019924a82c70bb123aae90d483087f94&challenge=5bd76b0b1c9388a667bba39af5cfd71eaj&lang=zh-cn&%24_BBF=0&client_type=web&w=(x5U5)0n0zt1KeeU1X7ZseyXl)fhHOh539)Bm2(6...
        提交: w: (x5U5)0n0zt1KeeU1X7ZseyXl)fhHOh539)Bm2(6bQrrmShGp9v27nhkmGV...
                gt: 019924a82c70bb123aae90d483087f94
                challenge: 5bd76b0b1c9388a667bba39af5cfd71eaj
                
        返回: message: "success"
                validate: "00c14c420b44ade8cd6d3ddd5c916010"
    

    需要明确的问题:
    1、还原底图(处理底图乱序问题);
    2、获取 w 值,生成轨迹;

    2. 还原底图

    • 通过分析页面元素,我们发现验证码是一个 canvas。


      1640148030(1).png
    • 我们打上 canvas 断点,然后刷新重新获取验证码。


      1640149950(1).png
      1640149950.png
    • 此时注意提示 https://static.geetest.com/pictures/gt/b9694f3e8/b9694f3e8.webp 打开以后就是乱序验证码, e = canvas.geetest_canvas_bg.geetest_absolute {width: 260, height: 160, title: '', toDataURL: ƒ, toBlob: ƒ, …} 的就是验证码的宽度和高度。
      同时,仔细观察我们发现,验证码混淆后分成了上下两部分。
      1640160643(1).png
    • 单步调试,集合验证码图片、分析代码;
      1640161775(1).png
      根据提示我们发现,有一段代码的提示是 l = ImageData {data: Uint8ClampedArray(3200), width: 10, height: 80, colorSpace: 'srgb'}, o = CanvasRenderingContext2D {canvas: canvas, globalAlpha: 1,推测 ImageData 是画图动作,进而推测验证码还原的关键步骤,在 l = o[$_CJET(69)](c, u, 10, a) 这一步。
      我们仔细分析一下这一段 for 循环代码。
                        for (var a = r / 2, _ = 0; _ < 52; _ += 1) {
                            var c = Ut[_] % 26 * 12 + 1
                              , u = 25 < Ut[_] ? a : 0
                              , l = o[$_CJET(69)](c, u, 10, a);
                            s[$_CJET(66)](l, _ % 26 * 10, 25 < _ ? a : 0);
                        }
    

    对上面的代码进一步分析,我们发现这里进行了 52 次循环,而代码中的 26 刚好是 52 的一半,这个与前面验证码被分成上下两部分呼应。
    我们推测验证码被分成了52个小块,上下两个部分各有26,每个小块是的宽度是 10,加在一起正好是 260,刚好是前面分析发现的整个验证码图片的宽度。

    1640162714(1).png
    仔细分析上面的代码,我们发现这个循环,会根据 Ut[_] 的变化而进行不同的操作。_ 是一个每次自增1且小于52的整数,那 Ut 是什么呢?
    我们在 console 中获取,发现他是一个52位的数组,[39, 38, 48, 49, 41, 40, 46, 47, 35, 34, 50, 51, 33, 32, 28, 29, 27, 26, 36, 37, 31, 30, 44, 45, 43, 42, 12, 13, 23, 22, 14, 15, 21, 20, 8, 9, 25, 24, 6, 7, 3, 2, 0, 1, 11, 10, 4, 5, 19, 18, 16, 17]
    这个时候,就可以断定这个数组就是验证码混淆的顺序,按照这个顺序反向操作就能还原验证码。而上面的 for 循环,实际就是在执行这个还原验证码的操作。

    注意
    这个数组可能是动态的,也可能是静态的,为了验证是静态还是动态,我们需要进行多次调试,对比该数组是否发生变化。这里我们通过分析,确认该数组是静态的。

    至此,底图还原的逆向工作完成。

    3. 获取 w 值 & 还原推动轨迹

    3.1. 获取 w 值

    w 值在这里曾经多次生成,这里只分析其中的一次。
    w 是一个数字单字符,这里通过直接搜索无法直接搜到。这里我们采用跟栈的方式一步步回溯。

    跟栈技巧

    跟栈的时候,注意我们的重点关注 w 值有没有在提示中出现,如果又出现就说明它在代码之前或者上一步,就已经被生成了。我们就继续往前找,一直找到提示中没有 w 值的时候,我们再分析 w 值到底是在哪里生成的。

    注意:在开始跟栈的时候,如果找不到 w 值,就往前多跟几步。

    image.png

    继续上面的思路往前跟栈,很快就会发现遇到一个平坦流,如下图:


    1640192953(1).png
    平坦流的跟栈技巧

    当遇到平坦流的时候,首先调试分析平坦流有没有对目标参数做修改,如果没有就不用关注控制流,直接跳过就好。
    很多时候,我们可以直接跳过平坦流,继续往前找,看看能不能找到 w 值,如果实在找不到再回来分析平坦流。这里其实可以先在函数内部、平坦流前打一个断点,然后刷新货重新请求、让浏览器在此处被断掉,继而进行跟栈分析。
    分析发现,w 值在 R 这层已经出现,那我们就没必要关注之前的平坦流,接着跟栈分析就可以。如下图:

    image.png
    我们接着进行跟栈分析,发现调试工具已经把 "w" 的生成逻辑都提示出来了。 image.png

    w 值的生成代码就是下面这行。

     "\u0077": h + u
    

    到这一步,就进入你想的关键步骤了。
    下面附上比较关键的一段加密代码,供大家参考。

            var rt = function() {
                var $_BFBDL = lTloj.$_CX
                  , $_BFBCi = ['$_BFBGO'].concat($_BFBDL)
                  , $_BFBEt = $_BFBCi[1];
                $_BFBCi.shift();
                var $_BFBFk = $_BFBCi[0];
                function t() {
                    var $_DBFAh = lTloj.$_DP()[0][4];
                    for (; $_DBFAh !== lTloj.$_DP()[2][3]; ) {
                        switch ($_DBFAh) {
                        case lTloj.$_DP()[0][4]:
                            return (65536 * (1 + Math[$_BFBDL(75)]()) | 0)[$_BFBDL(396)](16)[$_BFBDL(476)](1);
                            break;
                        }
                    }
                }
                return function() {
                    var $_BFBIl = lTloj.$_CX
                      , $_BFBHs = ['$_BFCBY'].concat($_BFBIl)
                      , $_BFBJq = $_BFBHs[1];
                    $_BFBHs.shift();
                    var $_BFCAQ = $_BFBHs[0];
                    return t() + t() + t() + t();
                }
                ;
            }();
    

    3.2. 轨迹还原

    当分析到前面代码处位置时,我们发现其中一个很重要的参数 o,这个 o 是一个对象,对象中其中包括了下面的内容。

    aa: "M/-821./0(!!Mty!)(!)!)!)!K)(!)ysttststsss(!!(f011/11x1111/19111o6,S/$)ZC"
    ep: {v: '7.8.6', $_BHR: false, me: true, tm: {…}, td: -1}
    imgload: 813
    lang: "zh-cn"
    passtime: 337
    rp: "a110d6f451e2b042883f4ae191a89272"
    userresponse: "99e999e71"
    xb3y: "1140037174"
    [[Prototype]]: Object
    

    通过多次调试,我们发现这里的 aarp 值,会随着每次拖动滑块验证码而变化,我们猜测这里可能是滑动的轨迹。通过想上查找和跟栈,我们找到了这个地方:

    1640248989(1).png
    l = n[$_CJJIW(1078)][$_CJJJd(1069)](n[$_CJJIW(1078)][$_CJJJd(1051)](), n[$_CJJJd(13)][$_CJJIW(1045)], n[$_CJJIW(13)][$_CJJIW(307)])
    

    通过还原,得到以下代码:

    l = n['$_CIBw']['$_BBCA'](n['$_CIBw']['$_GEy'](), n['$_CIY']['c'], n['$_CIY']['s'])
    

    通过 console 控制台,我们分析得到下面的内容:

    n['$_CIBw']['$_GEy']() :   'T,0./53/220-,(!!Mty*)))*)))(y((t)tssssswssssswsssssvvsssswsss(!!($)39/112011100120112.9191/:8Fp/01Ei:-:901C3/_$)J$*r'
    n['$_CIY']['c']        :   [12, 58, 98, 36, 43, 95, 62, 15, 12]
    n['$_CIY']['s']        :   '6f404c79'
    

    那上面的数据都是从哪里拿到的呢?我们将 n['$_CIY'] 还原,得到下面的内容:

    1640249913(1).png
    通过分析,可以知道 n['$_CIY']['c']n['$_CIY']['s'] 是前面通过请求返回的,可以直接拿来使用。
    这里比较重要的是 n['$_CIBw']['$_GEy'](),我们进入函数找到下面这段代码。
    "\u0024\u005f\u0047\u0045\u0079": function() {
                    var $_BEGIH = lTloj.$_CX
                      , $_BEGHL = ['$_BEHBT'].concat($_BEGIH)
                      , $_BEGJJ = $_BEGHL[1];
                    $_BEGHL.shift();
                    var $_BEHAr = $_BEGHL[0];
                    function n(t) {
                        var $_DBEIz = lTloj.$_DP()[0][4];
                        for (; $_DBEIz !== lTloj.$_DP()[2][3]; ) {
                            switch ($_DBEIz) {
                            case lTloj.$_DP()[0][4]:
                                var e = $_BEGJJ(430)
                                  , n = e[$_BEGJJ(182)]
                                  , r = $_BEGIH(33)
                                  , i = Math[$_BEGIH(383)](t)
                                  , o = parseInt(i / n);
                                n <= o && (o = n - 1),
                                o && (r = e[$_BEGIH(122)](o));
                                var s = $_BEGIH(33);
                                return t < 0 && (s += $_BEGJJ(456)),
                                r && (s += $_BEGJJ(459)),
                                s + r + e[$_BEGIH(122)](i %= n);
                                break;
                            }
                        }
                    }
                    var t = function(t) {
                        var $_BEHDi = lTloj.$_CX
                          , $_BEHCK = ['$_BEHGM'].concat($_BEHDi)
                          , $_BEHEF = $_BEHCK[1];
                        $_BEHCK.shift();
                        var $_BEHFx = $_BEHCK[0];
                        for (var e, n, r, i = [], o = 0, s = 0, a = t[$_BEHEF(182)] - 1; s < a; s++)
                            e = Math[$_BEHEF(156)](t[s + 1][0] - t[s][0]),
                            n = Math[$_BEHDi(156)](t[s + 1][1] - t[s][1]),
                            r = Math[$_BEHDi(156)](t[s + 1][2] - t[s][2]),
                            0 == e && 0 == n && 0 == r || (0 == e && 0 == n ? o += r : (i[$_BEHEF(140)]([e, n, r + o]),
                            o = 0));
                        return 0 !== o && i[$_BEHDi(140)]([e, n, o]),
                        i;
                    }(this[$_BEGJJ(361)])
                      , r = []
                      , i = []
                      , o = [];
                    return new ct(t)[$_BEGIH(84)](function(t) {
                        var $_BEHIs = lTloj.$_CX
                          , $_BEHHw = ['$_BEIBE'].concat($_BEHIs)
                          , $_BEHJy = $_BEHHw[1];
                        $_BEHHw.shift();
                        var $_BEIAO = $_BEHHw[0];
                        var e = function(t) {
                            var $_BEIDv = lTloj.$_CX
                              , $_BEICk = ['$_BEIGW'].concat($_BEIDv)
                              , $_BEIEU = $_BEICk[1];
                            $_BEICk.shift();
                            var $_BEIFh = $_BEICk[0];
                            for (var e = [[1, 0], [2, 0], [1, -1], [1, 1], [0, 1], [0, -1], [3, 0], [2, -1], [2, 1]], n = 0, r = e[$_BEIDv(182)]; n < r; n++)
                                if (t[0] == e[n][0] && t[1] == e[n][1])
                                    return $_BEIEU(413)[n];
                            return 0;
                        }(t);
                        e ? i[$_BEHIs(140)](e) : (r[$_BEHJy(140)](n(t[0])),
                        i[$_BEHJy(140)](n(t[1]))),
                        o[$_BEHJy(140)](n(t[2]));
                    }),
                    r[$_BEGJJ(444)]($_BEGIH(33)) + $_BEGIH(407) + i[$_BEGIH(444)]($_BEGJJ(33)) + $_BEGIH(407) + o[$_BEGIH(444)]($_BEGIH(33));
                }
    

    其中最后一行代码,经过 console 还原,可以还原呈现面的代码:

    r['join']('') + '!!' + i['join']('') + '!!' + o['join']('')
    

    这里的 rio 是三个数组,加密的核心逻辑,就是生成这三个数组。而这三个数组的生成过程,其实也就是对滑动轨迹的处理过程。那轨迹是什么呢?
    通过分析 return new ct(t)[$_BEGIH(84)] 我们发现,整个处理过程都是围绕 t 展开的。

    1640261395(1).png
    t 又是什么东西呢?调试工具已经提示给我们了,它是一个数组,分析数组内容我们推测,这就是滑块运行轨迹,数组中的每个小数组有三个值。
    经验判断三个值分别是:横向滑动距离、纵向滑动距离、以及滑动时间。
    0: (3) [35, 23, 0]
    1: (3) [1, 0, 83]
    2: (3) [1, 0, 8]
    3: (3) [2, 1, 8]
    4: (3) [2, 0, 8]
    5: (3) [2, 0, 6]
    6: (3) [2, 0, 8]
    7: (3) [2, 0, 8]
    8: (3) [2, 0, 8]
    9: (3) [0, 1, 8]
    10: (3) [1, 0, 8]
    11: (3) [1, 1, 22]
    12: (3) [1, 0, 8]
    13: (3) [1, 0, 16]
    14: (3) [0, 1, 8]
    15: (3) [1, 0, 8]
    16: (3) [2, 0, 8]
    17: (3) [1, 0, 6]
    18: (3) [1, 0, 8]
    19: (3) [2, 0, 10]
    20: (3) [1, 0, 6]
    21: (3) [1, 0, 24]
    22: (3) [1, 0, 8]
    23: (3) [1, 0, 14]
    24: (3) [1, 0, 17]
    25: (3) [1, 0, 23]
    26: (3) [1, 0, 11]
    27: (3) [1, 0, 19]
    28: (3) [1, 0, 16]
    29: (3) [1, 0, 8]
    30: (3) [1, 0, 7]
    31: (3) [1, 0, 8]
    32: (3) [2, 0, 8]
    33: (3) [2, 0, 15]
    34: (3) [1, 0, 24]
    35: (3) [1, 0, 22]
    36: (3) [1, 0, 16]
    37: (3) [1, 0, 8]
    38: (3) [1, 0, 8]
    39: (3) [0, 0, 116]
    length: 40
    [[Prototype]]: Array(0)
    

    这里需要注意的是,t 的值在经过 ct(t)[$_BEGIH(84)] 函数处理时,会发生变化,如果忽略了这一点可能会导致轨迹加密后,依然无法成功请求。
    到这里关键的逆向工作就差不多完成了,不过这里要注意加密的入口是 l = n[$_CJJIW(1078)][$_CJJJd(1069)](n[$_CJJIW(1078)][$_CJJJd(1051)](), n[$_CJJJd(13)][$_CJJIW(1045)], n[$_CJJIW(13)][$_CJJIW(307)]),我们前面的逆向完成的是传入的三个参数,外面的函数也需抠出来。我们下断追进去,发现下面这段代码,直接拿过来用就可以。

    "\u0024\u005f\u0042\u0042\u0043\u0041": function(t, e, n) {
                    var $_BEIIp = lTloj.$_CX
                      , $_BEIHl = ['$_BEJBo'].concat($_BEIIp)
                      , $_BEIJO = $_BEIHl[1];
                    $_BEIHl.shift();
                    var $_BEJAQ = $_BEIHl[0];
                    if (!e || !n)
                        return t;
                    var r, i = 0, o = t, s = e[0], a = e[2], _ = e[4];
                    while (r = n[$_BEIIp(373)](i, 2)) {
                        i += 2;
                        var c = parseInt(r, 16)
                          , u = String[$_BEIIp(206)](c)
                          , l = (s * c * c + a * c + _) % t[$_BEIIp(182)];
                        o = o[$_BEIIp(373)](0, l) + u + o[$_BEIJO(373)](l);
                    }
                    return o;
                }
    

    相关文章

      网友评论

          本文标题:JS逆向: js 逆向方式破解某验滑块验证码

          本文链接:https://www.haomeiwen.com/subject/xhjfqrtx.html