本文要分享的内容是去年为了抢鞋而分析 极验(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. iphone ios app互相调用

    正好要做这个,记录一下 1.ios应用程序间互相启动 2.网页如何调用自己的app http://www.dotblogs.com.tw/yang5664/archive/2012/11/24/850 ...

  2. 关于TensorFlow九件你非知不可的事

    来源 | Hackernoon 译者 | Revolver 前些天我参加了7 月24 日在美国旧金山举行的Google Cloud Next 2018 大会,其中的一个演讲( What's New w ...

  3. Matlab 编程简介与实例

    函数作图 二维平面曲线作图函数  plot(x, y, 's') x, y是长度相同的向量,s表示线型和颜色 如果作多条曲线在同一图上,则用函数: plot(x1, y1, 's1', x2, y2, ...

  4. IOS 空字符串报错 解决办法

    NSScanner: nil string argument  NSScanner: nil string argument libc++abi.dylib: terminate_handler un ...

  5. linux部署win服务 dotnet mono jexus

    .Net Core (dotnet C#应用) dotnet 可以用在linux上运行 C#应用 适用于 SSO 统一身份认证系统 # 安装依赖 yum install libunwind yum i ...

  6. java web综合案例

    1.采用的技术: bootstrap+jsp+servlet+三层架构(servlet,service,dao)+mysql 注意:mysql使用的是5.5版本,使用高版本会有很多问题.可以将5.5版 ...

  7. 403 Invalid CORS request 跨域问题

    5.跨域问题 跨域:浏览器对于javascript的同源策略的限制 . 以下情况都属于跨域: 跨域原因说明 示例 域名不同 www.jd.com 与 www.taobao.com 域名相同,端口不同 ...

  8. 还不懂 ConcurrentHashMap ?这份源码分析了解一下

    上一篇文章介绍了 HashMap 源码,反响不错,也有很多同学发表了自己的观点,这次又来了,这次是 ConcurrentHashMap 了,作为线程安全的HashMap ,它的使用频率也是很高.那么它 ...

  9. 如何让Java应用成为杀不死的小强?(下篇)

    各位坐稳扶好,我们要开车了.不过在开车之前,我们还是例行回顾一下上期分享的要点. 经过前两期的铺垫及烧脑的分享,我们大概对「如何实现 Java 应用进程的状态监控,如果被监控的进程 down 掉,是否 ...

  10. SpringAOP入门

    Spring的AOP aop概述 Aspect Oriented Programing 面向切面(方面)编程, aop:扩展功能不修改源代码实现 aop采取横向抽取机制,取代了传统纵向继承体系重复性代 ...