本系列文章所討論的 JavaScript 資安與逆向工程技術,旨在分享知識、探討防禦之道,並促進技術交流。
所有內容僅供學術研究與學習,請勿用於任何非法或不道德的行為。
讀者應對自己的行為負完全責任。尊重法律與道德規範是所有技術人員應共同遵守的準則。
本文同步發佈:https://javascript-reverse-engineering-obfuscation-aes-easy
aHR0cHM6Ly93d3cubWFzaGFuZ3BhLmNvbS9wcm9ibGVtLWRldGFpbC81Lw==
在 Chrome DevTools 打開 「 Network 」 分頁,並篩選 「 XHR / Fetch 」 請求。
可以看到 Method 為 POST 代表可能需要檢查 Payload 是否帶有驗證參數。
切換到 Payload 分頁,可以看到請求內容有一個 xl 參數,值是一段長字串這就是加密後的資料。
按下 ESC 打開底部工具欄透過 Search 搜尋 xl。
搜尋結果顯示 pagination5.js 檔案裡有 body: JSON.stringify({xl: encryptedQuery}) 的程式碼。
這代表 Payload 中的 xl 值,實際上是 encryptedQuery。
在 pagination5.js 檔案中看到 LoadPage 函式將 params 轉成 JSON string。
接著呼叫 encrypt(jsonString) 產生 encryptedQuery。
在第 54 行 let encryptedQuery = encrypt(jsonString); 打斷點。
切換分頁時程式會停在這邊,就可以查看 params 與 jsonString 的內容。
滑鼠移到 encrypt 上方會顯示該函式資訊。
點擊後即可跳轉到 encrypt 函式所在的行數。
在 encrypt 函式內看到加密流程。
let _0x2703a2 = dd['a'][_0x4d843e(0xd6)]['Utf8']['parse'](_0x277028)
這段程式碼看起來非常難以理解,實際上只是換個方式調用函式而已。
_0x4d843e(0xd6) 實際上是 enc。
也就是說這段程式碼的意思如下。
let _0x2703a2 = dd.a.enc.Utf8.parse(_0x277028)
往上查看程式碼,發現 dd = { 'a': CryptoJS }。
證明程式使用 CryptoJS 函式庫進行加密。
也就是說
let _0x2703a2 = dd.a.enc.Utf8.parse(_0x277028)
相當於
let _0x2703a2 = CryptoJS.enc.Utf8.parse(_0x277028)
那這樣是不是跟之前介紹的CryptoJS的使用方式一樣了呢。
將 _0x4d843e(0xd2) 反白後滑鼠放上去,可以看到 _0x4d843e(0xd2) 是 AES。
那麼已經可以確定是 AES 加密演算法,搭配 CBC 模式與 PKCS7 填充。
在encrypt函式上方已經預定義了 Key 跟 IV。
const CryptoJS = require('crypto-js')
function encrypt(data) {
const key = CryptoJS.enc.Utf8.parse('jo8j9wGw%6HbxfFn');
const iv = CryptoJS.enc.Utf8.parse('0123456789ABCDEF');
const parseData = CryptoJS.enc.Utf8.parse(data);
const encryptData = CryptoJS.AES.encrypt(
parseData,
key,
{
'mode': CryptoJS.mode.CBC,
'padding': CryptoJS.pad.Pkcs7,
'iv': iv
}
)
return encryptData.ciphertext.toString(CryptoJS.enc.hex)
}
const getPage = async(page) => {
const timestamp = new Date().getTime();
const xl = encrypt(JSON.stringify({
page: page,
_ts: timestamp,
}))
const response = await fetch("https://xxxxxxxxxx/api/problem-detail/5/data/", {
"headers": {
"accept": "*/*",
"accept-language": "zh-TW,zh;q=0.9,en;q=0.8,en-US;q=0.7",
"cache-control": "no-cache",
"pragma": "no-cache",
"priority": "u=1, i",
"sec-ch-ua": "\"Not;A=Brand\";v=\"99\", \"Google Chrome\";v=\"139\", \"Chromium\";v=\"139\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"cookie": "sessionid=xxxxxxxxxx",
"Referer": "https://xxxxxxxxxx/problem-detail/5/",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
},
"body": JSON.stringify({
"xl": xl
}),
"method": "POST",
});
const json = await response.json()
return json.current_array.reduce((a, b) => a + b, 0);
}
const run = async() => {
let total = 0;
for(let i = 1; i <= 20; i++){
total += (await getPage(i))
}
console.log(`total: ${total}`)
}
run()
https://github.com/mrnick6886/ScrapingChallenges/blob/main/mashangpa/5.js