1. 抓包分析
打开某验的 demo,点出验证码图片,分析 network 中的请求以及参数变化
请求与参数:
1、 Request URL: https://www.geetest.com/demo/gt/register-slide?t=1640096834809
返回: challenge: "5bd76b0b1c9388a667bba39af5cfd71e"
gt: "019924a82c70bb123aae90d483087f94"
2、 Request URL: https://apiv6.geetest.com/gettype.php?gt=019924a82c70bb123aae90d483087f94&callback=geetest_1640096837076
返回: fullpage: "/static/js/fullpage.9.0.8.js"
slide: "/static/js/slide.7.8.6.js"
3、 Request URL: https://apiv6.geetest.com/get.php?gt=019924a82c70bb123aae90d483087f94&challenge=5bd76b0b1c9388a667bba39af5cfd71e&lang=zh-cn&pt=0&client_type=web&w=7(Pr5uTDzYQIrCym5Psn(W7fvv7K3ji2dFDhp...
提交: gt: 019924a82c70bb123aae90d483087f94
challenge: 5bd76b0b1c9388a667bba39af5cfd71e
w: 7(Pr5uTDzYQIrCym5Psn(W7fvv7K3ji2dFDhpCxfIsWhd...
返回: status: "success"
4、 Request URL: https://api.geetest.com/ajax.php?gt=019924a82c70bb123aae90d483087f94&challenge=5bd76b0b1c9388a667bba39af5cfd71e&lang=zh-cn&pt=0&client_type=web&w=7ZMNp(76n(MlO6aTxrUCxNw(7Jj5kEFnmodPZj2Nid...
提交: w: 7ZMNp(76n(MlO6aTxrUCxNw(7Jj5kEFnmodPZj2NidffUg3PXYPHS(65Up0jO2X...
callback: geetest_1640096847867
5、 Request URL: https://api.geetest.com/get.php?is_next=true&type=slide3>=019924a82c70bb123aae90d483087f94&challenge=5bd76b0b1c9388a667bba39af5cfd71e&lang=zh-cn&https=true&protocol=https%3A%2F%2F&offline=false&product=embed&api_server=api.geetest.com&isPC=true&autoReset=true&width=100%25&callback=geetest_1640096847383
提交: gt: 019924a82c70bb123aae90d483087f94
challenge: 5bd76b0b1c9388a667bba39af5cfd71e
返回: fullbg: "pictures/gt/b9694f3e8/b9694f3e8.jpg"
slice: "pictures/gt/b9694f3e8/slice/f35f84584.png"
bg: "pictures/gt/b9694f3e8/bg/f35f84584.jpg"
拖动滑块,完成验证,继续分析请求和参数。
6、 Request URL: https://api.geetest.com/ajax.php?gt=019924a82c70bb123aae90d483087f94&challenge=5bd76b0b1c9388a667bba39af5cfd71eaj&lang=zh-cn&%24_BBF=0&client_type=web&w=(x5U5)0n0zt1KeeU1X7ZseyXl)fhHOh539)Bm2(6...
提交: w: (x5U5)0n0zt1KeeU1X7ZseyXl)fhHOh539)Bm2(6bQrrmShGp9v27nhkmGV...
gt: 019924a82c70bb123aae90d483087f94
challenge: 5bd76b0b1c9388a667bba39af5cfd71eaj
返回: message: "success"
validate: "00c14c420b44ade8cd6d3ddd5c916010"
需要明确的问题:
1、还原底图(处理底图乱序问题);
2、获取 w 值,生成轨迹;
2. 还原底图
-
通过分析页面元素,我们发现验证码是一个 canvas。
-
我们打上 canvas 断点,然后刷新重新获取验证码。
- 此时注意提示
https://static.geetest.com/pictures/gt/b9694f3e8/b9694f3e8.webp
打开以后就是乱序验证码,e = canvas.geetest_canvas_bg.geetest_absolute {width: 260, height: 160, title: '', toDataURL: ƒ, toBlob: ƒ, …}
的就是验证码的宽度和高度。
同时,仔细观察我们发现,验证码混淆后分成了上下两部分。
- 单步调试,集合验证码图片、分析代码;
根据提示我们发现,有一段代码的提示是l = ImageData {data: Uint8ClampedArray(3200), width: 10, height: 80, colorSpace: 'srgb'}, o = CanvasRenderingContext2D {canvas: canvas, globalAlpha: 1
,推测ImageData
是画图动作,进而推测验证码还原的关键步骤,在l = o[$_CJET(69)](c, u, 10, a)
这一步。
我们仔细分析一下这一段 for 循环代码。
for (var a = r / 2, _ = 0; _ < 52; _ += 1) {
var c = Ut[_] % 26 * 12 + 1
, u = 25 < Ut[_] ? a : 0
, l = o[$_CJET(69)](c, u, 10, a);
s[$_CJET(66)](l, _ % 26 * 10, 25 < _ ? a : 0);
}
对上面的代码进一步分析,我们发现这里进行了 52 次循环,而代码中的 26 刚好是 52 的一半,这个与前面验证码被分成上下两部分呼应。
我们推测验证码被分成了52个小块,上下两个部分各有26,每个小块是的宽度是 10,加在一起正好是 260,刚好是前面分析发现的整个验证码图片的宽度。
仔细分析上面的代码,我们发现这个循环,会根据
Ut[_]
的变化而进行不同的操作。_
是一个每次自增1且小于52的整数,那 Ut
是什么呢?我们在 console 中获取,发现他是一个52位的数组,
[39, 38, 48, 49, 41, 40, 46, 47, 35, 34, 50, 51, 33, 32, 28, 29, 27, 26, 36, 37, 31, 30, 44, 45, 43, 42, 12, 13, 23, 22, 14, 15, 21, 20, 8, 9, 25, 24, 6, 7, 3, 2, 0, 1, 11, 10, 4, 5, 19, 18, 16, 17]
。这个时候,就可以断定这个数组就是验证码混淆的顺序,按照这个顺序反向操作就能还原验证码。而上面的 for 循环,实际就是在执行这个还原验证码的操作。
注意:
这个数组可能是动态的,也可能是静态的,为了验证是静态还是动态,我们需要进行多次调试,对比该数组是否发生变化。这里我们通过分析,确认该数组是静态的。
至此,底图还原的逆向工作完成。
3. 获取 w 值 & 还原推动轨迹
3.1. 获取 w 值
w 值在这里曾经多次生成,这里只分析其中的一次。
w 是一个数字单字符,这里通过直接搜索无法直接搜到。这里我们采用跟栈的方式一步步回溯。
跟栈技巧
跟栈的时候,注意我们的重点关注 w 值有没有在提示中出现,如果又出现就说明它在代码之前或者上一步,就已经被生成了。我们就继续往前找,一直找到提示中没有 w 值的时候,我们再分析 w 值到底是在哪里生成的。
注意:在开始跟栈的时候,如果找不到 w 值,就往前多跟几步。
继续上面的思路往前跟栈,很快就会发现遇到一个平坦流,如下图:
平坦流的跟栈技巧
当遇到平坦流的时候,首先调试分析平坦流有没有对目标参数做修改,如果没有就不用关注控制流,直接跳过就好。
很多时候,我们可以直接跳过平坦流,继续往前找,看看能不能找到 w 值,如果实在找不到再回来分析平坦流。这里其实可以先在函数内部、平坦流前打一个断点,然后刷新货重新请求、让浏览器在此处被断掉,继而进行跟栈分析。
分析发现,w 值在 R
这层已经出现,那我们就没必要关注之前的平坦流,接着跟栈分析就可以。如下图:
我们接着进行跟栈分析,发现调试工具已经把
"w"
的生成逻辑都提示出来了。
w 值的生成代码就是下面这行。
"\u0077": h + u
到这一步,就进入你想的关键步骤了。
下面附上比较关键的一段加密代码,供大家参考。
var rt = function() {
var $_BFBDL = lTloj.$_CX
, $_BFBCi = ['$_BFBGO'].concat($_BFBDL)
, $_BFBEt = $_BFBCi[1];
$_BFBCi.shift();
var $_BFBFk = $_BFBCi[0];
function t() {
var $_DBFAh = lTloj.$_DP()[0][4];
for (; $_DBFAh !== lTloj.$_DP()[2][3]; ) {
switch ($_DBFAh) {
case lTloj.$_DP()[0][4]:
return (65536 * (1 + Math[$_BFBDL(75)]()) | 0)[$_BFBDL(396)](16)[$_BFBDL(476)](1);
break;
}
}
}
return function() {
var $_BFBIl = lTloj.$_CX
, $_BFBHs = ['$_BFCBY'].concat($_BFBIl)
, $_BFBJq = $_BFBHs[1];
$_BFBHs.shift();
var $_BFCAQ = $_BFBHs[0];
return t() + t() + t() + t();
}
;
}();
3.2. 轨迹还原
当分析到前面代码处位置时,我们发现其中一个很重要的参数 o
,这个 o
是一个对象,对象中其中包括了下面的内容。
aa: "M/-821./0(!!Mty!)(!)!)!)!K)(!)ysttststsss(!!(f011/11x1111/19111o6,S/$)ZC"
ep: {v: '7.8.6', $_BHR: false, me: true, tm: {…}, td: -1}
imgload: 813
lang: "zh-cn"
passtime: 337
rp: "a110d6f451e2b042883f4ae191a89272"
userresponse: "99e999e71"
xb3y: "1140037174"
[[Prototype]]: Object
通过多次调试,我们发现这里的 aa
和 rp
值,会随着每次拖动滑块验证码而变化,我们猜测这里可能是滑动的轨迹。通过想上查找和跟栈,我们找到了这个地方:
l = n[$_CJJIW(1078)][$_CJJJd(1069)](n[$_CJJIW(1078)][$_CJJJd(1051)](), n[$_CJJJd(13)][$_CJJIW(1045)], n[$_CJJIW(13)][$_CJJIW(307)])
通过还原,得到以下代码:
l = n['$_CIBw']['$_BBCA'](n['$_CIBw']['$_GEy'](), n['$_CIY']['c'], n['$_CIY']['s'])
通过 console 控制台,我们分析得到下面的内容:
n['$_CIBw']['$_GEy']() : 'T,0./53/220-,(!!Mty*)))*)))(y((t)tssssswssssswsssssvvsssswsss(!!($)39/112011100120112.9191/:8Fp/01Ei:-:901C3/_$)J$*r'
n['$_CIY']['c'] : [12, 58, 98, 36, 43, 95, 62, 15, 12]
n['$_CIY']['s'] : '6f404c79'
那上面的数据都是从哪里拿到的呢?我们将 n['$_CIY']
还原,得到下面的内容:
通过分析,可以知道
n['$_CIY']['c']
、n['$_CIY']['s']
是前面通过请求返回的,可以直接拿来使用。这里比较重要的是
n['$_CIBw']['$_GEy']()
,我们进入函数找到下面这段代码。
"\u0024\u005f\u0047\u0045\u0079": function() {
var $_BEGIH = lTloj.$_CX
, $_BEGHL = ['$_BEHBT'].concat($_BEGIH)
, $_BEGJJ = $_BEGHL[1];
$_BEGHL.shift();
var $_BEHAr = $_BEGHL[0];
function n(t) {
var $_DBEIz = lTloj.$_DP()[0][4];
for (; $_DBEIz !== lTloj.$_DP()[2][3]; ) {
switch ($_DBEIz) {
case lTloj.$_DP()[0][4]:
var e = $_BEGJJ(430)
, n = e[$_BEGJJ(182)]
, r = $_BEGIH(33)
, i = Math[$_BEGIH(383)](t)
, o = parseInt(i / n);
n <= o && (o = n - 1),
o && (r = e[$_BEGIH(122)](o));
var s = $_BEGIH(33);
return t < 0 && (s += $_BEGJJ(456)),
r && (s += $_BEGJJ(459)),
s + r + e[$_BEGIH(122)](i %= n);
break;
}
}
}
var t = function(t) {
var $_BEHDi = lTloj.$_CX
, $_BEHCK = ['$_BEHGM'].concat($_BEHDi)
, $_BEHEF = $_BEHCK[1];
$_BEHCK.shift();
var $_BEHFx = $_BEHCK[0];
for (var e, n, r, i = [], o = 0, s = 0, a = t[$_BEHEF(182)] - 1; s < a; s++)
e = Math[$_BEHEF(156)](t[s + 1][0] - t[s][0]),
n = Math[$_BEHDi(156)](t[s + 1][1] - t[s][1]),
r = Math[$_BEHDi(156)](t[s + 1][2] - t[s][2]),
0 == e && 0 == n && 0 == r || (0 == e && 0 == n ? o += r : (i[$_BEHEF(140)]([e, n, r + o]),
o = 0));
return 0 !== o && i[$_BEHDi(140)]([e, n, o]),
i;
}(this[$_BEGJJ(361)])
, r = []
, i = []
, o = [];
return new ct(t)[$_BEGIH(84)](function(t) {
var $_BEHIs = lTloj.$_CX
, $_BEHHw = ['$_BEIBE'].concat($_BEHIs)
, $_BEHJy = $_BEHHw[1];
$_BEHHw.shift();
var $_BEIAO = $_BEHHw[0];
var e = function(t) {
var $_BEIDv = lTloj.$_CX
, $_BEICk = ['$_BEIGW'].concat($_BEIDv)
, $_BEIEU = $_BEICk[1];
$_BEICk.shift();
var $_BEIFh = $_BEICk[0];
for (var e = [[1, 0], [2, 0], [1, -1], [1, 1], [0, 1], [0, -1], [3, 0], [2, -1], [2, 1]], n = 0, r = e[$_BEIDv(182)]; n < r; n++)
if (t[0] == e[n][0] && t[1] == e[n][1])
return $_BEIEU(413)[n];
return 0;
}(t);
e ? i[$_BEHIs(140)](e) : (r[$_BEHJy(140)](n(t[0])),
i[$_BEHJy(140)](n(t[1]))),
o[$_BEHJy(140)](n(t[2]));
}),
r[$_BEGJJ(444)]($_BEGIH(33)) + $_BEGIH(407) + i[$_BEGIH(444)]($_BEGJJ(33)) + $_BEGIH(407) + o[$_BEGIH(444)]($_BEGIH(33));
}
其中最后一行代码,经过 console 还原,可以还原呈现面的代码:
r['join']('') + '!!' + i['join']('') + '!!' + o['join']('')
这里的 r
、i
、o
是三个数组,加密的核心逻辑,就是生成这三个数组。而这三个数组的生成过程,其实也就是对滑动轨迹的处理过程。那轨迹是什么呢?
通过分析 return new ct(t)[$_BEGIH(84)]
我们发现,整个处理过程都是围绕 t
展开的。
那
t
又是什么东西呢?调试工具已经提示给我们了,它是一个数组,分析数组内容我们推测,这就是滑块运行轨迹,数组中的每个小数组有三个值。经验判断三个值分别是:横向滑动距离、纵向滑动距离、以及滑动时间。
0: (3) [35, 23, 0]
1: (3) [1, 0, 83]
2: (3) [1, 0, 8]
3: (3) [2, 1, 8]
4: (3) [2, 0, 8]
5: (3) [2, 0, 6]
6: (3) [2, 0, 8]
7: (3) [2, 0, 8]
8: (3) [2, 0, 8]
9: (3) [0, 1, 8]
10: (3) [1, 0, 8]
11: (3) [1, 1, 22]
12: (3) [1, 0, 8]
13: (3) [1, 0, 16]
14: (3) [0, 1, 8]
15: (3) [1, 0, 8]
16: (3) [2, 0, 8]
17: (3) [1, 0, 6]
18: (3) [1, 0, 8]
19: (3) [2, 0, 10]
20: (3) [1, 0, 6]
21: (3) [1, 0, 24]
22: (3) [1, 0, 8]
23: (3) [1, 0, 14]
24: (3) [1, 0, 17]
25: (3) [1, 0, 23]
26: (3) [1, 0, 11]
27: (3) [1, 0, 19]
28: (3) [1, 0, 16]
29: (3) [1, 0, 8]
30: (3) [1, 0, 7]
31: (3) [1, 0, 8]
32: (3) [2, 0, 8]
33: (3) [2, 0, 15]
34: (3) [1, 0, 24]
35: (3) [1, 0, 22]
36: (3) [1, 0, 16]
37: (3) [1, 0, 8]
38: (3) [1, 0, 8]
39: (3) [0, 0, 116]
length: 40
[[Prototype]]: Array(0)
这里需要注意的是,t
的值在经过 ct(t)[$_BEGIH(84)]
函数处理时,会发生变化,如果忽略了这一点可能会导致轨迹加密后,依然无法成功请求。
到这里关键的逆向工作就差不多完成了,不过这里要注意加密的入口是 l = n[$_CJJIW(1078)][$_CJJJd(1069)](n[$_CJJIW(1078)][$_CJJJd(1051)](), n[$_CJJJd(13)][$_CJJIW(1045)], n[$_CJJIW(13)][$_CJJIW(307)])
,我们前面的逆向完成的是传入的三个参数,外面的函数也需抠出来。我们下断追进去,发现下面这段代码,直接拿过来用就可以。
"\u0024\u005f\u0042\u0042\u0043\u0041": function(t, e, n) {
var $_BEIIp = lTloj.$_CX
, $_BEIHl = ['$_BEJBo'].concat($_BEIIp)
, $_BEIJO = $_BEIHl[1];
$_BEIHl.shift();
var $_BEJAQ = $_BEIHl[0];
if (!e || !n)
return t;
var r, i = 0, o = t, s = e[0], a = e[2], _ = e[4];
while (r = n[$_BEIIp(373)](i, 2)) {
i += 2;
var c = parseInt(r, 16)
, u = String[$_BEIIp(206)](c)
, l = (s * c * c + a * c + _) % t[$_BEIIp(182)];
o = o[$_BEIIp(373)](0, l) + u + o[$_BEIJO(373)](l);
}
return o;
}