共计 5035 个字符,预计需要花费 13 分钟才能阅读完成。
申明
本文章中所有内容仅供学习交换,抓包内容、敏感网址、数据接口均已做脱敏解决,严禁用于商业用途和非法用处,否则由此产生的所有结果均与作者无关,若有侵权,请分割我立刻删除!
本文章未经许可禁止转载,禁止任何批改后二次流传,擅自应用本文解说的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【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]),
P6z.push([Y6z, g6z, a6z]),
0 == Y6z && 0 == g6z && 0 == a6z || (0 == Y6z && 0 == g6z ? D6z += a6z : (E6z.push([Y6z, g6z, a6z + D6z]), D6z = 0));
}
return 0 !== D6z && E6z.push([Y6z, g6z, D6z]), E6z;
}
function O6z(r6z){var d6z = "()*,-./0123456789:?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqr"
, m6z = d6z.length
, Z6z = ""
, H6z = Math.abs(r6z)
, W6z = parseInt(H6z / m6z);
W6z >= m6z && (W6z = m6z - 1), W6z && (Z6z = d6z.charAt(W6z)), H6z %= m6z;
var q6z = "";
return r6z < 0 && (q6z += "!"), Z6z && (q6z += "$"), q6z + Z6z + d6z.charAt(H6z);
}
function u6z(R6z){for (var z6z = [[1, 0], [2, 0], [1, -1], [1, 1], [0, 1], [0, -1], [3, 0], [2, -1], [2, 1]], h6z = 0, C6z = z6z.length; h6z < C6z; h6z++){if (R6z[0] == z6z[h6z][0] && R6z[1] == z6z[h6z][1]){return "stuvwxyz~"[h6z]
}
}
return 0;
}
以上只是 F7z
第一次生成的中央,前面还有二次解决,如下图所示:
同样跟进去,三个传入的参数别离是第一次生成的 F7z
、get.php
申请返回的 c
和 s
参数。
同样是一段控制流,还原后如下:
function getF7z2(Q1z, v1z, T1z){var i1z, x1z = 0, c1z = Q1z, y1z = v1z[0], k1z = v1z[2], L1z = v1z[4];
while (1){if (i1z = T1z.substr(x1z, 2)){
x1z += 2;
var n1z = parseInt(i1z, 16)
, M1z = String.fromCharCode(n1z)
, I1z = (y1z * n1z * n1z + k1z * n1z + L1z) % Q1z.length;
c1z = c1z.substr(0, I1z) + M1z + c1z.substr(I1z);
}else {return c1z}
}
return Q1z
}
至此 aa
参数剖析结束!
获取 ep 值
ep
的值就是一个版本号,此处是 {'v': '6.0.9'}
,写死即可。
获取 rp 值
自此 Y7z
的第一步生成就剖析结束了,留神接下来还有一步,向 Y7z
里新增了一个 rp
参数:
这个值的组成看起来很长,实际上是将 gt、challenge 前 32 位以及 passtime 相加通过 MD5 加密后失去的。
Y7z["rp"] = md5(gt + challenge.slice(0, 32) + passtime)
上图中 I0B
就是 MD5 办法,跟进去其实是能够看到很多 MD5 特色的,如下图所示:
自此 Y7z
的值就搞定了,而后接着后面的看,也就是 q7z
的值,同样和三四代一样的,encrypt
是 AES 加密,Y7z
通过 JSON.stringify()
解决为字符串作为待加密对象,前面是 16 为随机字符串作为 AES 的 Key,留神这里的随机字符串应该和获取 H7z
值时的随机字符串统一,不然是验证不胜利的。
而后下一步就是获取 r7z
的值,将上一步失去的 q7z
通过一个办法进行解决,跟进办法,又是和三四代一样的,相熟的 res + end
,如下图所示:
间接扣代码,或者间接应用三代的代码即可:
function $_GJF(e) {var t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789()";
return e < 0 || e >= t["length"] ? "." : t["charAt"](e);
}
function $_HBO(e, t) {return e >> t & 1;}
function $_HCX(e, o) {
var i = this;
o || (o = i);
for (var t = function(e, t) {for (var n = 0, r = 24 - 1; 0 <= r; r -= 1)
1 === $_HBO(t, r) && (n = (n << 1) + $_HBO(e, r));
return n;
}, n = "", r ="", s = e.length, a = 0; a < s; a += 3) {
var c;
if (a + 2 < s)
c = (e[a] << 16) + (e[a + 1] << 8) + e[a + 2],
n += $_GJF(t(c, 7274496)) + $_GJF(t(c, 9483264)) + $_GJF(t(c, 19220)) + $_GJF(t(c, 235));
else {
var _ = s % 3;
2 == _ ? (c = (e[a] << 16) + (e[a + 1] << 8),
n += $_GJF(t(c, 7274496)) + $_GJF(t(c, 9483264)) + $_GJF(t(c, 19220)),
r = ".") : 1 == _ && (c = e[a] << 16,
n += $_GJF(t(c, 7274496)) + $_GJF(t(c, 9483264)),
r = "." + ".");
}
}
return {
"res": n,
"end": r
};
}
获取 w 值
自此 w
的就曾经进去了,r7z + H7z
即为 w
的值。
后果验证
测试过掉验证码抓取数据胜利: