freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

【验证码逆向专栏】某验二代滑块验证码逆向分析
2023-03-09 18:02:34
所属地 湖北省

声明

本文章中所有内容仅供学习交流,抓包内容、敏感网址、数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除!

本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K哥爬虫】联系作者立即删除!

逆向目标

抓包情况

主页点击搜索就会跳出二代的验证码,netWebServlet.json的请求,会返回 challengegt

01

有个 get.php的请求,返回了一个新的 challenge,这个请求之后的操作,都要用这个新的 challenge,不然是验证不成功的,其他的还有验证码背景图片、乱序图片地址、cs等值,之前写过三代的文章,都是类似的,这里就不一一分析了。

02

然后是 ajax.php验证是否通过,通过之后返回一个 validate,请求里同样是需要我们逆向的 w参数:

03

04

然后同样还是 netWebServlet.json接口,带上 get.php请求返回的 challenge以及 ajax.php返回的 validate,请求拿到一个 name的字段。

05

06

后续的搜索数据,带上这个 name就行了:

07

逆向分析

搞过三、四代的都知道我们可以直接搜索 w的 Unicode 值 \u0077即可定位,但是二代则不是 Unicode,而是16进制的编码,搜索 \x77即可定位,当然按照正常流程,跟栈也能很容易找到加密的位置。

08

获取 H7z 值

从上图中可以知道 w的值为 r7z + H7z,先看 H7z

09

跟进这个方法,来到一大串控制流,这里还是推荐用 AST 还原一下,后续可能有一些循环啥的,硬跟的话容易出错,当然直接全部扣一把梭也是可以的,H7z的核心其实就是 RSA 加密随机字符串,三代四代都有,这里就不细讲了。

10

获取 r7z 值

然后就是 r7z,主要由以下两句代码生成:

11

q7z = n0B[M9r.R8z(699)](h7B[M9r.C8z(105)](Y7z), V7z[M9r.R8z(818)]())
r7z = p7B[M9r.R8z(260)](q7z)

可以看到其中有个变量 Y7z参与了计算,先来看看他是怎么来的,直接搜索即可定位,可以发现同样是16进制的编码,由五个值组成:userresponsepasstimeimgloadaaep

12

获取 userresponse 值

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

13

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

14

15

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

16

还原一下代码如下:

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。

17

18

获取 imgload 值

imgload也没啥特别的,从字面意思猜测应该是图片加载耗时,实测直接写死即可,或者整个随机值就行。

获取 aa 值

aa的值就是 F7z,如下图所示:

19

搜索 F7z,定位到下图所示的地方,向一个方法中传入了一个时间戳:

20

跟进去同样是 switch-case控制流,需要注意的是下图中 c7B[M9r.R8z(781)](M9r.R8z(764), K1z)的值其实就是轨迹。

21

这段控制流还原一下就变成这样了:

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第一次生成的地方,后面还有二次处理,如下图所示:

22

同样跟进去,三个传入的参数分别是第一次生成的 F7zget.php请求返回的 cs参数。

23

同样是一段控制流,还原后如下:

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'},写死即可。

24

获取 rp 值

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

25

这个值的组成看起来很长,实际上是将 gt、challenge 前 32 位以及 passtime 相加经过 MD5 加密后得到的。

Y7z["rp"] = md5(gt + challenge.slice(0, 32) + passtime)

26

上图中 I0B就是 MD5 方法,跟进去其实是可以看到很多 MD5 特征的,如下图所示:

27

自此 Y7z的值就搞定了,然后接着前面的看,也就是 q7z的值,同样和三四代一样的,encrypt是 AES 加密,Y7z经过 JSON.stringify()处理为字符串作为待加密对象,后面是 16 为随机字符串作为 AES 的 Key,注意这里的随机字符串应该和获取 H7z值时的随机字符串一致,不然是验证不成功的。

28

然后下一步就是获取 r7z的值,将上一步得到的 q7z经过一个方法进行处理,跟进方法,又是和三四代一样的,熟悉的 res + end,如下图所示:

29

直接扣代码,或者直接使用三代的代码即可:

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的值。

30

结果验证

测试过掉验证码抓取数据成功:

31

# 网络安全 # web安全 # 数据安全
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录