猿人學內部練習平臺第54~60題

脱下长日的假面發表於2024-06-07

第54題 無限debugger練習/入門js

本題開啟控制檯就會自動無限 debugger,解決無限 debugger 的最簡單方式就是使用 Firefox 121 版本以上的版本,Firefox 121 以上的版本會對程式碼內部的 debugger 自動過濾,只有手動打的斷點才會生效。
本題是無限 debugger 練習,嘗試手動解決該問題。
首先是檢視斷點位置及呼叫堆疊,可以看到,這個無限 debugger 其實就是利用了 eval 的特性不斷斷點:


那麼我們 hook eval 函式就可以了:

let _eval = eval;
eval= function(){
    if (arguments[0].indexOf('debugger') != -1) {
        return _eval('')
    }
    return _eval.apply(this, arguments)
}

將這段程式碼輸入控制檯回車,點選執行按鈕,程式就會繼續執行了,但是這種方式重新整理頁面就失效了,可以透過油猴指令碼外掛實現持久化,也可以使用 v_jstools 外掛自帶的 hook_eval 功能或注入程式碼功能。
這個點過了之後,程式又會在下一處無限 debugger,可以看到,有很多個 script 標籤,裡面有 debugger 命令,這時可以透過替換本地內容的方式:

可以右鍵點選 54 檔案,選擇“替換內容”(override content),如果是第一次進行此項操作的話還會讓你選擇本地的一個資料夾,作為替換的資料夾,允許相關許可權即可。然後 54 檔案就會被儲存到本地,並被本地的該檔案替換,接下來就可以把裡面的 debugger 部分刪掉即可。

刪掉後儲存並重新整理頁面,可以看到此處也沒有無限 debugger 了。
接著是下一處無限 debugger,檢視呼叫堆疊可知,該處是利用了 appendChild 方法不斷新增含 debugger 內容的 script 標籤並移除觸發的,此時需要 hook appendChild 方法。


透過控制檯列印可知,n 是 document,n 的原型是 HTMLDocument ,而 HTMLDocument 經查詢沒有 appendChild 方法:

那麼繼續從原型鏈上查詢,最終發現 appendChild 方法是 Node 節點下的:

hook 該方法可以這麼寫:

let _appendChild = Node.prototype.appendChild
Node.prototype.appendChild = function(){
    if (arguments[0].innerHTML && arguments[0].innerHTML.indexOf('debugger') != -1){
         arguments[0].innerHTML = ''
    }
    return _appendChild.apply(this, arguments)
}

該處無限 debugger 過掉後,程式碼會在如下地方再次停下:

由於是我們已經本地替換的檔案內容, 直接把此部分刪掉並儲存即可。這樣該題所有的無限 debugger 就都過掉了。
接下來就是檢視介面,逆向引數了,本題需找到引數 token 的由來,全域性搜尋即可找到:

很簡單,是對頁碼的 base64 編碼。

第55題 結果加密跟值

此題請求沒有加密,但是返回的響應是加密的,透過呼叫堆疊很容易找到加密位置:

檢視 decode 方法:

可以看到是一個 AES 加密,根據程式碼推測是一個 ECB 模式的,填充方式為 Pkcs7,金鑰為 aiding6666666666 的 AES 加密,可以放到標準 AES 加密裡對比:

這是一個網上找的標準 AES 加密,將密碼、模式和填充模式選擇為對應的之後,輸入框輸入該題中的加密值,這裡我用的第三頁的加密值,點選解密:

可以看到,成功解密出了值,說明這是一個未經魔改的 AES 加密,我們本地模擬呼叫就可以了,對於此題,將 decode 方法複製到本地就可以直接使用。

第56題 經典入門資料加密

該題和上題一樣,請求沒有加密,響應結果是加密的,透過呼叫堆疊也很容易找到對應位置,如下:

該檔案程式碼是經過混淆的,不方便檢視,可以先解混淆再檢視,可以自己透過 AST 解混淆,也可以透過一些外掛解混淆,這裡使用 v_jstools 外掛的解混淆功能:

將檔案程式碼複製到source框中,點選普通解混淆即可,將code框中的解混淆程式碼複製到本地檢視:

可以看到,有一個設定 private key 的操作,ctrl 點選 PVA 檢視該變數,如下:

