本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!
本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K哥爬虫】联系作者立即删除!
aHR0cHM6Ly95dW5wYW4xLndhbmcv
当我们打开控制台,拖动滑块后,会发现页面显示 当前环境正在被调试
:
不打开控制台就没有,说明是有控制台检测的,我们先来解决这个。
先搜索关键字 devtools
发现,在 v2/challenge.js
文件中,有几处位置,我们重点分析下:
发现关键字 devtoolsFormatterChecker
,控制台格式检查器:
return f.a
注释替换就可以成功过它的控制台检测了。我们从断点这个位置来分析,字面意思就是是否被打开,是否被启动:
我们来看下是哪里调用的这两个函数,通过堆栈定位到下图处:
这段代码就是用于检测控制台(DevTools)是否打开的轮询函数 _detectLoop
。通过周期性地调用不同的检测器(checker
)判断控制台是否处于开启状态,并在状态变化时触发广播。
查看 this
就能看到它们检测器对象,每隔 500ms
检查一次:
_detectLoop
处的方法:断在这个位置上,将 this._detectLoop = function() {};
就行了,或者将调用 launch
方法的地方进行重写也可以,方法还有很多,重点在于尝试和分析,用到了这个开源项目,感兴趣的可以学习一下:
我们继续抓包分析,加载的文件不多,第一次请求首页,状态码 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 ]
相关代码,会分享到知识星球当中,需要的小伙伴自取,仅供学习交流~
【验证码逆向专栏】某亭雷池 waf 验证码逆向分析
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。