banner
约 2,400 字
8 分钟

【JS逆向练习】极验三代滑块模式验证码逆向分析(上)

无标签

作者声明:文章仅供学习交流与参考!严禁用于任何商业与非法用途!否则由此产生的一切后果均与作者无关!如有侵权,请联系作者本人进行删除!

前言#

上一篇,分析了一键通过模式的验证,这次准备把对滑块验证分析的过程更详细记录下来

地址:aHR0cHM6Ly93d3cuZ2VldGVzdC5jb20vZGVtby9zbGlkZS1mbG9hdC5odG1s

流程分析#

blog image
blog image

首先进入 验证码 __ 地址,然后点击弹出滑块,完成并通过验证。整个过程抓包,发现共涉及了5个请求。

  • 1、register-slide

blog image
blog image

主要返回了gt和challenge,下一个接口get.php的主要参数。

  • 2、get.php

blog image
blog image

关键入参有:gt、challenge、w(第一个w,记为w1),返回c和s后续会用到。

  • 3、ajax.php

blog image
blog image

点击按键验证,会弹出滑块窗口,然后调用ajax.php。 入参同样带了gt、challenge、w(第二个w,记为w2),接口返回验证码的 类 __ 型,返回的结果虽然没用到,但是如果不请求或者请求不带 w 后面都会报错。 第二个w如果如果生成的结果不对,也会影响最后一步ajax.php的验证,比如返回forbidden。

  • 4、get.php

blog image
blog image

这里又会有一个get.php,仍然返回c和s以及滑块图片和底图,34位的challenge。

  • 5、ajax.php

blog image
blog image

最后,滑动完成拼图,得到验证结果 validate 参数,这个参数就是后续登录的令牌,在后续操作的请求中会用到。 入参涉及第三个w、challenge(第二次调用get.php返回)、gt。

逆向分析#

w1#

由上分析知道,第一个w参数在第一次调用get.php时候涉及了,所以首先找到第一个w的生成入口。这里直接搜"\u0077"

blog image
blog image

"w"=i+r,先分析第一个r值来源,在6970行var r = t[$_CEEJl(1169)]()

blog image
blog image

接下来进入这个t[$_CEEJl(1169)]函数,看下在哪生成的值。

blog image
blog image

这里生成t并返回的 var t = new X()$_CGCJs(17)](e));

这里是个md5加密,参数是16位随机字符串,自己随便生成就行。不过后续还会用到。要保持一致都用这个16位随机字符串。

blog image
blog image
blog image
blog image

加密方法可以扣代码,或者自己引库复现。我这里是赋值到全局变量 window下 了,省点事。代码参考

window
window

r复现完成后,还剩个i,接下来继续看怎么生成的。

blog image
blog image
  • 发现 i = p[$_CEEJl(1151)](o),然后里面传个o参数

o = $_BF_()[$_CEEIh(1184)](he[$_CEEIh(491)](t[$_CEEJl(363)]), t[$_CEEIh(1119)]())

  • 所以需要先看下o怎么生成的。

o = $_BF_()[“encrypt1”](he["stringify"](t[$_CEEJl(363)]), t[$_CEEIh(1119)]()),先简单还原的混淆的变量方便观察。

  • 发现也是个加密,然后把第一个参数t[$_CEEJl(363)]转义成json格式,第二个参数t[$_CEEIh(1119)]()其实就是刚才的16位随机字符串。

  • 我们把t[$_CEEJl(363)]内容贴出来,看下都有哪些内容。

blog image
blog image

这里只有gt、challenge是动态的,其它固定即可。

  • 这样分析下来入参明确的,把o的加密代码扣下来或复现即可,我这边还是扣代码。参考代码

blog image
blog image
blog image
blog image
blog image
blog image

至此第一个w还原完成。

w2#

第二个w,是第一次调用ajax.php涉及到了。这里只能跟栈,不能搜"\u0077"找到入口了。 跟栈过程跳过了,具体可以看上一篇一键通过验证的分析,在$_CDIo这里。

blog image
blog image

w是n对象下的一个属性,是通过t的"$_CEAw"属性赋值的。需要分析下在什么时候"$_CEAw"赋值了

blog image
blog image

可以看到在t[$_CFCIL(1160)]()时候,还没有"$_CEAw",执行后有了,所以重点在t[$_CFCIL(1160)]()

blog image
blog image
blog image
blog image

进到t[$_CFCIL(1160)]()函数内部找下相关生成位置,在7233行,发现有赋值的地方