PVA = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAy5R1R2yM5jPPvkO2F47qVqMkYj7o92DF8y1yMkCSxY1WwqG0\ndCdUZTnaoBuAz99wGt55oGLcdalV71nPUiGWs/b6GzVN5v72baz/Q2OxHtkrFKqL\nVX16LW31cW9hAntN84RCbvTeB0MNV+SHmXjIf17OQLCtDKHBZWZ5NKyqFstO+KOd\nu32d2jsw+DT5lOBzDUBk/wUw2KyFJVx7eK6sSXEyWqBk2nxMRDNYixIEN1V1EBSq\nf+OwKK5Mxi04r38+Qog8z03/t/u6CfAOWVmi+MdrD1VHXv/P7bnFlgRcLzKwK1QL\nTSLBE1PrMmNNj0oRjByhMoI9tY5X6mRBqLyDhwIDAQABAoIBAGO++RmGO6D9CNAJ\n4Bm52eKaK5UBiubOIR8NiNLLZb5qinRxg3eX35d7Wb2xzBLNwOFBWSl21trFncfY\n4qY0s+C4ZYHYQ7Om/7nsFeQAYAOj1yJYj01TXf4NTsGGF2t+W8qxZlV0H6dCOLL0\nU2YkUmRp4Le8eQVj6dyTcVaYNPxWQBnb9ZOEIEvEjeoO/DD7CCmt7LDCey9KrTQl\nAvuc2nN6uRV1Wfm0P8conKPJtVdgzMvJujNdpz+bBDqwsqgeCICjs/hSCNO81VH3\nDD7J0mG2OHqowOVqagoDHpBprHOUKxAeTs9I0KEL+hEI4zXCDL69+Xs6azuts733\nzSOmwxkCgYEA25czfPVxxcK685LhaAvwbmzWHqNp07ytRNGf+Aww6OdgWkdgPy0n\n20Gkg0HAqsxGcgZJk6cAkOy5hBLNHpHlGbeWFi+62lVNYUv3hAxumtiPyBMu7avE\nZQCTXND1H1f/2enRDJRxQsR8y/SX1ivmC5U6fx7hbpKxnXyRHnvSlk8CgYEA7VWp\nhLNkn4AEaPPW0TknwKG40At/hjecX2zWAyZVt4ydDSeKgMEOUdmvGGlSCrefAl0n\nPTfM9SdIDcO5OTa2wUayKLIsrb6TDnG6KXXN6z3HR3Q4qKJbG83eaMYDqqziPPV+\nxzRVWShI3EGwkLczASmiYy+sEAT0OkxP59xTKUkCgYBgaGjFkukJfy4fJDxsNtmv\nUX9MYkhjGrIjxbjq6UdL6dGGsVGTSxr1i0NUETkqg5bmFtaUybxY5GWqk6qUok8o\nVE7DnN73Xn4jmnun8OFagHvXxnxTApeuFGueU2tbAIKmxJ3wXPfA7Y0w6kkDUbCl\nIzZUe1VT+3mZgAgijxBsxwKBgQDNytiJ62/V6hBo3P6pPtEcdF6nb0DtpazfBaVw\n572twaywqlermzsKeCIenbx49I1ZZGLQ72C2NpCA9vTWCn5fiyiSpyScp0ImZTDS\nIIckctYoPDug5d7wdgtjeEfXp78osopyuwtCmu7Kpd8vLNt6J5raPI0K+vC22FL1\nLpOhmQKBgQCFeU448fL87N1MjMyusi8wJ5MLcn+kHbLTtpskTpfQM2p3Cnp4oL+7\nBI4AlXlKItV37rJIjZxQgLWhGoTZPplZaW4ooJCFJbazce5ua5fnsFS0oXhDN7uw\njaq+v5t8G6gFS09hEa4kz9O53t/7UGuQqh0Bxb0cJ9iNeAlhagvBDQ==\n-----END RSA PRIVATE KEY-----";

可以看到,這是一個 RSA 加解密的私鑰,將密文和私鑰放入標準 RSA 中解密,如下:

成功解密得到了我們所需的資料,說明這是一個標準 RSA 加密,本地模擬呼叫即可,python 模擬 RSA 解密的參考程式碼:

import rsa
import base64

