spiderdemo第5题
前言
继续搞spiderdemo第5题
网址如下
T5-数字采集加强一枪
https://www.spiderdemo.cn/authentication/ob1_challenge/?challenge_type=ob1_challenge

正文
分析
打开页面,打开开发者工具,点击下一页
可以发现许多debugger

这个js的名字是ob1_challenge.js,可以把这个文件下载下来,去掉debugger
但是,先不慌
因为这个里面,有许多的混淆,比如

其实笔者尝试去解过混淆,但感觉很痛苦
那么,需要一点好玩的东西,哈哈哈哈哈哈哈
先不慌,慢慢来
确定请求
可以在ob1_challenge.js里面发现如下的东西

这个可以发现的,没有混淆
看看关键信息
const signature = O0o0O0O0();
const url = `/authentication/api/ob1_challenge/page/${page}/?challenge_type=${encodeURIComponent(type)}&signature=${encodeURIComponent(signature)}`;
结合图片,可以知道
- 发送的是get请求
- 然后O0o0O0O0这个函数,不需要传参
- 请求头没有加密参数
- params有两个,一个是challenge_type,定值ob1_challenge,另一个就是O0o0O0O0这个函数执行的结果
正常情况下,应该是要看看O0o0O0O0这个函数在干什么,笔者已经干过了,硬解混淆
但实际上没必要,不需要解混淆。
做个mitmproxy的实验
笔者把ob1_challenge.js复制到本地的index.js中,再导入index.js到html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="index.js"></script>
<script>
console.log(O0o0O0O0())
</script>
</head>
</html>
运行,发现这个参数是可以打印出来的

但是,笔者点击刷新,是会被debugger

笔者想测试一下,把这个参数发送给某个端口,然后直接返回这个参数,看看可不可以
笔者首先引入jquery,额,fetch也行,这不重要。
index.html里面的代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
</head>
<script src="index.js"></script>
<script>
let signature= O0o0O0O0()
let baseUrl = 'http://localhost:8082'
$.post(baseUrl+'/a', signature, function (data) {
alert(data);
});
</script>
</html>
笔者发送一个post,和路由/a,
因为笔者发现空的路由会阻塞,所以随便搞了个路由。
给出mitmproxy中脚本的代码
from mitmproxy import http
def request(flow: http.HTTPFlow) -> None:
if flow.request.method=="POST":
flow.response = http.Response.make(
status_code=200,
content=flow.request.content, # 把请求体原样返回
headers={
"Content-Type": flow.request.headers.get("Content-Type", "text/plain"),
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "*",
}
)
post请求,直接原样返回。
结果如下

可以看到是拿到参数了的。
实际上就很简单了,直接发送请求就可以了
没有解混淆,不要管那些。
什么变种ob,这些那些的,笔者什么都不知道,没学过。
直接发请求
请求代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
</head>
<script src="index.js"></script>
<script>
let signature= O0o0O0O0()
let baseUrl = 'http://localhost:8082'
const url = baseUrl+`/authentication/api/ob1_challenge/page/1/?challenge_type=ob1_challenge&signature=${signature}`;
$.get(url, function (data) {
console.log(data);
});
</script>
<body>
</body>
</html>
是一个get请求,添加cookie
from mitmproxy import http
def request(flow: http.HTTPFlow) -> None:
if flow.request.method=="GET":
flow.request.headers["Cookie"]="sessionid=ee9chqk26l9emee8runioq14st0yzc83"
mitmweb --mode reverse:https://www.spiderdemo.cn -p 8082 -s proxy.py
结果如下

就没了,简简单单。
==============就这样,明天再说=========================================
获取100页数据


没问题,没有绕过debugger,也没有解混淆,哈哈哈哈哈哈哈哈哈。直接卡bug了
绕过debugger
首先,笔者发现一个好玩的事情,这个在pycharm里面可以调试html,

如果使用调试html

没想到,居然直接就在本地的index.js文件断下了

啊,哈哈哈哈哈,那这不是随便过了
这就不多说了
而且我在本地的index.js文件里面打断点,
调试模式,是不是也可以断下了,测试一下
笔者测试了一下

哦,想不到jetbrain系列的东西还有这个功能,笔者以前还不知道。
有点意思,发现了新大陆。
当然,比如过了debugger之后,
反而会出现报错

不是,哥们
既然如此
解混淆(一)
前面已经知道,可以在本地的js里面打断
那么,慢慢来,不慌
首先,在本地的js文件中,报错的地方打个断点
运行,结果如下

可以发现这个本质上是一个字符串String