blog image
blog image
  • i[$_CFFII(1147)] = p[$_CFFII(1151)](c[$_CFFII(17)](r, i[$_CFFJL(1119)]()));分析下这行代码

  • i[$_CFFII(1147)] = p["$_HEz"](c["encrypt"](r, i[$_CFFJL(1119)]()));还原下部分混淆名称发现也是个加密,参数是r和i[$_CFFJL(1119)](),r是个对象,i[$_CFFJL(1119)]是16位字符串,还是w1时候的那个。

  • 接下来看下r的生成位置,这里很明显就在上两行。

r = $_CFGDr(793) + i[$_CFGEA(1183)] + $_CFGEA(1145) + n(o[$_CFGEA(22)]() + n(n[$_CFGEA(22)]()) + n(e[$_CFGEA(22)]())) + $_CFGEA(1193);

blog image
blog image

下个断点,看下是哪些内容。

blog image
blog image

发现也是一些参数,贴出来看下。

blog image
blog image

这里rp是动态的,passtime固定,但后面涉及的时候要保持一致。重点rp看下怎么还原。发现是在i[$_CFGEA(1183)]里包含的。

blog image
blog image

继续看i[$_CFGEA(1183)]在哪赋值的,i[$_CFGEA(1183)]还原混淆名字是i["$_CEC_"],在7187这行里rp生成并且赋值"$_CEC_"里,这里其实比较绕,可以手动还原一些代码,更容易理解和找到。

blog image
blog image

rp=MD5(gt+challenge+passtime),passtime和上述的一样。把拼接内容md5加密就可以了,我这边引库。

blog image
blog image

参考代码

JavaScript
const CryptoJS = require("crypto-js");

function md5(plaintext) {
    let ciphertext = CryptoJS.MD5(plaintext).toString();
    return ciphertext;
}

gt = "019924a82c70bb123aae90d483087f94";
challenge = "1e3d427363669cb56d3741eee8c8534d"
pass_time = 2701
plaintext = gt + challenge + pass_time;
console.log(md5(plaintext));


至此rp拿到,r还原完成,回到w生成入口。发现还需要把r加密下,i[$_CFFJL(1119)]()是16位字符串(和前面一样)也是RSA加密的密钥,向量是0000000000000000。

blog image
blog image

加密代码如果引库的话,向量可以自己跟进去加密函数内,拿到iv字节 数组 __ 还原下。

blog image
blog image

贴下工具代码

JavaScript

const ivData = {
    "words": [808464432, 808464432, 808464432, 808464432],
    "sigBytes": 16
};

// 将 `words` 转换为字节数组
let byteArray = [];
for (let i = 0; i < ivData.words.length; i++) {
    let word = ivData.words[i];
    byteArray.push((word >> 24) & 0xFF);  // 获取字节
    byteArray.push((word >> 16) & 0xFF);
    byteArray.push((word >> 8) & 0xFF);
    byteArray.push(word & 0xFF);
}

// 将字节数组转换为字符串
let ivString = String.fromCharCode.apply(null, byteArray);

console.log("IV as String:", ivString);

我这边依旧是扣代码,和w1的加密一样就是调用的加密方法不一样,w1调用的encrypt1,w2是encrypt。贴下参考代码

blog image
blog image

至此第二个w分析完成。

w3#

第三个w是在slide这个js里,可以跟栈或搜"\u0077"找到。

blog image
blog image

w=h+u,先分析u的还原,是个r函数,进入内部,发现var e = new U()[$_CBFJN(392)](this[$_CBGAK(744)](t));这里生成。

blog image
blog image

参数还是16为字符串,用之前的就可以。

blog image
blog image

加密代码,还是扣一下,把U这个函数扣下来,缺什么环境再补下。

blog image
blog image

u完成之后,还剩一个h需要还原。发现h是由m函数生成,入参是l,l是个加密参数是o r[$_CAIAK(744)]()是16位字符串,和之前一样即可。

blog image
blog image

先看下o的内容,这里passtime,aa,ep,rp,userresponse都是动态的需要还原,其余固定。 注:首先passtime是滑动轨迹里的滑动时间,一定要和最后轨迹数组的最后一个元素的z相同

blog image
blog image

接下来先看rp的还原,就在u的上方,很容易观察到。o=gt+challenge(32位的)+passtime challenge在第二次调用get.php返回的是34位的,在32位的后面多了2位,前32位一样

blog image
blog image

rp完成后,继续看ep。往上翻,就能找到o的生成。

blog image
blog image

ep是个r函数生成的,进入函数内部看下。返回一个对象

blog image
blog image

v:版本号 $_BIE:布尔类型,固定false me:布尔类型,固定true tm:动态生成 td:固定-1 所以继续分析tm,new了一个bt,先把bt扣一下。

blog image
blog image

里面 $_BGGX属性也需要扣下