# 私鑰
private_key_data = '''-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAy5R1R2yM5jPPvkO2F47qVqMkYj7o92DF8y1yMkCSxY1WwqG0
dCdUZTnaoBuAz99wGt55oGLcdalV71nPUiGWs/b6GzVN5v72baz/Q2OxHtkrFKqL
VX16LW31cW9hAntN84RCbvTeB0MNV+SHmXjIf17OQLCtDKHBZWZ5NKyqFstO+KOd
u32d2jsw+DT5lOBzDUBk/wUw2KyFJVx7eK6sSXEyWqBk2nxMRDNYixIEN1V1EBSq
f+OwKK5Mxi04r38+Qog8z03/t/u6CfAOWVmi+MdrD1VHXv/P7bnFlgRcLzKwK1QL
TSLBE1PrMmNNj0oRjByhMoI9tY5X6mRBqLyDhwIDAQABAoIBAGO++RmGO6D9CNAJ
4Bm52eKaK5UBiubOIR8NiNLLZb5qinRxg3eX35d7Wb2xzBLNwOFBWSl21trFncfY
4qY0s+C4ZYHYQ7Om/7nsFeQAYAOj1yJYj01TXf4NTsGGF2t+W8qxZlV0H6dCOLL0
U2YkUmRp4Le8eQVj6dyTcVaYNPxWQBnb9ZOEIEvEjeoO/DD7CCmt7LDCey9KrTQl
Avuc2nN6uRV1Wfm0P8conKPJtVdgzMvJujNdpz+bBDqwsqgeCICjs/hSCNO81VH3
DD7J0mG2OHqowOVqagoDHpBprHOUKxAeTs9I0KEL+hEI4zXCDL69+Xs6azuts733
zSOmwxkCgYEA25czfPVxxcK685LhaAvwbmzWHqNp07ytRNGf+Aww6OdgWkdgPy0n
20Gkg0HAqsxGcgZJk6cAkOy5hBLNHpHlGbeWFi+62lVNYUv3hAxumtiPyBMu7avE
ZQCTXND1H1f/2enRDJRxQsR8y/SX1ivmC5U6fx7hbpKxnXyRHnvSlk8CgYEA7VWp
hLNkn4AEaPPW0TknwKG40At/hjecX2zWAyZVt4ydDSeKgMEOUdmvGGlSCrefAl0n
PTfM9SdIDcO5OTa2wUayKLIsrb6TDnG6KXXN6z3HR3Q4qKJbG83eaMYDqqziPPV+
xzRVWShI3EGwkLczASmiYy+sEAT0OkxP59xTKUkCgYBgaGjFkukJfy4fJDxsNtmv
UX9MYkhjGrIjxbjq6UdL6dGGsVGTSxr1i0NUETkqg5bmFtaUybxY5GWqk6qUok8o
VE7DnN73Xn4jmnun8OFagHvXxnxTApeuFGueU2tbAIKmxJ3wXPfA7Y0w6kkDUbCl
IzZUe1VT+3mZgAgijxBsxwKBgQDNytiJ62/V6hBo3P6pPtEcdF6nb0DtpazfBaVw
572twaywqlermzsKeCIenbx49I1ZZGLQ72C2NpCA9vTWCn5fiyiSpyScp0ImZTDS
IIckctYoPDug5d7wdgtjeEfXp78osopyuwtCmu7Kpd8vLNt6J5raPI0K+vC22FL1
LpOhmQKBgQCFeU448fL87N1MjMyusi8wJ5MLcn+kHbLTtpskTpfQM2p3Cnp4oL+7
BI4AlXlKItV37rJIjZxQgLWhGoTZPplZaW4ooJCFJbazce5ua5fnsFS0oXhDN7uw
jaq+v5t8G6gFS09hEa4kz9O53t/7UGuQqh0Bxb0cJ9iNeAlhagvBDQ==
-----END RSA PRIVATE KEY-----'''

# Base64編碼的密文
base64_ciphertext = 'gYBBFZr5uSZ1KtSDNjfnwdFgJ99bZvu6fALrExo/L1ceUQiSHmVkL4HmV60vO90D80AzeoOgBVFCwH3cm+a23s45WlACTwhoAAAjZ6N6dH+pLQDOKYkIN45eZW6goCR8drDEIMLVyJL0hoTe/jB79IsAxY3xv+3cgJTg78j9liSorSXJlKqlWMwzSfdK6HRagJhJGq9QHJZJndJmvuZ6vzJf+6Nfvu7qgzdfzvHX+2u+KudrD/sTzKnEIajU6jZnROupAgb9agDYp2wANL9ORpMM9WCgzEG225XQ+cNPQJHutJoAIjBvc/WBSHs0As718OYpPQrLWaGeIVIn1sjU7w=='

# 解析私鑰
private_key = rsa.PrivateKey.load_pkcs1(private_key_data.encode())

# 解碼Base64編碼的密文
ciphertext = base64.b64decode(base64_ciphertext)

# 使用私鑰解密密文
plaintext = rsa.decrypt(ciphertext, private_key)
print("解密後的明文:", plaintext.decode('utf-8'))

# 輸出:
# 解密後的明文: {"status": "1", "state": "success", "data": [{"value": "5030"}, {"value": "9161"}, {"value": "6942"}, {"value": "6932"}, {"value": "3421"}, {"value": "3035"}, {"value": "8875"}, {"value": "5787"}, {"value": "2007"}, {"value": "3938"}]}

第57題 返回資料加密第三彈

