datochan 发表于 2020-4-22 18:13

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

本帖最后由 besterChen 于 2020-4-22 18:40 编辑

本文要分享的内容是去年为了抢鞋而分析 极验(GeeTest)反爬虫防护的笔记,由于篇幅较长(为了多混点CB)我会按照我的分析顺序,分成如下四个主题与大家分享:

1. [极验反爬虫防护分析之交互流程分析](https://www.52pojie.cn/thread-1162853-1-1.html)
2. [极验反爬虫防护分析之接口交互的解密方法](https://www.52pojie.cn/thread-1162893-1-1.html)
3. [极验反爬虫防护分析之接口交互的解密方法补遗](https://www.52pojie.cn/thread-1162951-1-1.html)
4. [极验反爬虫防护分析之slide验证方式下图片的处理及滑动轨迹的生成思路](https://www.52pojie.cn/thread-1162979-1-1.html)

本文是第四篇, 也是最后一篇,网上大部分针对极验的绕过方法大都是模拟手工滑动滑块的方式,但是通过上面几篇文章的分析,我们是能知道Geetest已经对目前市面上大多自动化测试的工具进行了监测,包括 Selenium甚至electron等。所以基于这些工具的破解不是不行,只是人家官方没有严查,不长久的,稳妥之计还是要直接从封包入手。下面进入正文~

---

## 背景图片乱序的还原

如《[极验反爬虫防护分析之交互流程分析](https://www.52pojie.cn/thread-1162853-1-1.html)》第五步的分析,得到的 `bg`和`fullbg`图片都是乱序处理后的图片,要判断滑动的距离及轨迹需要将图片进行还原。如下图:



还原后的代码为:
```javascript
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) + 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位就是此字符串常量, 如下图:



继续跟进绘图的代码:




将混淆的代码还原之后,如下:
```javascript
function $_GEN(t, e) {
    var $_CJDIX = $_AB.$_Ei();
    for (; $_CJDIX !== $_AB.$_Ei();) {
      switch ($_CJDIX) {
            case $_AB.$_Ei():
                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();
                break;
            case $_AB.$_Ei():
                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();
                break;
      }
    }
}
```

将以上JS编写为还原图片的Python代码如下:

```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) + 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
      n = len(slice_img)
      new_np_img = 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`,测试结果如下图:



至此,图片乱序还原的问题搞定。


## 滑动轨迹的加密方法

同样的方法跟踪滑块失败后的请求,分析回溯来到如下代码:

``` javascript
"$_CHBV": function (t, e, n) {
    var $_CABJD = $_AB.$_Ds, $_CABIQ = ['$_CACCE'].concat($_CABJD), $_CACAM = $_CABIQ;
    $_CABIQ.shift();
    var $_CACBm = $_CABIQ;
    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的组成,将代码还原为:
``` javascript
/**
* 用于计算 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 = o | t["charCodeAt"](a) << s, a++;
                return s = a % 4 * 8, o = o | 128 << s, o = n << 3, o = 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, 7, 3614090360), p, g, s, 12, 3905402710), d, p, s, 17, 606105819), m, d, s, 22, 3250441966), g = e(g, m = e(m, d = e(d, p, g, m, s, 7, 4118548399), p, g, s, 12, 1200080426), d, p, s, 17, 2821735955), m, d, s, 22, 4249261313), g = e(g, m = e(m, d = e(d, p, g, m, s, 7, 1770035416), p, g, s, 12, 2336552879), d, p, s, 17, 4294925233), m, d, s, 22, 2304563134), g = e(g, m = e(m, d = e(d, p, g, m, s, 7, 1804603682), p, g, s, 12, 4254626195), d, p, s, 17, 2792965006), m, d, s, 22, 1236535329), g = n(g, m = n(m, d = n(d, p, g, m, s, 5, 4129170786), p, g, s, 9, 3225465664), d, p, s, 14, 643717713), m, d, s, 20, 3921069994), g = n(g, m = n(m, d = n(d, p, g, m, s, 5, 3593408605), p, g, s, 9, 38016083), d, p, s, 14, 3634488961), m, d, s, 20, 3889429448), g = n(g, m = n(m, d = n(d, p, g, m, s, 5, 568446438), p, g, s, 9, 3275163606), d, p, s, 14, 4107603335), m, d, s, 20, 1163531501), g = n(g, m = n(m, d = n(d, p, g, m, s, 5, 2850285829), p, g, s, 9, 4243563512), d, p, s, 14, 1735328473), m, d, s, 20, 2368359562), g = r(g, m = r(m, d = r(d, p, g, m, s, 4, 4294588738), p, g, s, 11, 2272392833), d, p, s, 16, 1839030562), m, d, s, 23, 4259657740), g = r(g, m = r(m, d = r(d, p, g, m, s, 4, 2763975236), p, g, s, 11, 1272893353), d, p, s, 16, 4139469664), m, d, s, 23, 3200236656), g = r(g, m = r(m, d = r(d, p, g, m, s, 4, 681279174), p, g, s, 11, 3936430074), d, p, s, 16, 3572445317), m, d, s, 23, 76029189), g = r(g, m = r(m, d = r(d, p, g, m, s, 4, 3654602809), p, g, s, 11, 3873151461), d, p, s, 16, 530742520), m, d, s, 23, 3299628645), g = i(g, m = i(m, d = i(d, p, g, m, s, 6, 4096336452), p, g, s, 10, 1126891415), d, p, s, 15, 2878612391), m, d, s, 21, 4237533241), g = i(g, m = i(m, d = i(d, p, g, m, s, 6, 1700485571), p, g, s, 10, 2399980690), d, p, s, 15, 4293915773), m, d, s, 21, 2240044497), g = i(g, m = i(m, d = i(d, p, g, m, s, 6, 1873313359), p, g, s, 10, 4264355552), d, p, s, 15, 2734768916), m, d, s, 21, 1309151649), g = i(g, m = i(m, d = i(d, p, g, m, s, 6, 4149444226), p, g, s, 10, 3174756917), d, p, s, 15, 718787259), m, d, s, 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 = 57 < o ? o - 87 : o - 48;
            }

            n = 36 * r + r;
            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(i)] || (c = 1, u["push"](s), _ = 5 == ++_ ? 0 : _);
            }

            var f, h = a, d = 4, p = "", g = ;

            while (0 < h) {
                if (0 <= h - g) {
                  f = parseInt(Math["random"]() * u["length"], 10);
                  p += u;
                  h -= g;
                } 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 = t(e, 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 - t);
                            n = Math["round"](t - t);
                            r = Math["round"](t - t);
                            0 == e && 0 == n && 0 == r || (0 == e && 0 == n ? o += r : (i["push"](), o = 0));
                  
                  }

                  return 0 !== o && i["push"](), i;
                }(_pt_array);    // 滑动的轨迹数组

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

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

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

      /**
         * t: $_BAFz的返回值
         * e: 接口返回值中C的值:
         * n: 接口返回的s值
         */
      "$_BGDl": function(t, e, n){
                if (!e || !n) return t;
                var r, i = 0, o = t, s = e, a = e, u = e;
                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], , , , , , , , , , , , ];

var _ = SlideObject["$_BGDl"](SlideObject["$_BAFz"](pt_list), , "35304332")
console.log(_);

```

其中 `SlideObject["$_CEAQ"]`方法依赖于浏览器的windows环境,如下图:



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



因此,可以根据 `Performance.timing` 时间产生的先后顺序以及时间间隔,用当前的时间戳减去相应的值来模拟。由于相关代码比较简单,为了节省篇幅就不给出了。


## 滑动轨迹生成的思路

由于极验采用人工智能的方式对滑动的轨迹进行的验证,因此如果我们比较随意的生成鼠标滑动轨迹基本是肯定被封的,因此我们要详细分析一下鼠标轨迹的规律,
通之前介绍的调试手段,手工滑动滑块,获取到鼠标滑动轨迹的集合数组如下:
> [[-37,-41,0], , , , , , , , , , , , ]

每个点的组成为:,含义如下:
* 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坐标生成方法就告一段落。剩下的是滑动时间的分配。

对上面十次滑动的坐标集合,计算出每个坐标点消耗的时间,对时间进行汇总,如下表:

| 序号 | 0-15(ms) | 15-20(ms) | 20-200(ms) | 200-400(ms) |
| --- | --- | --- | --- | --- |
| 1 | 1.72% | 81.03% | 15.52% | 1.72% |
| 2 | 2.04% | 79.59% | 14.29% | 4.08% |
| 3 | 1.15% | 90.80% | 6.90% | 1.15% |
| 4 | 1.47% | 82.35% | 13.24% | 2.94% |
| 5 | 1.43% | 87.14% | 10.00% | 1.43% |
| 6 | 2.63% | 80.26% | 13.16% | 3.95% |
| 7 | 1.52% | 83.33% | 13.64% | 1.52% |
| 8 | 1.20% | 85.54% | 12.05% | 1.20% |
| 9 | 1.33% | 81.33% | 16.00% | 0.00% |
| 10 | 3.85% | 78.85% | 15.38% | 1.92% |

统计分析结果如下:
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坐标。

最后,整理出相关示例代码如下:

```python
"""
用于生成坐标轨迹
"""
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 =

      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 =
      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 =
      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 < ss:
                th = ss

      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 += delta_pt
            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(y), int(z)])

      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%左右,足够我们使用了。

全文完!

x88tv 发表于 2020-4-23 13:12

支持下,看到

allenluo 发表于 2020-5-4 17:00

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

苏晓宇c 发表于 2020-4-22 18:33

{:301_1009:}过分优秀。

zhengpengxin 发表于 2020-4-22 18:38

优秀,大佬大佬{:1_927:}

pppjie 发表于 2020-4-22 19:14

看着就比较难

生有涯知无涯 发表于 2020-4-22 19:36

这是真正的大佬{:301_1008:}

夜饮 发表于 2020-4-22 20:00

跟着大佬学习学习

sifeng 发表于 2020-4-22 21:45

膜拜大佬大佬牛逼

xiao智可以不帅 发表于 2020-4-22 21:48

非常优秀

q6378561 发表于 2020-4-22 22:46

干货很充足 学习了。

VChq 发表于 2020-4-22 23:08

干货,支持!!
页: [1] 2 3 4 5 6 7
查看完整版本: 极验反爬虫防护分析之slide验证方式下图片的处理及滑动轨迹的生成思路