blog image
blog image

贴下代码

JavaScript
//扣代码ep参数
function bt() {
    var $_DBFCb = mwbxQ.$_DW()[0][13];
    for (; $_DBFCb !== mwbxQ.$_DW()[3][13];) {
        switch ($_DBFCb) {
        }
    }
}

bt['prototype'] = {
    "$_BGGX": function () {
        var $_BJBES = mwbxQ.$_Cg,
            $_BJBDB = ["$_BJBHN"].concat($_BJBES),
            $_BJBFe = $_BJBDB[1];
        $_BJBDB.shift();
        var $_BJBGH = $_BJBDB[0];
        return window[$_BJBES(678)] && window[$_BJBFe(678)][$_BJBFe(655)] && this[$_BJBES(614)]() || -1;
    },
    "$_BGHj": function () {
        var $_BJBJc = mwbxQ.$_Cg,
            $_BJBID = ["$_BJCCA"].concat($_BJBJc),
            $_BJCAJ = $_BJBID[1];
        $_BJBID.shift();
        var $_BJCBm = $_BJBID[0];
        var t = window[$_BJCAJ(678)][$_BJCAJ(655)];
        return {
            "a": t[$_BJCAJ(662)],
            "b": t[$_BJBJc(633)],
            "c": t[$_BJBJc(672)],
            "d": t[$_BJBJc(691)],
            "e": t[$_BJCAJ(653)],
            "f": t[$_BJCAJ(688)],
            "g": t[$_BJCAJ(658)],
            "h": t[$_BJCAJ(668)],
            "i": t[$_BJCAJ(619)],
            "j": t[$_BJCAJ(684)],
            "k": t[$_BJBJc(613)],
            "l": t[$_BJBJc(624)],
            "m": t[$_BJCAJ(638)],
            "n": t[$_BJCAJ(689)],
            "o": t[$_BJCAJ(628)],
            "p": t[$_BJCAJ(690)],
            "q": t[$_BJCAJ(656)],
            "r": t[$_BJCAJ(645)],
            "s": t[$_BJCAJ(696)],
            "t": t[$_BJBJc(620)],
            "u": t[$_BJCAJ(609)]
        };
    }

};

blog image
blog image

结果验证没问题,ep完事继续userresponse还原。

blog image
blog image

ep是H函数生成,参数t是滑动距离,i[$_CAHJS(134)]是34位challenge。扣下H函数即可

JavaScript
function H(t, e) {
    var $_DAJEI = mwbxQ.$_DW()[9][13];
    for (; $_DAJEI !== mwbxQ.$_DW()[0][11];) {
        switch ($_DAJEI) {
            case mwbxQ.$_DW()[6][13]:
                for (var n = e[$_CJES(187)](-2), r = [], i = 0; i < n[$_CJES(192)]; i++) {
                    var o = n[$_CJFt(120)](i);
                    r[i] = 57 < o ? o - 87 : o - 48;
                }
                n = 36 * r[0] + r[1];
                var s,
                    a = Math[$_CJES(158)](t) + n,
                    _ = [[], [], [], [], []],
                    c = {},
                    u = 0;
                i = 0;
                $_DAJEI = mwbxQ.$_DW()[6][12];
                break;
            case mwbxQ.$_DW()[0][12]:
                for (var l = (e = e[$_CJES(187)](0, -2))[$_CJFt(192)]; i < l; i++) c[s = e[$_CJES(125)](i)] || (c[s] = 1, _[u][$_CJES(137)](s), u = 5 == ++u ? 0 : u);
                var h,
                    f = a,
                    d = 4,
                    p = $_CJFt(2),
                    g = [1, 2, 5, 10, 50];
                while (0 < f) 0 <= f - g[d] ? (h = parseInt(Math[$_CJFt(21)]() * _[d][$_CJFt(192)], 10), p += _[d][h], f -= g[d]) : (_[$_CJES(115)](d, 1), g[$_CJES(115)](d, 1), d -= 1);
                return p;
                break;
        }
    }
}

试下结果没问题,继续最后一个aa

blog image
blog image
blog image
blog image

aa是由e赋值的,e是当前函数的入参,需要看上一个栈找下

发现是l生成的

blog image
blog image
JavaScript
l = n[$_DAAAU(985)][$_CJJJU(1075)](n[$_CJJJU(985)][$_CJJJU(1073)](), n[$_CJJJU(67)][$_CJJJU(1033)], n[$_DAAAU(67)][$_CJJJU(345)]);
伪代码还原下,发现里面是trace滑动轨迹,c和s是第二次调用get.php返回的。
l = n[$_DAAAU(985)][$_CJJJU(1075)](trace, c, s);

END