iT邦幫忙

2025 iThome 鐵人賽

DAY 21
0
Security

走進資安現場: JavaScript資安逆向工程超實戰系列 第 21

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

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20250921/201697752vT89zDWS0.jpg

本系列文章所討論的 JavaScript 資安與逆向工程技術,旨在分享知識、探討防禦之道,並促進技術交流。
所有內容僅供學術研究與學習,請勿用於任何非法或不道德的行為。
讀者應對自己的行為負完全責任。尊重法律與道德規範是所有技術人員應共同遵守的準則。

本文同步發佈:https://javascript-reverse-engineering-obfuscation-aes-easy

挑戰網址

aHR0cHM6Ly93d3cubWFzaGFuZ3BhLmNvbS9wcm9ibGVtLWRldGFpbC81Lw==

解題過程

觀察 Network 請求

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

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

https://ithelp.ithome.com.tw/upload/images/20250921/20169775S3cfTNQuyy.jpg

檢查 Payload

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

https://ithelp.ithome.com.tw/upload/images/20250921/20169775HEVobvwd0Q.jpg

搜尋關鍵字

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

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

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

https://ithelp.ithome.com.tw/upload/images/20250921/20169775vI1pVNK68N.jpg

找到加密位置

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

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

https://ithelp.ithome.com.tw/upload/images/20250921/20169775I7Xn1Jmed8.jpg

設置斷點

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

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

https://ithelp.ithome.com.tw/upload/images/20250921/201697759HsOEugcID.jpg

定位加密函式

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

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

https://ithelp.ithome.com.tw/upload/images/20250921/20169775rVRnYtKow0.jpg

進入 encrypt 函式

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

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

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

_0x4d843e(0xd6) 實際上是 enc。

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

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

https://ithelp.ithome.com.tw/upload/images/20250921/20169775gQihIImthF.jpg

確認 dd 來源

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

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

也就是說

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

相當於

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

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

https://ithelp.ithome.com.tw/upload/images/20250921/20169775mFB0NfhyAB.jpg

確認加密方式

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

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

https://ithelp.ithome.com.tw/upload/images/20250921/20169775gRbTPv2DvI.jpg

找到金鑰與 IV

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

https://ithelp.ithome.com.tw/upload/images/20250921/20169775WhjHanNSy2.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()

Github 原始碼

https://github.com/mrnick6886/ScrapingChallenges/blob/main/mashangpa/5.js


上一篇
Day 20 逆向實戰 - 解析MD5簽名機制流程 (簡單)
下一篇
Day 22 逆向實戰 - Header 驗證參數 + Response加密 (簡單)
系列文
走進資安現場: JavaScript資安逆向工程超實戰23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言