前言
其实这个方法已经在不同的篇章和论坛回答里用过很多次了
但是一直没有整理,所以决定今天整理一下
正文
很多网页会为了防止脚本对基础API进行劫持,会对调用进行一层封装
从而导致即使劫持了,因为上层传入的函数没有任何特征,导致无从下手
例如
function runTimer(callback, time=1000) {
setTimeout(() => {
callback();
}, time);
}
function exampleA() {
runTimer(() => {
console.log("A");
});
}
function exampleB() {
runTimer(() => {
console.log("B");
});
}
exampleA()
exampleB()
因为对setTimeout劫持得到的一定是一个箭头函数,根本无法无从下手
这个时候我们就可以尝试制造一个Error错误,然后利用错误对堆栈进行检查
从而找到一个有特征的上层函数,来实现通过基础AP进行劫持和过滤操作
我们可以尝试一下
const originSetTimeout = window.setTimeout;
window.setTimeout = function (callback, time) {
const err = new Error("大赦天下");
console.log(err);
return originSetTimeout.call(this, callback, time);
};
打印的输出内容有
Error: 大赦天下
at window.setTimeout (1.js:3:16)
at runTimer (1.js:8:3)
at exampleA (1.js:13:3)
at 1.js:23:1
Error: 大赦天下
at window.setTimeout (1.js:3:16)
at runTimer (1.js:8:3)
at exampleB (1.js:18:3)
at 1.js:24:1
可以发现可以通过exampleA和exampleB的字样来选择过滤函数,例如我们想要过滤掉exampleA
const originSetTimeout = window.setTimeout;
window.setTimeout = function (callback, time) {
const err = new Error("大赦天下");
if (err.stack.indexOf("exampleA") !== -1) {
return -1;
}
return originSetTimeout.call(this, callback, time);
};
现在开始只有exampleB可以正常调用runTimer的setTimeout来等待到时间回调输出内容了
利用异常抛出终止JS文件执行
当整个文件我们都想阻断执行,或者找不到注入点的时候
可以利用该方法来阻断文件的执行
其原理是因为现代前端会使用打包工具进行打包,或框架/库存在初始化代码
所以通常一个文件的开头会调用一些基础API,我们对基础API进行劫持
当发现堆栈来自于某个我们想要拦截的文件,就抛出一个异常
由于这个异常的出现,会导致JS文件的终止,从而杀死后续的代码执行
例如现在存在两个JS文件,代码分别为
// A.js
(window.initArr ?? (window.initArr = [])).push("AFile");
console.log("A")
// B.js
(window.initArr ?? (window.initArr = [])).push("BFile");
console.log("B")
我们将window.initArr
视为文件的初始化流程的模拟
这时候可以针对push劫持,并且制造一个Error检查是否来自A.js
window.initArr = window.initArr ?? [];
const originPush = window.initArr.push;
window.initArr.push = function (...args) {
const err = new Error("大赦天下");
if(err.stack.indexOf("A.js")!=-1){
throw Error("Kill A.js")
}
return originPush.call(this, ...args);
};
可以发现A.js执行到第一条由于异常直接被强制终止了,而B.js可以正确执行,打印输出如下
inject.js:6 Uncaught Error: Kill A.js
at window.initArr.push (inject.js:6:11)
at A.js:1:43
B.js:2 B
这个时候无论是想要彻底不再执行这个文件,还是在阻断后在gm_xhr请求这个文件再去执行都没问题了
当然抛出一个错误不好看,我们可以再通过onError
捕获抛出的错误,如果是我们自己的就pass掉
这样就很干净了!
window.onerror = function(message, source, lineno, colno, error) {
if(error.message=="Kill A.js"){
return true
}
};
结语
完结撒花~