首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【验证码逆向专栏】某亭雷池 waf 验证码逆向分析

【验证码逆向专栏】某亭雷池 waf 验证码逆向分析

原创
作者头像
K哥爬虫
发布2025-05-06 18:36:49
发布2025-05-06 18:36:49
43500
代码可运行
举报
运行总次数:0
代码可运行
0
0

声明

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

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

逆向目标

  • 目标:某亭雷池 waf 验证码逆向分析
  • 类型:无感、直滑
  • 网站:aHR0cHM6Ly95dW5wYW4xLndhbmcv
7YRWvI.png
7YRWvI.png

抓包分析

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

7YRcIV.png
7YRcIV.png

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

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

7YRa0L.png
7YRa0L.png

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

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

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

7YRDxJ.png
7YRDxJ.png

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

7YRO4G.png
7YRO4G.png

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

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

7YRUkB.png
7YRUkB.png
7YR1Kt.png
7YR1Kt.png
  • 方法二,重写调用轮询函数 _detectLoop 处的方法:
7YRPPb.png
7YRPPb.png

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

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

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

先看 issue 接口:

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

返回内容:

7YRrNP.png
7YRrNP.png

再看 verify 接口:

7YRtaw.png
7YRtaw.png
  • 一些明文环境值;
  • visitorId:访问者 ID,需要分析,可以先写死;
  • issue_id:由 issue 接口返回;
  • result:需要分析;
  • serials:轨迹值,最后发现固定写死就行,这就是无感和直滑唯一的区别,还没有校验。

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

7YRX4f.png
7YRX4f.png

逆向分析

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

7YR2a4.png
7YR2a4.png

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

7YR6Mh.png
7YR6Mh.png

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

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

7YRZ39.png
7YRZ39.png
代码语言:javascript
代码运行次数:0
运行
复制
const FingerprintJS = require('@fingerprintjs/fingerprintjs');
​
FingerprintJS.load().then(fp => {
  fp.get().then(result => {
    console.log(result.visitorId);
  });
});

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

代码语言:javascript
代码运行次数:0
运行
复制
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 断点,然后跟堆栈就发现其生成位置:

7YRd5Y.png
7YRd5Y.png
7YReCH.png
7YReCH.png

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

7YY9kZ.png
7YY9kZ.png

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

代码语言:javascript
代码运行次数:0
运行
复制
originalCreateObjectURL = URL.createObjectURL;
URL.createObjectURL = function(obj) {
  console.trace("Blob 被创建了", obj);
  debugger;
  return originalCreateObjectURL.call(this, obj);
};

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

7YYkbU.png
7YYkbU.png

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

7YYBRq.png
7YYBRq.png
7YYTFs.png
7YYTFs.png

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

7YYpqa.png
7YYpqa.png

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

7YYLD7.png
7YYLD7.png

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

代码语言:javascript
代码运行次数:0
运行
复制
​
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 这套,但暴露出来的算法,得到的加密一致,两套均可使用:

7YY0MI.png
7YY0MI.png
7YYl3V.png
7YYl3V.png
代码语言:javascript
代码运行次数:0
运行
复制
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 ]

相关代码,会分享到知识星球当中,需要的小伙伴自取,仅供学习交流~

结果验证

7YY7AL.png
7YY7AL.png

【验证码逆向专栏】某亭雷池 waf 验证码逆向分析

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 声明
  • 逆向目标
  • 抓包分析
  • 逆向分析
  • 结果验证
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档