本文要分享的内容是去年为了抢鞋而分析 极验(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

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

  1. 反反爬虫 IP代理

    0x01 前言 一般而言,抓取稍微正规一点的网站,都会有反爬虫的制约.反爬虫主要有以下几种方式: 通过UA判断.这是最低级的判断,一般反爬虫不会用这个做唯一判断,因为反反爬虫非常容易,直接随机UA即可 ...

  2. 爬虫进阶教程:极验(GEETEST)验证码破解教程

    摘要 爬虫最大的敌人之一是什么?没错,验证码!Geetest作为提供验证码服务的行家,市场占有率还是蛮高的.遇到Geetest提供的滑动验证码怎么破?授人予鱼不如授人予渔,接下来就为大家呈现本教程的精 ...

  3. 潭州课堂25班:Ph201805201 爬虫基础 第十课 图像处理- 极验验证码 (课堂笔记)

    用 python 的  selenium  访问  https://www.huxiu.com/ 自动通过验证码 # -*- coding: utf-8 -*- # 斌彬电脑 # @Time : 20 ...

  4. 对极验geetest滑块验证码图片还原算法的研究

    免责声明 本文章所提到的技术仅用于学习用途,禁止使用本文章的任何技术进行发起网络攻击.非法利用等网络犯罪行为,一切信息禁止用于任何非法用途.若读者利用文章所提到的技术实施违法犯罪行为,其责任一概由读者 ...

  5. crawler_爬虫_反爬虫策略

    关于反爬虫和恶意攻击的一些策略和思路   有时网站经常受到恶意spider攻击,疯狂抓取网站内容,对网站性能有较大影响. 下面我说说一些反恶意spider和spam的策略和思路. 1. 通过日志分析来 ...

  6. 破解极验(geetest)验证码

      破解极验(geetest)验证码 这是两年前的帖子: http://www.v2ex.com/t/138479 一个月前的破解程序,我没用过 asp.net ,不知道是不是真的破解了, demo ...

  7. 极验验证码破解之selenium

    这一篇写完很久了,因为识别率一直很低,没办法拿出来见大家,所以一直隐藏着,今天终于可以拿出来见见阳光了. 哈喽,大家好,我是星星在线,我又来了,今天给大家带来的是极验验证码的selenium破解之法, ...

  8. Python爬虫开发:反爬虫措施以及爬虫编写注意事项

  9. 配置Nutch模拟浏览器以绕过反爬虫限制

    原文链接:http://yangshangchuan.iteye.com/blog/2030741 当我们配置Nutch抓取 http://yangshangchuan.iteye.com 的时候,抓 ...

随机推荐

  1. python使用matplotlib:subplot绘制多个子图 不规则画图

    https://www.cnblogs.com/xiaoboge/p/9683056.html

  2. 使用scikit-learn解决文本多分类问题(附python演练)

    来源 | TowardsDataScience 译者 | Revolver 在我们的商业世界中,存在着许多需要对文本进行分类的情况.例如,新闻报道通常按主题进行组织; 内容或产品通常需要按类别打上标签 ...

  3. 深入理解JavaScript中的堆与栈 、浅拷贝与深拷贝

    JavaScript中的浅拷贝与深拷贝  学了这么长时间的JavaScript想必大家对浅拷贝和深拷贝还不太熟悉吧,今天在项目中既然用到了,早晚也要理清一下思路了,在了解之前,我们还是先从JavaSc ...

  4. 模块 shutil_zipfile_tarfile压缩解压

    shutil_zipfile_tarfile压缩解压 shutil 模块 高级的 文件.文件夹.压缩包 处理模块 shutil.copyfileobj(fsrc, fdst[, length]) #将 ...

  5. unix中数据缓冲区高速缓冲的设计

    目录 1. 概述 2. 缓冲区的设计 2.1 缓冲区头部 2.2 缓冲区的结构 2.3 缓冲区的检索算法 2.3. 申请一个缓冲区算法 getblk 2.3.2 释放一个缓冲区算法 brelse 2. ...

  6. Codeforces 631 (Div. 2) C. Dreamoon Likes Coloring 思维or构造

    https://codeforces.com/contest/1330/problem/C 给n个格子染色,有m种颜色,要求最后的所以格子被染色,并且有m种颜色. 染色要求:每种颜色有一个值li,选择 ...

  7. Linux - Ubuntu18.04下更改apt源为阿里云源

    进入apt目录,备份原来的源地址 cd /etc/apt mv ./source.list ./source.list.bak 修改源文件source.list vim source.list 更换阿 ...

  8. Oracle数据库表和表列讲解

    如果将数据库比作一个存储东西的储物柜,表就像是储物柜上的各个抽屉,每个抽屉分门别类地存放了各种数据,在设计和规划数据库时,表的定义和规划往往相当重要,良好的表设计决定了程序人员编写程序的便利性与数据库 ...

  9. 201771010108 -韩腊梅-java学习进度表

    2018面向对象程序设计(Java)课程进度表 周次 (阅读/编写)代码行数  发布博文量/评论他人博文数量  课余学习时间(小时)  学习收获最大的程序阅读或编程任务 1 30/40 1/0 8   ...

  10. vue中的$router 和 $route的区别

    最近在学习vue的单页面应用开发,需要vue全家桶,其中用到了VueRouter,在路由的设置和跳转中遇到了两个对象$router 和 $route ,有些傻傻分不清,后来自己结合网上的博客和自己本地 ...