但是,不对啊,没Data.String啊
Uncaught TypeError: Date.String is not a function
笔者在这里想了想,有问题。
为什么会是String,或者本身就是错误的代码
确实,有可能,
既然如此,不妨测试一下,
首先,一定是可以获取数据,加密一定是可以的,因为笔者已经过了。
恢复js文件本来的样子,不打开开发者工具,在会报错的地方打印一下,
如果说,出现了打印语句,就说明要执行,不打印,就说明,这个代码有问题,
如果打印了,还需要判断是不是String,修改如下

添加两个打印语句,结果如下

可以发现是now,
有点意思,当笔者过了debugger,打印的是String,没有过打印的是now,这对吗?
笔者细细想想,就说明绕过debugger的使用,没绕对,有问题,再看看关键代码
debugger ;const _fnStr_check = oO0oOO0O["toString"]();
const _first50_check = _fnStr_check["substring"](0, 50);
if (!_first50_check["includes"]("deb")) {
throw new Error("\u68C0\u6D4B\u5230\u53CD\u8C03\u8BD5\u4EE3\u7801\u88AB\u4FEE\u6539")
}
首先,有debugger。
然后使用oO0oOO0O的toString方法,得到_fnStr_check
然后再执行_fnStr_check是substring方法
接着下面一个if语句,判断是否包括deb,如果不包括,就报错,
所以,很明显,笔者发现了其中的漏洞
这个代码没有说是判断是debugger,而是判断deb。
那么,笔者进行如下操作
- 在pycharm中,在js文件中,使用快捷键ctrl+r,把debugger替换成deb()
- 然后在js文件最前面写一个deb函数
如下

function deb(){
}
什么都不做
再获取一下数据
<script>
let signature= O0o0O0O0()
let baseUrl = 'http://localhost:8082'
const url = baseUrl+`/authentication/api/ob1_challenge/page/1/?challenge_type=ob1_challenge&signature=${signature}`;
$.get(url, function (data) {
console.log(data);
});
</script>
结果如下

额,又绕过去,笔者感觉出这道题的人是希望笔者解混淆
但是笔者没解就过了,哎,代码写的有bug,哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈
也又可能就是这么搞的,算了,没有标准答案
解混淆(二)
都走到这里了,笔者要把它解开
OoO0ooo0
这是一个关键的函数,代码如下
function OoO0ooo0(index) {
deb() ;const _fnStr_check = OoO0ooo0["toString"]();
const _first50_check = _fnStr_check["substring"](0, 50);
if (!_first50_check["includes"]("deb")) {
throw new Error("\u68C0\u6D4B\u5230\u53CD\u8C03\u8BD5\u4EE3\u7801\u88AB\u4FEE\u6539")
}
for (var OooOooOO = 3764559944 - (3764559944 - 2); OooOooOO !== 587680605 - (587680605 - 0); ) {
deb() ;switch (OooOooOO) {
case 3793311298 - (3793311298 - 4):
return Oo000ooo;
OooOooOO = 2882498261 - (2882498261 - 0);
break;
case 2467507125 - (2467507125 - 2):
var o0O00Oo0 = encode_arr
, Oo000ooo = atob(o0O00Oo0[index]);
OooOooOO += 32641650 - (32641650 - 2);
break
}
}
}
在本地js文件中打个断点,看看是怎么回事
首先encode_arr是一个数组,去掉本身吗?截取前50个


![]()
额,不重要,
后面,计算数字加减
比如2467507125 - (2467507125 - 2),就是2
这个js代码里面有非常多这样的计算
计算出来,移动就可以了
因此,代码如下
function OoO0ooo0(index) {
return atob(encode_arr[index]);
}
O0o0O0O0
这个是最主要的函数,这个函数解混淆,额,全是数字计算,移动。
慢慢来,耐心点
笔者移动之后,结果如下

突然,笔者发现了一个关键的东西
(function (myarr, num) {
var OoOO00oo = function (nums) {
while (--nums) {
myarr.push(myarr.shift())
}
};
OoOO00oo(++num)
}
)(encode_arr, 16);
笔者去掉了前面1=的deb函数,这个好像在做偏移,把这个注释掉就会报错

