鐵人賽 Day 21 逆向實戰 - 解析微混淆 + AES簽名機制流程 (簡單)

鐵人賽 Day 21 逆向實戰 - 解析微混淆 + AES簽名機制流程 (簡單)
鐵人賽 Day 21 逆向實戰 - 解析微混淆 + AES簽名機制流程 (簡單)

本系列文章所討論的 JavaScript 資安與逆向工程技術,旨在分享知識、探討防禦之道,並促進技術交流。

所有內容僅供學術研究與學習,請勿用於任何非法或不道德的行為。

讀者應對自己的行為負完全責任。尊重法律與道德規範是所有技術人員應共同遵守的準則。

 

 aHR0cHM6Ly93d3cubWFzaGFuZ3BhLmNvbS9wcm9ibGVtLWRldGFpbC81Lw==

在 Chrome DevTools 打開 「 Network 」 分頁,並篩選 「 XHR / Fetch 」 請求。

可以看到 Method 為 POST 代表可能需要檢查 Payload 是否帶有驗證參數。

1.jpg

切換到 Payload 分頁,可以看到請求內容有一個 xl 參數,值是一段長字串這就是加密後的資料。

2.jpg

按下 ESC 打開底部工具欄透過 Search 搜尋 xl。

搜尋結果顯示 pagination5.js 檔案裡有 body: JSON.stringify({xl: encryptedQuery}) 的程式碼。

這代表 Payload 中的 xl 值,實際上是 encryptedQuery。

3.jpg

在 pagination5.js 檔案中看到 LoadPage 函式將 params 轉成 JSON string。

接著呼叫 encrypt(jsonString) 產生 encryptedQuery。

4.jpg

在第 54 行 let encryptedQuery = encrypt(jsonString); 打斷點。

切換分頁時程式會停在這邊,就可以查看 params 與 jsonString 的內容。

5.jpg

滑鼠移到 encrypt 上方會顯示該函式資訊。

點擊後即可跳轉到 encrypt 函式所在的行數。

6.jpg

在 encrypt 函式內看到加密流程。

let _0x2703a2 = dd['a'][_0x4d843e(0xd6)]['Utf8']['parse'](_0x277028)

這段程式碼看起來非常難以理解,實際上只是換個方式調用函式而已。

_0x4d843e(0xd6) 實際上是 enc。

也就是說這段程式碼的意思如下。

let _0x2703a2 = dd.a.enc.Utf8.parse(_0x277028)
7.jpg

往上查看程式碼,發現 dd = { 'a': CryptoJS }。

證明程式使用 CryptoJS 函式庫進行加密。

也就是說

let _0x2703a2 = dd.a.enc.Utf8.parse(_0x277028)

相當於

let _0x2703a2 = CryptoJS.enc.Utf8.parse(_0x277028)

那這樣是不是跟之前介紹的CryptoJS的使用方式一樣了呢。

8.jpg

將 _0x4d843e(0xd2) 反白後滑鼠放上去,可以看到 _0x4d843e(0xd2) 是 AES。

那麼已經可以確定是 AES 加密演算法,搭配 CBC 模式與 PKCS7 填充。

9.jpg

在encrypt函式上方已經預定義了 Key 跟 IV。

10.jpg
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

 

作者頭像
Nick

擅長從前端的互動設計到後端的資料處理,都能親自規劃與實作。對我來說開發網站不只是完成功能,而是打造一個能被真正使用、體驗順暢的平台。我喜歡把複雜的技術轉化成簡單好懂的成果,並在這個過程中持續學習與挑戰自己。