最近 Hack 了一個(gè)前端頁面(自家網(wǎng)站壶笼,但是暫時(shí)不能從源碼改)虐沥,來增強(qiáng)它的某些功能杨名。
這些增強(qiáng)功能需要使用網(wǎng)頁中的一些接口脏榆,但是經(jīng)過調(diào)試發(fā)現(xiàn)需要對接口傳輸?shù)谋韱芜M(jìn)行簽名校驗(yàn)。嘗試了一下常見的 Hash 算法以及少許迭代組合台谍,輸入輸出都對不上须喂,而逆向整個(gè)算法代價(jià)過高,所以打算使用瀏覽器擴(kuò)展篡改 JS 趁蕊,將簽名接口直接暴露出來坞生。
一、JS 文件攔截和篡改
經(jīng)過調(diào)試定位到了簽名算法所在的地方掷伙,然后取前后若干代碼作為特征碼是己,到時(shí)候只需要把要插入的內(nèi)容以合適的方式添加到特征碼里面,然后替換原文件中的特征碼任柜,就可以達(dá)到篡改 JS 的效果了卒废。
1.1 JS 文件攔截
這個(gè)攔截需要 webRequestBlocking和 webRequest權(quán)限,因此在 manifest.json 中聲明這兩個(gè)權(quán)限:
"permissions": [
...
"webRequest",
"webRequestBlocking"
]
然后在background.js中過濾帶有簽名算法的JS請求:
chrome.webRequest.onBeforeRequest.addListener(
function(details){
const { url } = details;
if(/xxxx\.js/.test(url)){
// 這個(gè)函數(shù)要同步返回宙地,因此我們不能在這里篡改文件
// 不過先返回一個(gè)“信標(biāo)”摔认,注入到 dom 里作為注入 JS 的憑據(jù)
// secretPageId 確保頁面對得上,不過這一點(diǎn)貌似是多余的
const secretPageId = Date.now() + "--" + Math.random();
const redirectUrl = `
data:javascript,
var node = document.createElement('div');
node.id = 'secretPageId';
node.innerHTML ="${secretPageId}";
document.body.appendChild(node);
`.replace(/\n/g, '');
getAndChangeScript(url, secretPageId);
return {
redirectUrl
}
}
return {
redirectUrl: url,
}
}
);
1.2 JS 文件篡改
你可能注意到了上面的代碼片段中绸栅,調(diào)用的 getAndChangeScript 函數(shù)還沒有定義级野,看函數(shù)名應(yīng)該猜得到它是用來篡改 JS 的:
async function getAndChangeScript(src, secretPageId){
const scriptStr = await (await fetch(url)).text();
const changedScript = scriptStr.replace(
// 這里是特征碼
"e.filterNoNumber=Y;",
// 修改后的特征碼,替換到原文中去
"window.signMaker = J;e.filterNoNumber=Y;"
);
scriptInjectBus.send(secretPageId, changedScript);
}
二、將篡改后的 JS 注入頁面
2.1 將文件從 background.js 發(fā)送到 content.js
畢竟background.js并不能操作 DOM 蓖柔,因此只能使用 content.js辰企,這里就需要一個(gè)“傳送門”來發(fā)送這些內(nèi)容。
在background.js這一側(cè)况鸣,定義一個(gè)scriptInjectBus來干這事:
const scriptInjectBus = (function () {
const listenQueue = [];
const send = function (info) {
listenQueue.forEach(function (handler) {
handler(info);
});
};
const listen = function (handler) {
listenQueue.push(handler);
};
return {
send,
listen
};
})();
并且要監(jiān)聽來自 content 的消息:
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
scriptInjectBus.listen(function (info) {
chrome.tabs.sendMessage(tab.id, info, function (res) {});
});
});
??這里使用了 tabs牢贸,記得在 manifest.json中添加 tabs權(quán)限。
2.2 content.js 接收代碼并注入頁面
content.js這里的寫法想必大家就不陌生了:
chrome.runtime.onMessage.addListener(function(info){
const node = document.getElementById('secretPageId');
const secretPageId = node.innerHTML.trim();
if(secretPageId && secretPageId === info.secretPageId){
node.innerHTML = '';
const scriptNode = document.createElement('script');
scriptNode.innerHTML = info.js;
document.body.appendChild(scriptNode);
}
}
三镐捧、也許這并不是最好的辦法
但是管用就行潜索,對于自家網(wǎng)站來講,最好的辦法當(dāng)然是在源碼里修改懂酱。這種使用瀏覽器擴(kuò)展的奇技淫巧至少會(huì)有如下缺陷:
不支持import方法直接引入的 ESM模塊竹习;
對于依賴 JS 加載時(shí)序的頁面,打亂的時(shí)序會(huì)影響原來的邏輯(當(dāng)然這種設(shè)計(jì)是不科學(xué)的)列牺。