哦哦哦哦哦,笔者明白了
搞这样,原来不是死代码。
修改一下这个偏移函数
如下
function rotateLeft(arr, num) {
let times = num + 1;
while (--times) {
arr.push(arr.shift());
}
}
rotateLeft(encode_arr, 16);
最终解开的代码
后续,继续美化代码
笔者是通过把一部分代码给AI和打断点两个操作
如果全部代码给AI,搞不了。
============过了不知道多久=====================
最终代码如下
function get_signature() {
/* ------------------ 配置 ------------------ */
const requestConfig = {
url: '/authentication/api/ob1_challenge/page',
method: 'get',
headers: {
common: {
Accept: 'application/json, text/plain, */*'
}
},
params: {
float: 'down',
genre: '5000',
device: 'iphone',
type: 'one',
brand: 'paid'
},
baseURL: '',
timeout: 30000
};
/* ------------------ 工具函数 ------------------ */
// 将字符串先 URI-Encode 再转成 Base64(浏览器 / Node 兼容)
function uriToBase64(str) {
const step1 = encodeURIComponent(str).replace(/%([0-9A-F]{2})/gi, (_, hex) =>
String.fromCharCode(parseInt(hex, 16))
);
if (typeof btoa === 'function') return btoa(step1); // 浏览器
if (typeof Buffer === 'function') return Buffer.from(step1, 'binary').toString('base64'); // Node
throw new Error('No Base64 encoder available');
}
// 简单的 XOR 加解密
function xorCipher(text, key) {
const base = 0;
return Array.from(text)
.map((ch, i) => {
const nCode = ch.charCodeAt(0);
const kCode = key[(i + 10) % key.length].charCodeAt(0);
return String.fromCharCode(nCode ^ kCode);
})
.join('');
}
// 读取 Cookie(若不存在则返回当前时间戳)
function getCookieValue(key) {
const reg = new RegExp('(^| )' + key + '=([^;]*)(;|$)');
const m = document.cookie.match(reg);
return m ? decodeURIComponent(m[2]) : Date.now().toString();
}
/* ------------------ 主流程 ------------------ */
// 1. 计算“syncd”偏移
const syncdValue = -getCookieValue('syncd');
const deltaTime = Date.now() - (syncdValue || 0) - 1661224081041;
// 2. 拼接 params 值并排序
const paramValues = Object.keys(requestConfig.params)
.filter(k => k !== 'analysis') // 跳过已存在的 analysis 字段
.map(k => requestConfig.params[k])
.sort()
.join('');
// 3. 多层追加字符串
const delimiter = '@#';
let payload = uriToBase64(paramValues);
payload += delimiter + requestConfig.url.replace(requestConfig.baseURL, '');
payload += delimiter + deltaTime;
payload += delimiter + 3; // 962613973 - (962613973 - 3)
// 4. 浏览器指纹 + 随机后缀
let fingerprintSeed;
try {
// 若“document”被劫持则主动退出
if ((Object.getOwnPropertyDescriptor(window, 'document').get + '').indexOf('native code') === -1) {
return false;
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.textBaseline = 'alphabetic';
ctx.fillStyle = '#f60';
ctx.fillRect(125, 1, 62, 20);
ctx.fillStyle = '#069';
ctx.fillText('Browser fingerprint', 2, 15);
ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
ctx.fillText('Browser fingerprint', 4, 17);
fingerprintSeed = 'xyz517cda96efgh' + Math.floor(Math.random() * 10);
} catch (e) {
fingerprintSeed = 'xyz517cda96efgh' + Math.floor(Math.random() * 9);
}
// 5. 再次混淆 + 时间戳
const obfuscated = uriToBase64(xorCipher(payload, fingerprintSeed));
const timeSuffix = new Date().getTime();
const final = obfuscated + timeSuffix;
// 6. 最终 Base64 一次并返回
return btoa(final);
}
没有偏移函数
没有debugger
没有O0o0O0O0
最终解混淆之后的代码如下
最后的测试
将解混淆的函数放到a.js文件中
index.html文件的内容如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
</head>
<script src="a.js"></script>
<script>
let signature= get_signature()
let baseUrl = 'http://localhost:8082'
const url = baseUrl+`/authentication/api/ob1_challenge/page/7/?challenge_type=ob1_challenge&signature=${signature}`;
$.get(url, function (data) {
console.log(data);
});
</script>
</html>
结果如下

没问题,解混淆成功。
说起来,这个东西还和cookie有关,但是笔者在本地没有cookie啊
笔者把那个cookie有关的函数的参数写死的。
没有cookie,可以使用的时间戳
也行
终于解决了
啊
啊
啊
解混淆是真的繁琐。
总结
笔者发现在pyharm可以对本地html进行调试,在js文件里面打断点也会被中断,这就很好了。
解混淆是繁琐的。就这样。
更多推荐

所有评论(0)