此題仍是一個返回資料加密,根據呼叫堆疊可以找到加密位置:

進入 I 函式檢視:

可以看到,這是一個 AES 加密,不過根據除錯,這不是一個標準 AES 加密,因為傳入的 key 是 8 位元組的,而標準 AES 加密的金鑰必須是 16,24 或 32 位元組的,此時只能扣程式碼或者補環境了。
可以注意到,這部分程式碼是一個 webpack 打包後的,將 script 標籤內的程式碼複製到本地,然後修改 0x2: [function(P, n, o) { 後面的部分內容,如下:

刪除 ajax 部分的程式碼,並在上面新增了測試輸出程式碼,執行後結果如下:

可以看到,成功列印了對應內容, 這樣就可以透過暴露全域性變數的方式,將次結果儲存下來,在檔案首行新增如下內容:

global.result;
global.X = '__global_X';
global.l = '__global_l';

然後修改剛剛測試的地方如下:

最後在檔案末尾列印:

console.log(global.result)

此時就可以在 python 中呼叫了:

import subprocess
import os

webpack57js = open('57webpack.js', 'r', encoding='utf-8')
jscode = webpack57js.read().replace('__global_X', 'BGsHfKJP').replace('__global_l', '7VR537hkBRLAfu0WRFny6y7U4IKEu/mSYqntvsauubCNoS4lKZ2zf8xXU5TOG1AHJfa0qaj8Ec2dPQRq3vFpgvPl9SrmAukLJcpsWBQ52WSsk3ZgqKMGLpdGrJZRKrM4Wnyb/Ub2kerUc7dDCNQpKORE/97ajxUTxA+UvlVsHUMv32DeR8PuHYspnVMF7IPpCF6vn91yjWUl9rSpqPwRzcaR1kqmkzbD8+X1KuYC6QsqjgSCWwVxCgZyCsaBG64el0asllEqszgyOr9NX7uLEdRzt0MI1CkoYdygg//70suPa+FJHQ5eMiTZT0oS92dhV0mt/UUQvSTs37RD3Vo4EA==')
with open('57_.js', 'w', encoding='utf8') as f:
    f.write(jscode)
result = subprocess.check_output(['node', '57_.js'])
print(result.decode())
os.remove('57_.js')
# 執行結果:
# {"status": "1", "state": "success", "data": [{"value": "3495\r"}, {"value": "7529\r"}, {"value": "8960\r"}, {"value": "238\r"}, {"value": "3033\r"}, {"value": "9569\r"}, {"value": "2520\r"}, {"value": "4727\r"}, {"value": "6179\r"}, {"value": "3153\r"}]}

第58題 勇敢牛牛不怕困難

檢視請求可知,需獲取 token,很容易找到 token 的來源:

可以看到,是一個 md5 加密,傳入的引數是頁碼,經對比發現,這是一個標準 md5 加密,本地模擬即可:

import hashlib

page = 3
result = hashlib.md5(str(page).encode()).hexdigest()
print(result)
print(result[8:24])
# eccbc87e4b5ce2fe28308fd9f2a7baf3
# 4b5ce2fe28308fd9

第59題 髒資料

此題無任何加密,但是直接請求求和提交會顯示錯誤,檢視響應成功後相關處理的部分原始碼如下:

當 r===0x33 時,即頁碼為 51 頁時,執行 i['data'][0x0]['value'] = '5734\x0d',也就是將 51 頁的第一個值改為 5734,跳轉至 51 頁可以看到,第一個值確實是 5734:

但是我們請求的介面返回的第一值卻是 5733:

所以總和是少了1

第60題 輕混url加密

該題是 url 加密,根據呼叫堆疊可以找到加密位置:

L 方法就是加密的方法,傳入的引數是頁碼,可以看到,這是一個 AES 加密,但不是一個標準的 AES 加密,因為其金鑰是 aiding88,只有 8 位元組,而標準 AES 金鑰需為 16,24或32位元組,可以注意到這部分程式碼其實是在一個 script 標籤中,而這個標籤中的程式碼很明顯是一個 webpack 打包,這裡的處理方法類似 57 題,將 script 標籤中的程式碼複製到本地,修改加密部分的程式碼如下做測試:

這裡將 ajax 部分的程式碼刪掉,新增圖中列印,執行,結果如下:

將 page 改為2,再次執行測試,結果如下:

對比題目的介面,可以發現,結果不一樣:

說明程式碼存在環境檢測,這裡我們可以先搜尋 try 關鍵字,因為這裡是改變程式流程的一個點,搜尋後發現如下環境檢測點:



將這幾個 try catch 語句刪掉,再次執行,結果就與題目介面中展示的一致了。然後將頁碼暴露全域性使用即可。

相關文章