过一关,斩六将(本文方法适用于1~6题)。但大家最好不要依赖于这种讨巧的方法,否则会失去“体会不同混淆方式”的机会。
一、困境
这道题的代码经过混淆后,调试起来非常困难,就像加了很多无效的花指令。很多事件都会走同一段代码,其中的变量在不同事件回调中担任不同的角色,所以,即便定位到发起请求的地方,也很难顺藤摸瓜找到前后的逻辑代码。
不仅不容易找出token的生成方式,同时,接口响应后的回调函数也很难找到。
二、思路
经过一番思考,感觉从混淆后的代码上面很难有所突破。所以,最终考虑:当服务器返回响应数据时,想办法将数据拦截下来进行使用。
具体的操作:
1)首先修改 XMLHttpRequest 原型链上的 open 方法,当方法被调用时,在open中可以通过 this 获取到当前的 XMLHttpRequest 对象;
2)在 XMLHttpRequest 对象上绑定 onreadystatechange 事件,我们知道,在一个请求中,readyState 有五个状态值,即0、1、2、3、4,当 readyState 值为4时,代表已经拿到全部的响应数据。所以,我们在 onreadystatechange 的回调函数中进行判断,当事件对象中的 readyState 值为4,我们就将 responseText 属性中的数据梳理出来,以便后面求和。
三、破局
按照上述思路完成的代码如下:
// 变量声明:请求成功的次数
let count = 0;
// 变量声明:所有请求到的数据
let dataList = [];
// 函数声明:XMLHttpRequest jshook
function hook(func, name) {
const origin = func;
return function () {
if (arguments[1] === '/api/match2023/3') {
this.onreadystatechange = function (e) {
if (e.target.readyState === 4) {
let res = JSON.parse(e.target.response)
let data = (res.data || []).map(v => v.value)
dataList = [...dataList, ...data];
count++;
if (count >= 5) {
let sum = dataList.reduce((a, b) => (a + b), 0);
console.log('sum:', sum)
}
}
console.log(e.target.readyState, e.target.response)
}
}
return origin.apply(this, arguments);
}
}
XMLHttpRequest.prototype.open = hook(XMLHttpRequest.prototype.open, 'open>>>');
// 开始运行:循环依次发送各个页面的数据请求
for (let i = 1; i <= 5; i++) {
setTimeout(() => {
call(`${i}`) // call是请求某页面数据的全局函数
}, i * 200)
}
值得一提的是,这里有个小坑,因为页码应该会被用来和时间戳做拼接,然后参与计算token值,所以这里的页码(即call函数的传参)必须是字符串,否则,会导致token计算错误。