【验证码逆向专栏】某验二代滑块验证码逆向分析

张春  金牌会员 | 2023-1-12 15:20:09 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 570|帖子 570|积分 1710


声明

本文章中所有内容仅供学习交流,抓包内容、敏感网址、数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除!
本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K哥爬虫】联系作者立即删除!
逆向目标

抓包情况

主页点击搜索就会跳出二代的验证码,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,主要由以下两句代码生成:
  1. q7z = n0B[M9r.R8z(699)](h7B[M9r.C8z(105)](Y7z), V7z[M9r.R8z(818)]())
  2. r7z = p7B[M9r.R8z(260)](q7z)
复制代码
可以看到其中有个变量 Y7z 参与了计算,先来看看他是怎么来的,直接搜索即可定位,可以发现同样是16进制的编码,由五个值组成:userresponse、passtime、imgload、aa、ep

获取 userresponse 值

挨个分析,首先是 userresponse,将滑动距离和 challenge 的值传入一个方法,得到一个 9 位字符串:

上图中 g7z 就是滑动距离,搜索可以看到定义的地方,尺子量一下对比一下,和滑动的距离是一致的:


然后再来看看那个方法,跟进去之后也是一大串 switch-case 控制流:

还原一下代码如下:
  1. function getUserResponse(L0z, o0z) {
  2.     for (var j0z = o0z.slice(32), c0z = [], X0z = 0; X0z < j0z.length; X0z++){
  3.         var K0z = j0z.charCodeAt(X0z);
  4.         c0z[X0z] = K0z > 57 ? K0z - 87 : K0z - 48;
  5.     }
  6.     j0z = 36 * c0z[0] + c0z[1];
  7.     var k0z = Math.round(L0z) + j0z;
  8.     o0z = o0z.slice(0, 32);
  9.     var n0z, f0z = [[], [], [], [], []], Q0z = {}, N0z = 0;
  10.     X0z = 0;
  11.     for (var i0z = o0z.length; i0z > X0z; X0z++){
  12.         n0z = o0z.charAt(X0z), Q0z[n0z] || (Q0z[n0z] = 1, f0z[N0z].push(n0z), N0z++, N0z = 5 == N0z ? 0 : N0z);
  13.     }
  14.     var y0z, v0z = k0z, B0z = 4, x0z = "", I0z = [1, 2, 5, 10, 50];
  15.     while ( v0z > 0) {
  16.         v0z - I0z[B0z] >= 0 ? (y0z = parseInt(Math.random() * f0z[B0z].length, 10),
  17.         x0z += f0z[B0z][y0z], v0z -= I0z[B0z]) : (f0z.splice(B0z, 1),
  18.         I0z.splice(B0z, 1), B0z -= 1);
  19.     }
  20.     return x0z;
  21. }
复制代码
获取 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) 的值其实就是轨迹。

这段控制流还原一下就变成这样了:
  1. function getF7z(track){
  2.     var o5r = 6;
  3.     for (var N1z, X1z = s6z(track), f1z = [], B1z = [], o1z = [], t1z = 0, j1z = X1z.length; t1z < j1z; t1z++){
  4.         if (o5r * (o5r + 1) % 2 + 8) {
  5.             N1z = u6z(X1z[t1z]),
  6.             N1z ? B1z.push(N1z) : (f1z.push(O6z(X1z[t1z][0])),
  7.             B1z.push(O6z(X1z[t1z][1]))),
  8.             o1z.push(O6z(X1z[t1z][2]));
  9.             o5r = o5r >= 17705 ? o5r / 3 : o5r * 3;
  10.         }
  11.     }
  12.     return f1z.join("") + "!!" + B1z.join("") + "!!" + o1z.join("");
  13. }
  14. function s6z(F6z){
  15.     for (var Y6z, g6z, a6z, E6z = [], D6z = 0, P6z = [], J6z = 0, l6z = F6z.length - 1; J6z < l6z; J6z++) {
  16.         Y6z = Math.round(F6z[J6z + 1][0] - F6z[J6z][0]),
  17.         g6z = Math.round(F6z[J6z + 1][1] - F6z[J6z][1]),
  18.         a6z = Math.round(F6z[J6z + 1][2] - F6z[J6z][2]),
  19.         P6z.push([Y6z, g6z, a6z]),
  20.         0 == Y6z && 0 == g6z && 0 == a6z || (0 == Y6z && 0 == g6z ? D6z += a6z : (E6z.push([Y6z, g6z, a6z + D6z]), D6z = 0));
  21.     }
  22.     return 0 !== D6z && E6z.push([Y6z, g6z, D6z]), E6z;
  23. }
  24. function O6z(r6z){
  25.     var d6z = "()*,-./0123456789:?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqr"
  26.       , m6z = d6z.length
  27.       , Z6z = ""
  28.       , H6z = Math.abs(r6z)
  29.       , W6z = parseInt(H6z / m6z);
  30.     W6z >= m6z && (W6z = m6z - 1), W6z && (Z6z = d6z.charAt(W6z)), H6z %= m6z;
  31.     var q6z = "";
  32.     return r6z < 0 && (q6z += "!"), Z6z && (q6z += "$"), q6z + Z6z + d6z.charAt(H6z);
  33. }
  34. function u6z(R6z){
  35.     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++){
  36.         if (R6z[0] == z6z[h6z][0] && R6z[1] == z6z[h6z][1]){
  37.             return "stuvwxyz~"[h6z]
  38.         }
  39.     }
  40.     return 0;
  41. }
复制代码
以上只是 F7z 第一次生成的地方,后面还有二次处理,如下图所示:

同样跟进去,三个传入的参数分别是第一次生成的 F7z、get.php 请求返回的 c 和 s 参数。

同样是一段控制流,还原后如下:
  1. function getF7z2(Q1z, v1z, T1z){
  2.     var i1z, x1z = 0, c1z = Q1z, y1z = v1z[0], k1z = v1z[2], L1z = v1z[4];
  3.     while (1){
  4.         if (i1z = T1z.substr(x1z, 2)){
  5.             x1z += 2;
  6.             var n1z = parseInt(i1z, 16)
  7.               , M1z = String.fromCharCode(n1z)
  8.               , I1z = (y1z * n1z * n1z + k1z * n1z + L1z) % Q1z.length;
  9.             c1z = c1z.substr(0, I1z) + M1z + c1z.substr(I1z);
  10.         }else {
  11.             return c1z
  12.         }
  13.     }
  14.     return Q1z
  15. }
复制代码
至此 aa 参数分析完毕!
获取 ep 值

ep 的值就是一个版本号,此处是 {'v': '6.0.9'},写死即可。

获取 rp 值

自此 Y7z 的第一步生成就分析完毕了,注意接下来还有一步,向 Y7z 里新增了一个 rp 参数:

这个值的组成看起来很长,实际上是将 gt、challenge 前 32 位以及 passtime 相加经过 MD5 加密后得到的。
  1. 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,如下图所示:

直接扣代码,或者直接使用三代的代码即可:
[code]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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

张春

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表