声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

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

逆向目标

  • 目标:某亭雷池 waf 验证码逆向分析

  • 类型:无感、直滑

  • 网站:aHR0cHM6Ly95dW5wYW4xLndhbmcv

抓包分析

当我们打开控制台,拖动滑块后,会发现页面显示 当前环境正在被调试:

不打开控制台就没有,说明是有控制台检测的,我们先来解决这个。

先搜索关键字 devtools 发现,在 v2/challenge.js 文件中,有几处位置,我们重点分析下:

发现关键字 devtoolsFormatterChecker,控制台格式检查器:

  • 方法一,将这个检查器 return f.a 注释替换就可以成功过它的控制台检测了。

我们从断点这个位置来分析,字面意思就是是否被打开,是否被启动:

我们来看下是哪里调用的这两个函数,通过堆栈定位到下图处:

这段代码就是用于检测控制台(DevTools)是否打开的轮询函数_detectLoop。通过周期性地调用不同的检测器(checker)判断控制台是否处于开启状态,并在状态变化时触发广播。

查看 this 就能看到它们检测器对象,每隔 500ms 检查一次:

  • 方法二,重写调用轮询函数 _detectLoop 处的方法:

断在这个位置上,将 this._detectLoop = function() {}; 就行了,或者将调用 launch 方法的地方进行重写也可以,方法还有很多,重点在于尝试和分析,用到了这个开源项目,感兴趣的可以学习一下:

https://github.com/AEPKILL/devtools-detector

我们继续抓包分析,加载的文件不多,第一次请求首页,状态码 468,验证成功后,cookie 携带 sl-challenge-jwt 值就可以成功返回数据,状态码 200,我们来看看哪些参数需要分析。

先看 issue 接口:

  • client_id:搜索发现是 第一次请求首页 468 响应内容里的。

返回内容:

再看 verify 接口:

  • 一些明文环境值;

  • visitorId:访问者 ID,需要分析,可以先写死;

  • issue_id:由 issue 接口返回;

  • result:需要分析;

  • serials:轨迹值,最后发现固定写死就行,这就是无感和直滑唯一的区别,还没有校验。

返回成功的内容就是,其中 jwt 值就是我们需要携带的 sl-challenge-jwt 值:

逆向分析

我们先来解决 visitorId 参数,通过搜索即可定位到,在  v2/challenge.js 文件中:

通过异步调用返回,逐步分析,进到 i.load 方法中去:

发现它使用了新版的 fingerprintjs v3 库来生成的设备指纹:

官方: https://github.com/fingerprintjs/fingerprintjs

在线获取当前浏览器指纹网址:https://fingerprintjs.github.io/fingerprintjs/

const FingerprintJS = require('@fingerprintjs/fingerprintjs');

FingerprintJS.load().then(fp => {
  fp.get().then(result => {
    console.log(result.visitorId);
  });
});

不过该库,更适配于浏览器环境,我们可以用低版本的 fingerprintjs2 来伪造生成,对 nodejs 环境的适配性更好:

const Fingerprint2 = require('fingerprintjs2');

Fingerprint2.get(components => {
  const values = components.map(component => component.value);
  const murmur = Fingerprint2.x64hash128(values.join(''), 31);
  console.log(murmur);
});

再来解决 result 加密参数。

根据 api/verify 接口我们来下 xhr 断点,然后跟堆栈就发现其生成位置:

发现是 result 的结果是 e(t.data) 的返回值,然而我们在此处下断点,发现并没有断下来,查询文件后发现是一个临时生成的本地对象链接:

我们可以通过搜索关键字 createObjectURL 或者 HOOK 的手段来定位其创建的位置:

originalCreateObjectURL = URL.createObjectURL;
URL.createObjectURL = function(obj) {
  console.trace("Blob 被创建了", obj);
  debugger;
  return originalCreateObjectURL.call(this, obj);
};

定位到如下位置,就在我们 xhr 断点的前面,通过 v2/calc.js 文件返回的内容进行创建的:

所以我们只需要修改 v2/calc.js 文件的内容,就可以影响其创建 blob: 对象链接,那我们在需要断点的位置上,添加 debugger,然后再用浏览器替换,即可断住:

发现传入的参数是 由 issue 接口返回 data 值,我们再跟到 e 函数里,添加 debugger:

发现是走的 wasm 逻辑,逻辑比较简单,通过 e.data.wasm  实例化了一个 WebAssembly 模块,然后导出函数集合,对导入的 e.data.data 进行处理,最后返回的结果就是我们需要的 result 加密参数,我们往上跟一步,来看 e.data 是怎么生成的,发现分别是 v2/calc.wasm 接口 和 v2/api/issue 接口里的响应值:

我们直接用 nodejs 环境进行复现,将 wasm 文件双击保存到本地调用:

const fs = require('fs');


function get_result(data){
    wasm = fs.readFileSync('./calc.wasm');
    e = {
        data: {
            "action": "calc",
            "data": {
                "data": data,
                "issue_id": ""
            },
            "wasm": wasm
        }
    }
    var result;
    var flag = false;
    WebAssembly.instantiate(e.data.wasm).then(function(t) {
        result = function(e) {
            return t.instance.exports.reset(),
            e.map(function(e) {
                return t.instance.exports.arg(e)
            }),
            Array(t.instance.exports.calc()).fill(-1).map(function() {
                return t.instance.exports.ret()
            })
        }(e.data.data.data);
        flag = true;
    }).catch(function(e) {
        console.log(e);    
    })
    while (!flag) {
        require('deasync').sleep(100);
    }
    ;
    return result;
};

console.log(get_result([99, 35, 92, 48, 61, 31, 18, 43, 3, 54, 48, 22, 62, 50]));
// 结果: [ 17, 26, 51, 20 ]

分析的时候还发现,它还有一套兼容的算法暴露出来了,虽然浏览器走的 wasm 这套,但暴露出来的算法,得到的加密一致,两套均可使用:

function f(e) {
    for (var t = 1, n = e.reduce(function(e, t) {
        return e + t
    }, 0), r = (6 + e.length + n) % 6 + 6; r--; )
        t *= 6;
    t < 6666 && (t *= e.length),
    t > 0x3f940aa && (t = Math.floor(t / e.length));
    for (var o = 0; o < e.length; o++)
        t += Math.pow(e[o], 3),
        t ^= o,
        t ^= e[o] + o;
    for (var f = []; t > 0; )
        f.unshift(63 & t),
        t >>= 6;
    return f
};

console.log(f([99, 35, 92, 48, 61, 31, 18, 43, 3, 54, 48, 22, 62, 50]));
// 结果: [ 17, 26, 51, 20 ]

结果验证

点个推荐♡求求你啦

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