极验滑块逆向

默认分类·爬虫与逆向 · 2023-09-03 · 3502 人浏览

要点解析

访问 https://geetest.com/demo,可以看到很多的demo,这些模式混淆方式大多大差不差,因此这里我们选择最简单的滑块slide-bind模式来进行测试,这也是b站采用的方式(*现已更换为点选)

image-20240902233055384

验证码逆向和加速乐这类的waf参数逆向过程不一样,需要事先注意一下提交的参数是否为本地生成,不要一上来就直接到源码里搜参数,然后分析半天,最后发现参数在xhr的请求结果里。验证码一般在起始阶段,会获取一个被称为challenge的东西,这个一般就是网络请求返回的验证码题目标识,我们需要直接发送请求,先得到对应的数据,再去定位剩余加密参数。如图,这里的challenge和gt就是由之前的/register-slide得到的数据,因此我们在这里只需要重点关注w参数

image-20240902234421519

常用的查找加密参数的方式有搜索、跟栈、hook,我们该如何开始入手呢?这就不得不提到验证码有个特性了,目前遇到的大多数验证码都会利用jsonp来实现跨域请求,jsonp会携带请求参数请求接口,与普通的http请求不同的是,浏览器会自动拿返回的数据,执行指定的回调函数,因此我们可以通过直接hook jsonp的回调,来查看核心的执行逻辑。

为什么会使用jsonp呢?因为浏览器由于同源策略,无法直接请求与host不同的域名,因此,各大验证码平台,为了非侵入式直接嵌入,只能通过jsonp来请求自家的服务器,否则就需要通过后端来转发数据,这样会增加和项目的耦合性,也不利于在线升级安全策略

如何分辨jsonp请求呢?目前jsonp有几个硬指标:

  1. 响应类型是script
  2. get请求,只有get方法才会被浏览器允许跨域
  3. 请求的负载中含有callback参数,响应的内容中是callback函数和参数

上图就是一个经典的jsonp案例,对于jsonp来说,是通过修改script元素的src实现的代码注入,因此,我们可以通过hook script元素的src属性,获取到对应的url,这样我们就能找到这个网络请求参数是在哪个位置生成的了

hook思路

注意,需要hook原型链上的内容,否则会被检测。为了方便,我们可以hook HTMLScriptElement,这里我提供一套解决方案,注意,这里常规情况下,还需要对原型链上的toString方法进行hook,以防止格式化检测:

(function() {
    const originalSetAttribute = HTMLScriptElement.prototype.setAttribute;

    // 重写原型链上的 setAttribute 方法,这里也可以hook Document对象
    HTMLScriptElement.prototype.setAttribute = function(name, value) {
        if (name === 'src') {
            debugger; // 在这里打断点
            console.log(`src被更改: ${value}`);
        }
        return originalSetAttribute.apply(this, arguments);
    };

    // 使用 Object.defineProperty 拦截 src 属性的赋值操作
    Object.defineProperty(HTMLScriptElement.prototype, 'src', {
        set: function(value) {
            debugger; // 在这里打断点
            console.log(`src被更改: ${value}`);
            this.setAttribute('src', value);
        },
        get: function() {
            return this.getAttribute('src');
        },
        configurable: true
    });
})();

这里有同学可能会问,为什么除了我们平常常用的Object.defineProperty来拦截setter,还要重写HTMLScriptElement.prototype.setAttribute

因为使用 Object.defineProperty只能拦截 src 属性的赋值操作,如果有心人通过 setAttribute 方法设置 src 属性时,就无法拦截到了,采用我们上述的方案这样可以确保在所有情况下都能拦截对 script 标签 src 属性的赋值操作,并执行自定义逻辑。

image-20240903005632480

这里一下就hook到了具体的位置,n[e]很明显是script标签的src属性,而这里的e对象是又一个混淆后的方法生成的,这里$_BFIEk是一个字符串解密函数,返回$_BCBk,那么这里实际上就是var e=this.$_BCBK

image-20240903014527495

但是这里有一个问题,this获取的是闭包指向的外部对象,而外部对象的属性名全部都是混淆过后的,这里如果不使用ast将字符串解密并回填的话,分析将十分痛苦。不过在平常ast的经验中可以得出,字符串加密时,通常使用的是同一个字符串大数组,可以大胆猜想,420所代表的字符串是一致的。果然,我们通过对420的搜索搜索到了另一处内容

image-20240903015822003

对其进行插桩日志查询,发现了非常振奋人心的事实,这个插桩点正是我们要寻找的内容,这里不仅输出了jsonp的参数,还输出了手势轨迹,说明这里确实是关键点

image-20240903015551036

对此处进行插入条件断点 t.w !== undefined,等到断点断出含w的t后,一路跟栈,这里有手就行省略一万字,得出w生成是由h和u拼接而成的。(定睛一看,原来这里直接把w unicode化了,早知道直接搜索\u0077了😭😭😭)

image-20240903020628024

u参数

看一下u的生成方式,这里单步进入即可,一行一行向下走,直到return,可以发现,这段函数里面有大量的死代码用于增加分析难度,实际并没有作用。有用的执行内容仅为 var e = new U()[$_CBFJN(392)](this[$_CBGAK(744)](t));,其中this[$_CBGAK(744)](t)为加密函数,而this[$_CBGAK(744)](t)返回的则是一段固定值,在这里屡次调用加密函数,每次都能得出不同的结果。这时候就要警觉了,这不是标准rsa算法的特征吗?

image-20240903021246193

跟进去查看一下,内容看起来还是比较长的,我们着重去找字符串解密的地方,看看能不能寻找到蛛丝马迹

image-20240903022931311

将字符串解密回填,发现调用了this.doPublic,那么说明调用了RSA库,this是一个rsa对象,那么这时候,我们从控制台找到this对象,然后从原型上找到setPublic方法,点击进去打断点,就可以找到公钥和幂模了,这里公钥就是这一长串,模是16进制的10001,也就是10进制的65537

image-20240903035304317

l参数

可以知道,l 参数的结果是将 gt["stringify"](o)r["$_CCEc"]() 加密后得到的,先来分析 r["$_CCEc"]() ,选中后跟进进去,跳转到了熟悉的位置,就是之前的 16 位随机字符串。 gt["stringify"](o) 返回的是 JSON 格式的数据,由 o 参数生成,而o参数则是轨迹以及环境信息生成。分析完 V[$_CAIAt(353)] 即完成,跟进打下断点,可得到初始向量 iv ,值为 “0000000000000000”,由此可知此为AES加密

引库复现

function aesV(o_text, random_str) {
    var key = CryptoJS.enc.Utf8.parse(random_str);
    var iv = CryptoJS.enc.Utf8.parse("0000000000000000");
    var srcs = CryptoJS.enc.Utf8.parse(o_text);
    var encrypted = CryptoJS.AES.encrypt(srcs, key, {
        iv: iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    });
    for (var r = encrypted, o = r.ciphertext.words, i = r.ciphertext.sigBytes, s = [], a = 0; a < i; a++) {
        var c = o[a >>> 2] >>> 24 - a % 4 * 8 & 255;
        s.push(c);
    }
    return s;
};

h参数

跟进 m[$_CAIAt(782)],为 e 中两个 value 值相加:

[](htt)

t 为传入的 l 参数,跟进到 this[$_GFJn(264)] 中,扣下来即可复现,校验结果一致,至此w参数还原完毕

  1. S0Sad 09-03

    大佬,最后一张图片没有上传上来,看不到啊

    1. dream (作者)  14 天前
      @S0Sad

      最后一张没啥内容,就是两个参数的简单相加

Theme Jasmine by Kent Liao