滑動軌跡的加密方法
同樣的方法跟蹤滑塊失敗后的請求,分析回溯來到如下代碼:
"$_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坐標生成方法就告一段落。剩下的是滑動時間的分配。
對上面十次滑動的坐標集合,計算出每個坐標點消耗的時間,對時間進行匯總,如下表:

統計分析結果如下:
- 80%~90%的X坐標在15~20毫秒之間
- 10%~15%在20~200及以上,其中 [-a, 0, x, ...] 這里x只有一個,取值在110~200之間坐標集最后3~5個坐標取值再50~400之間,最后一個坐標數值最大
至此,整理坐標生成的思路與流程如下:
- 整個滑塊滑動時間分為三個部分,並根據統計的時間占比分配Z坐標:
a. 滑動啟動階段,時間占總時間分配的1%~3%
b. 快速滑動只目標區域階段,時間占比位75%~90%
c. 調整階段,剩下的時間為調整階段。 - 按照等分時間塊的方式,通過上面
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)

80%左右,足夠我們使用了。
全文完!

