声明
本文章中所有内容仅供学习交流,抓包内容、敏感网址、数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除!
本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K哥爬虫】联系作者立即删除!
逆向目标
- 目标:某验二代滑块验证码逆向分析
- 主页:
aHR0cDovL3d3dy5qc2dzai5nb3YuY246NTg4ODgvbWluaS9uZXR3ZWIvU01MaWJyYXJ5LmpzcA==
- 说明:大多数逻辑其实和三四代都一样,相同的就简写了,有疑惑的地方可以看以前的文章
- 【验证码逆向专栏】某验三代滑块验证码逆向分析
- 【验证码逆向专栏】某验四代滑块验证码逆向分析
抓包情况
主页点击搜索就会跳出二代的验证码,netWebServlet.json
的请求,会返回 challenge
和 gt
。
有个 get.php
的请求,返回了一个新的 challenge
,这个请求之后的操作,都要用这个新的 challenge
,不然是验证不成功的,其他的还有验证码背景图片、乱序图片地址、c
、s
等值,之前写过三代的文章,都是类似的,这里就不一一分析了。
然后是 ajax.php
验证是否通过,通过之后返回一个 validate
,请求里同样是需要我们逆向的 w
参数:
然后同样还是 netWebServlet.json
接口,带上 get.php
请求返回的 challenge
以及 ajax.php
返回的 validate
,请求拿到一个 name
的字段。
后续的搜索数据,带上这个 name
就行了:
逆向分析
搞过三、四代的都知道我们可以直接搜索 w
的 Unicode 值 \u0077
即可定位,但是二代则不是 Unicode,而是16进制的编码,搜索 \x77
即可定位,当然按照正常流程,跟栈也能很容易找到加密的位置。
获取 H7z 值
从上图中可以知道 w
的值为 r7z + H7z
,先看 H7z
。
跟进这个方法,来到一大串控制流,这里还是推荐用 AST 还原一下,后续可能有一些循环啥的,硬跟的话容易出错,当然直接全部扣一把梭也是可以的,H7z
的核心其实就是 RSA 加密随机字符串,三代四代都有,这里就不细讲了。
获取 r7z 值
然后就是 r7z
,主要由以下两句代码生成:
q7z = n0B[M9r.R8z(699)](h7B[M9r.C8z(105)](Y7z), V7z[M9r.R8z(818)]())
r7z = p7B[M9r.R8z(260)](q7z)
可以看到其中有个变量 Y7z
参与了计算,先来看看他是怎么来的,直接搜索即可定位,可以发现同样是16进制的编码,由五个值组成:userresponse
、passtime
、imgload
、aa
、ep
获取 userresponse 值
挨个分析,首先是 userresponse
,将滑动距离和 challenge
的值传入一个方法,得到一个 9 位字符串:
上图中 g7z
就是滑动距离,搜索可以看到定义的地方,尺子量一下对比一下,和滑动的距离是一致的:
然后再来看看那个方法,跟进去之后也是一大串 switch-case
控制流:
还原一下代码如下:
function getUserResponse(L0z, o0z) {
for (var j0z = o0z.slice(32), c0z = [], X0z = 0; X0z < j0z.length; X0z++){
var K0z = j0z.charCodeAt(X0z);
c0z[X0z] = K0z > 57 ? K0z - 87 : K0z - 48;
}
j0z = 36 * c0z[0] + c0z[1];
var k0z = Math.round(L0z) + j0z;
o0z = o0z.slice(0, 32);
var n0z, f0z = [[], [], [], [], []], Q0z = {}, N0z = 0;
X0z = 0;
for (var i0z = o0z.length; i0z > X0z; X0z++){
n0z = o0z.charAt(X0z), Q0z[n0z] || (Q0z[n0z] = 1, f0z[N0z].push(n0z), N0z++, N0z = 5 == N0z ? 0 : N0z);
}
var y0z, v0z = k0z, B0z = 4, x0z = "", I0z = [1, 2, 5, 10, 50];
while ( v0z > 0) {
v0z - I0z[B0z] >= 0 ? (y0z = parseInt(Math.random() * f0z[B0z].length, 10),
x0z += f0z[B0z][y0z], v0z -= I0z[B0z]) : (f0z.splice(B0z, 1),
I0z.splice(B0z, 1), B0z -= 1);
}
return x0z;
}
获取 passtime 值
passtime
不用考虑是怎么通过函数获取的,含义就是滑动完成所花费的时间,直接取轨迹的最后一个值即可,这个也和三四代是一样的,获取语句为:var passtime = track[track.length - 1][2]
,如下图所示,轨迹的最后一个值时间为 871,passtime
的值同样也为 871。
获取 imgload 值
imgload
也没啥特别的,从字面意思猜测应该是图片加载耗时,实测直接写死即可,或者整个随机值就行。
获取 aa 值
aa
的值就是 F7z
,如下图所示:
搜索 F7z
,定位到下图所示的地方,向一个方法中传入了一个时间戳:
跟进去同样是 switch-case
控制流,需要注意的是下图中 c7B[M9r.R8z(781)](M9r.R8z(764), K1z)
的值其实就是轨迹。
这段控制流还原一下就变成这样了:
function getF7z(track){
var o5r = 6;
for (var N1z, X1z = s6z(track), f1z = [], B1z = [], o1z = [], t1z = 0, j1z = X1z.length; t1z < j1z; t1z++){
if (o5r * (o5r + 1) % 2 + 8) {
N1z = u6z(X1z[t1z]),
N1z ? B1z.push(N1z) : (f1z.push(O6z(X1z[t1z][0])),
B1z.push(O6z(X1z[t1z][1]))),
o1z.push(O6z(X1z[t1z][2]));
o5r = o5r >= 17705 ? o5r / 3 : o5r * 3;
}
}
return f1z.join("") + "!!" + B1z.join("") + "!!" + o1z.join("");
}
function s6z(F6z){
for (var Y6z, g6z, a6z, E6z = [], D6z = 0, P6z = [], J6z = 0, l6z = F6z.length - 1; J6z < l6z; J6z++) {
Y6z = Math.round(F6z[J6z + 1][0] - F6z[J6z][0]),
g6z = Math.round(F6z[J6z + 1][1] - F6z[J6z][1]),
a6z = Math.round(F6z[J6z + 1][2] - F6z[J6z][2