1. 程式人生 > >極驗反爬蟲防護分析之slide驗證方式下圖片的處理及滑動軌跡的生成思路

極驗反爬蟲防護分析之slide驗證方式下圖片的處理及滑動軌跡的生成思路

本文要分享的內容是去年為了搶鞋而分析 極驗(GeeTest)反爬蟲防護的筆記,由於篇幅較長(為了多混點CB)我會按照我的分析順序,分成如下四個主題與大家分享:

  1. 極驗反爬蟲防護分析之互動流程分析
  2. 極驗反爬蟲防護分析之介面互動的解密方法
  3. 極驗反爬蟲防護分析之介面互動的解密方法補遺
  4. 極驗反爬蟲防護分析之slide驗證方式下圖片的處理及滑動軌跡的生成思路

 

本文是第四篇, 也是最後一篇,網上大部分針對極驗的繞過方法大都是模擬手工滑動滑塊的方式,但是通過上面幾篇文章的分析,我們是能知道Geetest已經對目前市面上大多自動化測試的工具進行了監測,包括 Selenium甚至electron等。所以基於這些工具的破解不是不行,只是人家官方沒有嚴查,不長久的,穩妥之計還是要直接從封包入手。下面進入正文~


背景圖片亂序的還原

如《極驗反爬蟲防護分析之互動流程分析》第五步的分析,得到的 bgfullbg圖片都是亂序處理後的圖片,要判斷滑動的距離及軌跡需要將圖片進行還原。如下圖:

 

 還原後的程式碼為:

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; r++) {
            t = 2 * parseInt(e[parseInt(r % 26 / 2)]) + r % 2;
            parseInt(r / 2) % 2 || (t += r % 2 ? -1 : 1);
            t += r < 26 ? 26 : 0;
            n["push"](t);
    }
    return n;
}

var result = SEQUENCE();
console.log(result.join(", "));

至此,我們知道它是通過兩次摺疊構建出來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位就是此字串常量, 如下圖:

 

 繼續跟進繪圖的程式碼:

 

 將混淆的程式碼還原之後,如下:

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 < 52; u += 1) {
                    var c = SEQUENCE % 26 * 12 + 1, _ = 25 < SEQUENCE ? a : 0,
                        l = CanvasRenderingContext2D["getImageData"](c, _, 10, a);
                    CanvasRenderingContext2D["putImageData"](l, u % 26 * 10, 25 < u ? a : 0);
                }
                $_CJDIX = $_AB.$_Ei()[0][24];
                break;
        }
    }
}

將以上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<26) else 0
        n.append(t)

    return n

def gen(_seq, _img):
    """
    用於將圖片還原
    @param _seq: 圖片的序列號,也就是 Sequence 方法生成的結果
    @param _img: 圖片
    @return new img
    """
    r = 160
    a = int(r / 2)
    np_image = np.array(img)
    new_np_img = np.zeros((160, 312, 3), dtype=np.uint8)

    for u in range(0, 52):
        c = _seq % 26 * 12 + 1
        _ = int(a if (25 < _seq) else 0)

        xpos = u % 26 * 10
        ypos = a if (25 < u) else 0

        # var l = getImageData(c, _, 10, a);
        # putImageData(l, u % 26 * 10, 25 < u ? a : 0);
        slice_img = np_image[_:(_+a), c:(c+10)]
        n = len(slice_img[0])
        new_np_img[ypos:(ypos+a), xpos:(xpos+n)] = slice_img

    return new_np_img

if __name__ == "__main__":
    seq = sequence()
    img = Image.open('/Users/datochan/WorkSpace/VSCProjects/nike-bot/Test/src/images/fullbg.jpg')
    newimg = gen(seq, img)

    plt.imshow(newimg)
    plt.show()

找一個待處理的圖片:https://static.geetest.com/pictures/gt/6edec3cc1/6edec3cc1.jpg,測試結果如下圖:

 

 

至此,圖片亂序還原的問題搞定。

滑動軌跡的加密方法

同樣的方法跟蹤滑塊失敗後的請求,分析回溯來到如下程式碼:

"$_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 << e | 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 <= 3; e++) {
                        n += (r = "0" + (t >>> 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 < n) s = a % 4 * 8, o[e = (a - a % 4) / 4] = o[e] | t["charCodeAt"](a) << s, a++;
                return s = a % 4 * 8, o[e = (a - a % 4) / 4] = o[e] | 128 << s, o[i - 2] = n << 3, o[i - 1] = n >>> 29, o;
        }(t = function w(t) {
                t = t["replace"](/\r\n/g, "\n");
                for (var e = "", n = 0; n < t["length"]; n++) {
                        var r = t["charCodeAt"](n);
                        r < 128 ? e += String["fromCharCode"](r) : (127 < r && r < 2048 ? e += String["fromCharCode"](r >> 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 < s["length"]; a += 16) p = i(p = i(p = i(p = i(p = r(p = r(p = r(p = r(p = n(p = n(p = n(p = n(p = e(p = e(p = e(p = e(l = p, g = e(f = g, m = e(h = m, d = e(_ = d, p, g, m, s[a + 0], 7, 3614090360), p, g, s[a + 1], 12, 3905402710), d, p, s[a + 2], 17, 606105819), m, d, s[a + 3], 22, 3250441966), g = e(g, m = e(m, d = e(d, p, g, m, s[a + 4], 7, 4118548399), p, g, s[a + 5], 12, 1200080426), d, p, s[a + 6], 17, 2821735955), m, d, s[a + 7], 22, 4249261313), g = e(g, m = e(m, d = e(d, p, g, m, s[a + 8], 7, 1770035416), p, g, s[a + 9], 12, 2336552879), d, p, s[a + 10], 17, 4294925233), m, d, s[a + 11], 22, 2304563134), g = e(g, m = e(m, d = e(d, p, g, m, s[a + 12], 7, 1804603682), p, g, s[a + 13], 12, 4254626195), d, p, s[a + 14], 17, 2792965006), m, d, s[a + 15], 22, 1236535329), g = n(g, m = n(m, d = n(d, p, g, m, s[a + 1], 5, 4129170786), p, g, s[a + 6], 9, 3225465664), d, p, s[a + 11], 14, 643717713), m, d, s[a + 0], 20, 3921069994), g = n(g, m = n(m, d = n(d, p, g, m, s[a + 5], 5, 3593408605), p, g, s[a + 10], 9, 38016083), d, p, s[a + 15], 14, 3634488961), m, d, s[a + 4], 20, 3889429448), g = n(g, m = n(m, d = n(d, p, g, m, s[a + 9], 5, 568446438), p, g, s[a + 14], 9, 3275163606), d, p, s[a + 3], 14, 4107603335), m, d, s[a + 8], 20, 1163531501), g = n(g, m = n(m, d = n(d, p, g, m, s[a + 13], 5, 2850285829), p, g, s[a + 2], 9, 4243563512), d, p, s[a + 7], 14, 1735328473), m, d, s[a + 12], 20, 2368359562), g = r(g, m = r(m, d = r(d, p, g, m, s[a + 5], 4, 4294588738), p, g, s[a + 8], 11, 2272392833), d, p, s[a + 11], 16, 1839030562), m, d, s[a + 14], 23, 4259657740), g = r(g, m = r(m, d = r(d, p, g, m, s[a + 1], 4, 2763975236), p, g, s[a + 4], 11, 1272893353), d, p, s[a + 7], 16, 4139469664), m, d, s[a + 10], 23, 3200236656), g = r(g, m = r(m, d = r(d, p, g, m, s[a + 13], 4, 681279174), p, g, s[a + 0], 11, 3936430074), d, p, s[a + 3], 16, 3572445317), m, d, s[a + 6], 23, 76029189), g = r(g, m = r(m, d = r(d, p, g, m, s[a + 9], 4, 3654602809), p, g, s[a + 12], 11, 3873151461), d, p, s[a + 15], 16, 530742520), m, d, s[a + 2], 23, 3299628645), g = i(g, m = i(m, d = i(d, p, g, m, s[a + 0], 6, 4096336452), p, g, s[a + 7], 10, 1126891415), d, p, s[a + 14], 15, 2878612391), m, d, s[a + 5], 21, 4237533241), g = i(g, m = i(m, d = i(d, p, g, m, s[a + 12], 6, 1700485571), p, g, s[a + 3], 10, 2399980690), d, p, s[a + 10], 15, 4293915773), m, d, s[a + 1], 21, 2240044497), g = i(g, m = i(m, d = i(d, p, g, m, s[a + 8], 6, 1873313359), p, g, s[a + 15], 10, 4264355552), d, p, s[a + 6], 15, 2734768916), m, d, s[a + 13], 21, 1309151649), g = i(g, m = i(m, d = i(d, p, g, m, s[a + 4], 6, 4149444226), p, g, s[a + 11], 10, 3174756917), d, p, s[a + 2], 15, 718787259), m, d, s[a + 9], 21, 3951481745), d = c(d, _), p = c(p, l), g = c(g, f), m = c(m, h);
        return (o(d) + o(p) + o(g) + o(m))["toLowerCase"]();
}

// console.log($_DCj("2328764cdf162e8e60cc0b04383fef81" + "4de7bb253d3999b2dca4d35049959acf7k"))
var SlideObject = {
        "$_CEAQ": function() {
                // PerformanceTiming
                var t = window["Performance"]["timing"];
                return {
                        "a": t["navigationStart"],
                        "b": t["unloadEventStart"],
                        "c": t["unloadEventEnd"],
                        "d": t["redirectStart"],
                        "e": t["redirectEnd"],
                        "f": t["fetchStart"],
                        "g": t["domainLookupStart"],
                        "h": t["domainLookupEnd"],
                        "i": t["connectStart"],
                        "j": t["connectEnd"],
                        "k": t["secureConnectionStart"],
                        "l": t["requestStart"],
                        "m": t["responseStart"],
                        "n": t["responseEnd"],
                        "o": t["domLoading"],
                        "p": t["domInteractive"],
                        "q": t["domContentLoadedEventStart"],
                        "r": t["domContentLoadedEventEnd"],
                        "s": t["domComplete"],
                        "t": t["loadEventStart"],
                        "u": t["loadEventEnd"]
                };
        },

        "$_CHCg": function () {
                return {
                        "v": "7.6.0",  // slide.js的版本號
                        "f": $_DCj(this["gt"] + this["challenge"]) || "",
                        "te": false,  // touchEvent, PC端只有滑鼠事件,沒有觸控事件
                        "me": true,   // mouseEvent
                        "tm": this["$_CEAQ"]()
                };
        },
        /**
         * 用來處理 userresponse 引數
         * t: 滑動的距離
         * e: Challenge's value
         */
        "$_CEI": function (t, e) {
            for (var n = e["slice"](32), r = [], i = 0; i < n["length"]; i++) {
                var o = n["charCodeAt"](i);
                r[i] = 57 < o ? o - 87 : o - 48;
            }

            n = 36 * r[0] + r[1];
            var s, a = Math["round"](t) + n;
            var u = [[], [], [], [], []], c = {}, _ = 0;

            i = 0;
            for (var l = (e = e["slice"](0, 32))[ "length"]; i < l; i++) {
                c[s = e["charAt"](i)] || (c[s] = 1, u[_]["push"](s), _ = 5 == ++_ ? 0 : _);
            }

            var f, h = a, d = 4, p = "", g = [1, 2, 5, 10, 50];

            while (0 < h) {
                if (0 <= h - g[d]) {
                    f = parseInt(Math["random"]() * u[d]["length"], 10);
                    p += u[d][f];
                    h -= g[d];
                } else {
                    u["splice"](d, 1);
                    g["splice"](d, 1);
                    d -= 1;
                }
            }

            return p;
        },
        /**
         * 
         * @param {*} e 軌跡陣列
         * @param {*} t 用於處理滑動軌跡陣列每個元素的回撥方法
         */
        "$_HBM": function (e, t) {
            var n = [], i = e["length"];

            if (e["map"]) {
                        e["map"](t);
                        return;
                }

            for (var r = 0; r < i; r += 1) {
                    n[r] = t(e[r], r);
                }
        },

        /**
         * _pt_array: 滑動的軌跡陣列
         */
        "$_BAFz": function (_pt_array) {
                function n(t) {
                    var s = "";

                        var e = "()*,-./0123456789:?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqr";
                    var n = e["length"], r = "", i = Math["abs"](t), o = parseInt(i / n);

                    n <= o && (o = n - 1), o && (r = e["charAt"](o));
                    return t < 0 && (s += "!"), r && (s += "$"), s + r + e["charAt"](i %= n);
                }

            var t = function (t) {
                    var e, n, r, i = [], o = 0, a = t["length"] - 1;

                    for (var s = 0; s < a; s++) {
                            e = Math["round"](t[s + 1][0] - t[s][0]);
                            n = Math["round"](t[s + 1][1] - t[s][1]);
                            r = Math["round"](t[s + 1][2] - t[s][2]);
                            0 == e && 0 == n && 0 == r || (0 == e && 0 == n ? o += r : (i["push"]([e, n, r + o]), o = 0));

                    }

                    return 0 !== o && i["push"]([e, n, o]), i;
                }(_pt_array);    // 滑動的軌跡陣列

                var r = [], i = [], o = [];

                this["$_HBM"](t, function (t) {
                var e = function (t) {
                            var e = [[1, 0], [2, 0], [1, -1], [1, 1], [0, 1], [0, -1], [3, 0], [2, -1], [2, 1]];
                            for (var n = 0; n < e["length"]; n++) {
                                    if (t[0] == e[n][0] && t[1] == e[n][1]) 
                                            return "stuvwxyz~"[n];
                            }
                            return 0;
                        }(t);
                e ? i["push"](e) : (r["push"](n(t[0])), i["push"](n(t[1]))), o["push"](n(t[2]));
            });

            return r["join"]("") + "!!" + i["join"]("") + "!!" + o["join"]("");
        },

        /**
         * t: $_BAFz的返回值
         * e: 介面返回值中C的值: [12, 58, 98, 36, 43, 95, 62, 15, 12]
         * n: 介面返回的s值
         */
        "$_BGDl": function(t, e, n){
                if (!e || !n) return t;
                var r, i = 0, o = t, s = e[0], a = e[2], u = e[4];
                while (r = n["substr"](i, 2)) {
                        i += 2;
                        var c = parseInt(r, 16);
                        var _ = String["fromCharCode"](c);
                        var l = (s * c * c + a * c + u) % t["length"];
                        o = o["substr"](0, l) + _ + o["substr"](l);
                }
                return o;
        },

        /**
         * t: 滑動的距離: 滑鼠軌跡最後一個座標點的X值
         * e: 加密後的滑鼠軌跡($_BGDl的返回值)
         * n: 使用者滑動的耗時=滑鼠軌跡每個點耗時相加
         */
        "$_CHBV": function(t, e, n) {
                var r = this, i = r[$_CABJD(78)]; // i是get.php返回的集合
                var o = {
                        "lang": "zh_hk" || "zh_cn",
                        "userresponse": $_CEI(t, i["challenge"]),  // t=滑鼠軌跡最後一個座標點的X值
                        "passtime": n,  // 滑塊消耗的時間=滑鼠軌跡每個點耗時相加
                        "imgload": r[$_CABJD(744)],    // 圖片載入需要的時間
                        "aa": e,   // 滑塊軌跡加密後的字符集
                        "ep": this["$_CHCg"]()    // 基礎環境資訊收集
                };

                o["rp"] = $_DCj(i["gt"] + i["challenge"]["slice"](0, 32) + o["passtime"]);  // 會話資訊的hash加密
                var s = rsa_encrypt(), a = AES_encrypt(gjson(o), aes_key), u = Base64_encrypt(a);
                var c = {
                        "gt": i[$_CABJD(159)],
                        "challenge": i[$_CACAM(139)],
                        "lang": "zh_hk",
                        "pt": r[$_CACAM(686)],
                        "w": u + s
                }

                // TODO: 提交post請求
        }
}

// 呼叫示例
var pt_list = [[-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]];

var _ = SlideObject["$_BGDl"](SlideObject["$_BAFz"](pt_list), [12, 58, 98, 36, 43, 95, 62, 15, 12], "35304332")
console.log(_);

其中 SlideObject["$_CEAQ"]方法依賴於瀏覽器的windows環境,如下圖:

 

 繼續跟進,發現"ep"值,是windows的Performance.timing中的值。

 

 

因此,可以根據 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座標,打印出來繪圖為:

 

 影象的軌跡有點兒像 tanh 和 arctan的混合體,如下圖:

 

 我們將兩個影象整合、移動並新增一些噪點,最終生成的影象為:

 

 

這樣的影象很像我們之前採集的滑鼠軌跡影象了。至此,滑鼠滑動軌跡的X座標生成方法就告一段落。剩下的是滑動時間的分配。

對上面十次滑動的座標集合,計算出每個座標點消耗的時間,對時間進行彙總,如下表:

 

 

統計分析結果如下:

  1. 80%~90%的X座標在15~20毫秒之間
  2. 10%~15%在20~200及以上,其中 [-a, 0, x, ...] 這裡x只有一個,取值在110~200之間座標集最後3~5個座標取值再50~400之間,最後一個座標數值最大

至此,整理座標生成的思路與流程如下:

  1. 整個滑塊滑動時間分為三個部分,並根據統計的時間佔比分配Z座標:
    a. 滑動啟動階段,時間佔總時間分配的1%~3%
    b. 快速滑動只目標區域階段,時間佔比位75%~90%
    c. 調整階段,剩下的時間為調整階段。
  2. 按照等分時間塊的方式,通過上面tanh 和 arctan整合的軌跡影象生成X座標和Y座標。

最後,整理出相關示例程式碼如下:

"""
用於生成座標軌跡
"""
import math
import random
import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl

class GTrace(object):
    def __init__(self):
        self.__pos_x = []
        self.__pos_y = []
        self.__pos_z = []

    def __set_pt_time(self):
        """
        設定各節點的時間
        分析不同時間間隔中X座標數量的佔比
        統計結果: 1. 80%~90%的X座標在15~20毫秒之間
                2. 10%~15%在20~200及以上,其中 [-a, 0, x, ...] 這裡x只有一個,取值在110~200之間
                    座標集最後3~5個座標取值再50~400之間,最後一個座標數值最大

        滑動總時間的取值規則: 圖片寬度260,去掉滑塊的寬度剩下200; 
                        如果距離小於100,則耗時1300~1900之間
                        如果距離大於100,則耗時1700~2100之間
        """
        __end_pt_time = []
        __move_pt_time = []
        self.__pos_z = []

        total_move_time = self.__need_time * random.uniform(0.8, 0.9)
        start_point_time = random.uniform(110, 200)
        __start_pt_time = [0, 0, int(start_point_time)]

        sum_move_time = 0

        _tmp_total_move_time = total_move_time
        while True:
            delta_time = random.uniform(15, 20)
            if _tmp_total_move_time < delta_time:
                break

            sum_move_time += delta_time
            _tmp_total_move_time -= delta_time
            __move_pt_time.append(int(start_point_time+sum_move_time))

        last_pt_time = __move_pt_time[-1]
        __move_pt_time.append(last_pt_time+_tmp_total_move_time)

        sum_end_time = start_point_time + total_move_time
        other_point_time = self.__need_time - sum_end_time
        end_first_ptime = other_point_time / 2

        while True:
            delta_time = random.uniform(110, 200)
            if end_first_ptime - delta_time <= 0:
                break

            end_first_ptime -= delta_time
            sum_end_time += delta_time
            __end_pt_time.append(int(sum_end_time))

        __end_pt_time.append(int(sum_end_time + (other_point_time/2 + end_first_ptime)))
        self.__pos_z.extend(__start_pt_time)
        self.__pos_z.extend(__move_pt_time)
        self.__pos_z.extend(__end_pt_time)

    def __set_distance(self, _dist):
        """
        設定要生成的軌跡長度
        """
        self.__distance = _dist

        if _dist < 100:
            self.__need_time = int(random.uniform(500, 1500))
        else:
            self.__need_time = int(random.uniform(1000, 2000))

    def __get_pos_z(self):
        return self.__pos_z

    def __get_pos_y(self):
        _pos_y = [random.uniform(-40, -18), 0]
        point_count = len(self.__pos_z)
        x = np.linspace(-10, 15, point_count - len(_pos_y))
        arct_y = np.arctan(x)

        for _, val in enumerate(arct_y):
            _pos_y.append(val)

        return _pos_y

    def __get_pos_x(self, _distance):
        """
        繪製標準的數學函式影象: 以 tanh 開始 以 arctan 結尾
        根據此模型用等比時間差生成X座標
        """
        # first_val = random.uniform(-40, -18)
        # _distance += first_val
        _pos_x = [random.uniform(-40, -18), 0]
        self.__set_distance(_distance)
        self.__set_pt_time()

        point_count = len(self.__pos_z)
        x = np.linspace(-1, 19, point_count-len(_pos_x))
        ss = np.arctan(x)
        th = np.tanh(x)

        for idx in range(0, len(th)):
            if th[idx] < ss[idx]:
                th[idx] = ss[idx]

        th += 1
        th *= (_distance / 2.5)

        i = 0
        start_idx = int(point_count/10)
        end_idx = int(point_count/50)
        delta_pt = abs(np.random.normal(scale=1.1, size=point_count-start_idx-end_idx))
        for idx in range(start_idx, point_count):
            if idx*1.3 > len(delta_pt):
                break

            th[idx] += delta_pt[i]
            i+=1

        _pos_x.extend(th)
        return _pos_x[-1], _pos_x

    def get_mouse_pos_path(self, distance):
        """
        獲取滑動滑塊滑鼠的滑動軌跡座標集合
        """
        result = []
        _distance, x = self.__get_pos_x(distance)
        y = self.__get_pos_y()
        z = self.__get_pos_z()

        for idx in range(len(x)):
            result.append([int(x[idx]), int(y[idx]), int(z[idx])])

        return int(_distance), result

if __name__ == "__main__":
    _color = ["blue", "green", "red", "cyan", "magenta"]
    trace = GTrace()

    # for idx in range(0, 10):
    # distance = random.uniform(70, 150)
    # print("長度為: %d , 座標為: \n" % distance)
    # distance, mouse_pos_path = trace.get_mouse_pos_path(distance)
    # print("長度為: %d , 座標為: \" % distance, mouse_pos_path)

至此,要繞過極驗的Slide驗證方式所涉及到的所有技術細節都已分享完畢。最後我將生成得到的滑鼠滑動軌跡座標根據之前的文章分析的加密方法進行加密,封裝Http請求介面並編寫測試指令碼測試一下通過率,如下圖:

 

 80%左右,足夠我們使用了。

全文完!

 

轉載:https://www.52pojie.cn/thread-1162979-1-1.html