AIS3-pre-exam-2025-writeups
Web 🌐
Tomorin db 🐧
進入網頁後,直接嘗試點擊
flag存取/flag。發現當對網頁請求
/flag時,會被伺服器重導向到 YouTube 影片。- 由於目的是要存取檔案,故使用存取
./flag應與flag相同,故嘗試利用本特性存取flag。 - 由於瀏覽器會直接將
./解釋為目前目錄而抵銷,我將其進行 URL 編碼避免被解釋。
- 由於目的是要存取檔案,故使用存取
將
/編碼為%2f,編碼/./flag後得到/.%2fflag。伺服器收到了
/.%2fflag後由於它不是/flag而繞過了檢查。接著透過./flag成功存取。
FLAG 🚩
AIS3{G01ang_H2v3_a_c0O1_way!!!_Us3ing_C0NN3ct_M3Th07_L0l@T0m0r1n_1s_cute_D0_yo7_L0ve_t0MoRIN?}
Login Screen 1
- 檢查
docker-compose.yml設定檔。發現users.db檔案被掛載到volumes中,與其他可公開存取的路徑類似。
直接在瀏覽器中嘗試存取
/users.db。- 成功下載
users.db檔案。
- 成功下載
開啟
users.db,發現其中包含使用者資訊,包括 admin 帳號的 2FA 密碼。
guest帳號的密碼是guest。基於此模式,推測admin帳號的密碼可能也是admin。
使用帳號
admin和密碼admin成功登入。進入 2FA 驗證階段後輸入從
users.db中找到的 admin 帳號對應的 2FA 密碼。成功登入 admin 帳號,並在頁面中找到 FLAG。

FLAG 🚩
AIS3{1.Es55y_SQL_1nJ3ct10n_w1th_2fa_IuABDADGeP0}
Misc 🧩
welcome
挑戰上面直接寫著 Flag 的文字內容。
雖然看起來不是圖片,但無法直接複製貼上。
- 使用 windows 內建剪取工具,將圖片中的文字轉換為可編複製的文字。

FLAG 🚩
AIS3{Welcome_And_Enjoy_The_CTF_!}
Ramen CTF
分析:
圖片中看起來就是一家普通的拉麵店,並且主餐還被使用圖片蓋住。
照片角落放著一張發票,看起來就是這題的重點資訊。
店家名稱:
- 從發票提取關鍵字
平和及統編前八碼3478592。
- Google 搜尋
平和 拉麵 3478592,得到地址宜蘭縣礁溪鄉礁溪路五段108巷1號。 - 將地址輸入 Google Maps,確認店名為
樂山溫泉拉麵。

- 從發票提取關鍵字
餐點品項:
- 掃描發票左方 QR Code,發現線索指向與
蝦拉有關的餐點。 - 搜尋
樂山溫泉拉麵菜單。 - 在菜單中找到
蝦拉麵。

- 掃描發票左方 QR Code,發現線索指向與
FLAG 🚩
AIS3{樂山溫泉拉麵:蝦拉麵}
AIS3 Tiny Server - Web / Misc
前置作業
啟動並測試題目提供之伺服器檔案,發現其應為一個簡易的檔案伺服器。
確認伺服器有目錄瀏覽功能,輸入資料夾名稱可以直接列出該資料夾下的內容。

1 透過題目提供之FLAG 位置提示,明確指出 FLAG 位於 /readable_flag_<somerandomstring> 這樣的路徑下,故得知其位於跟目錄,又由於其有目錄瀏覽功能,故我們能透過存取根目錄來查看裡面的內容。
* 參考先前Tomorin db 🐧 的解題過程,嘗試使用 URL 編碼來存取跟目錄。
2. 存取/%2f後取得目錄內容,包含一個符合flag檔名格式的檔案。

3. 請求 /%2freadable_flag_xNhHEswND5f45bnjjzm9TnguqR4l4BEl,伺服器成功解析並回傳 FLAG 。
FLAG 🚩
AIS3{tInY_we8_s3RvER_WitH_fIle_BrOWs1ng_45_@_FeATURE}
Pwn 💥
Welcome to the World of Ave Mujica🌙
前置作業
- 分析程式流程:
graph TD;
顯示Banner-->問Yes/no;
問Yes/no-->其他;
問Yes/no-->Yes;
Yes-->詢問字串長度;
詢問字串長度-->輸入大於127;
詢問字串長度-->輸入小於等於127;
輸入小於等於127-->使用者輸入
目標: 存取含式
Welcome_to_the_world_of_Ave_Mujica,位址0x401256,內含 shell 後門。

漏洞:
read(0, buf, int8);,buf大小為 143,存在 Buffer overflow。- 然而
read_int8()會檢查輸入值是否 > 127,若是則退出。
- 然而
- 由於變數型態為
unsigned __int8,範圍為1-255,輸入-1會被解釋為255,讀取大小則會成功設為255。
- 由於變數型態為
buf在rbp-160,return address在rbp+8。距離 =160 + 8 = 168。- Payload: `b’A’ * 168 + p64(0x401256)
yes-1- 發送 Payload
- 取得 Shell。
rev code
1 | |
python exploit code
1 | |
FLAG 🚩
AIS3{Ave Mujica🎭將奇蹟帶入日常中🛐(Fortuna💵💵💵)...Ave Mujica🎭為你獻上慈悲憐憫✝️(Lacrima😭🥲💦)..._c7328452ad54297bd152cf485d2f1943}
Reverse 🛠️
AIS3 Tiny Server - Reverse
透過IDA逆向並手動過過濾後後發現程式包含兩個與本題相關之函式
sub_2110: 處理請求並將處理後的輸入傳給sub_1E20。
sub 2110
1 | |
sub_1E20: FLAG 檢查函式,動態生成一個 45 位元組的目標 FLAG並與使用者輸入進行比較。
sub 1E20
1 | |
分析 sub_1E20
初始化:
- 金鑰
key_string被設為"rikki_l0v3"。 - 一個 44 位元組的初始資料緩衝區 (
original_v8_bytes) 由 11 個硬編碼的 DWORD 值(小端序)組成。 - 初始 XOR 值:
initial_v2 = 51(‘3’),initial_v3 = 114(‘r’)。
- 金鑰
FLAG 生成演算法:
FLAG[0] = initial_v2 ^ initial_v3;- 對於
i從 1 到 43:FLAG[i] = original_v8_bytes[i] ^ key_string[i % 10]; FLAG[44] = 0x14 ^ key_string[44 % 10];。
根據以上邏輯製作py腳本:
python exploit code
1 | |
FLAG 🚩
AIS3{w0w_a_f1ag_check3r_1n_serv3r_1s_c00l!!!}
web flag checker
轉換為 C 程式碼:
使用wasm2c將.wasm檔案轉換為 C 原始碼。1
wasm2c index.wasm -o output.c編譯:
使用gcc將上一步產生的.c檔案編譯為.o。1
gcc -c output.c -o output.o反編譯
- 將編譯好的
output.o載入IDA。 - 找到處理 FLAG 驗證的
flagchecker函式。
- 將編譯好的
腳本撰寫
- 載入題目提供的
reference_values和固定的mask。 - 利用
mask計算出每個加密區塊對應的旋轉位移量,存入keys。 - 對每個加密區塊,使用其對應的
keys值執行ror64操作,得到原始數據區塊。 - 將所有原始數據區塊轉換成位元組、拼接、去除尾部空字元,再以 UTF-8 解碼成字串,輸出 FLAG。
- 載入題目提供的
rev code
1 | |
python exploit code
1 | |
FLAG 🚩
AIS3{W4SM_R3v3rsing_w17h_g0_4pp_39229dd}
A_simple_snake_game
仔細翻找逆向後的檔案,會發現
SnakeGame::Screen::drawText函式,依據名稱看起來是用於處理文字輸出,故開始研究其架構。- 經分析,本函式負責文字顯示。當滿足特定條件
(int)this > 11451419 && a3 > 19810時,會觸發 FLAG 的顯示邏輯。
- 經分析,本函式負責文字顯示。當滿足特定條件
SnakeGame::Screen::drawText
1 | |
- 在 FLAG 顯示邏輯中,發現
1 | |
這些變數組成一段長度 43 的加密資料,再搭配 SnakeGame::hex_array1[] 做 XOR 解密:
1 | |
- 解密
decrypted_byte[i] = encrypted_byte[i] ^ hex_array1[i]
py腳本
1 | |
FLAG 🚩
AIS3{CH3aT_Eng1n3?_0fcau53_I_bo_1T_by_hAnD}
Crypto 🔑
Stream
採用已知明文攻擊:
- FLAG 以
AIS3{開頭。 - 設
C_int為加密後 FLAG 的整數形式,P_int為b"AIS3{"的整數形式。- 關鍵近似:
b_squared_approx = C_int ^ P_int。 - 計算
b_approx_root = math.isqrt(b_squared_approx)。 - 在
b_approx_root的一個小範圍內搜索實際的b值。
- 關鍵近似:
- 迭代
b_candidate = b_approx_root +/- delta,用C_int ^ (b_candidate**2)解密 FLAG 開頭,看是否能得到b"AIS3{"。 - 找到正確的
b後,用C_int ^ (b**2)解密完整 FLAG。
腳本編寫:
- 將
encrypted_flag轉換為intencrypted_int。同時,將已知的明文前綴b"AIS3{"以大端序轉換為整數known_int。 - 執行第一次 XOR 運算:
b_squared = encrypted_int ^ known_int,利用已知前綴來獲取一個與實際加密金鑰的平方相關的近似值。 - 計算
b_squared的整數平方根,得到 b 。 - 執行第二次 XOR 運算以進行解密:
original_int = encrypted_int ^ (b_value**2)。 - 最後,將解密得到的
original_int計算其所需的位元組長度,並以大端序轉換回位元組序列,再將此位元組序列以 UTF-8 解碼為字串,即為還原後的 FLAG。
code
1 | |
FLAG 🚩
AIS3{no_more_junks...plz}
Hill
由於只有一次查詢機會,須利用這次查詢同時恢復矩陣 A 和 B。
恢復 A 和 B:
- 構造一個包含 $2n=16$ 個區塊的選擇明文:$P_C = [e_0, \vec{0}, e_1, \vec{0}, \dots, e_{n-1}, \vec{0}]$(其中 $e_j$ 是標準基向量,$n=8$,$\vec{0}$ 是全零向量)。
- 將 $P_C$ 作為我們唯一的一次查詢提交給伺服器。
- 伺服器返回的 $16$ 個密文區塊 $C_C = [C_{C,0}, C_{C,1}, \dots, C_{C,15}]$ 將直接揭示
A和B的所有列:- $A$ 的第 $j$ 列 $= C_{C,2j}$
- $B$ 的第 $j$ 列 $= C_{C,2j+1}$
解密 FLAG:
- 得到
A和B後,計算A在模251下的逆矩陣 $A^{-1}$。 - 使用 $A^{-1}$ 和
B解密伺服器初始給出的 $C_F$:- $P_{F,0} = (A^{-1} \cdot C_{F,0}) \pmod{251}$
- $P_{F,i} = (A^{-1} \cdot (C_{F,i} - B \cdot P_{F,i-1})) \pmod{251}$
- 將解密後的各個明文區塊 $P_F$ 組合並解碼,即可得到 FLAG。
- 得到
py腳本
1 | |
FLAG 🚩
AIS3{b451c_h1ll_c1ph3r_15_2_3z_f0r_u5}
slow
- ECDSA 簽名演算法因使用 LCG 生成 nonce
k而導致 nonce 可被預測,能從少量簽名中恢復出私鑰。
透過與伺服器互動,連續兩次請求對固定訊息的 ECDSA 簽名,從而獲取兩組簽名 $(r_1, s_1)$ 和 $(r_2, s_2)$。這兩個簽名分別使用了由 LCG 生成的、存在線性關係 ($k_2 \equiv (A_{LCG} \cdot k_1 + C_{LCG}) \pmod{ORDER}$) 的 nonce $k_1$ 和 $k_2$。同時計算該固定訊息的雜湊值 $h_{example}$。
利用已知的 LCG 參數 (
A_LCG,C_{LCG})、曲線階 (ORDER)、獲取的兩個簽名 $(r_1, s_1), (r_2, s_2)$ 以及訊息雜湊 $h_{example}$,代入 ECDSA 簽名方程 ($s \cdot k \equiv h + r \cdot d \pmod{ORDER}$),建立並求解關於 $k_1$ 和私鑰 $d$ 的聯立方程。首先透過代數運算和模逆運算解出 $k_1$,然後將 $k_1$ 代回其中一個簽名方程以計算出私鑰 $d$。偽造目標訊息的簽名:
- 計算目標訊息
TARGET_MSG_BYTES(“give_me_flag”) 的雜湊值 $h_{target}$。 - 生成一個新的、密碼學安全的隨機 nonce $k_{new}$。
- 使用已恢復的私鑰 $d$ 和新的 nonce $k_{new}$,根據標準 ECDSA 簽名算法計算出目標訊息的有效簽名 $(r_{new}, s_{new})$。
- 計算目標訊息
將偽造的簽名 $(r_{new}, s_{new})$ 連同目標訊息提交給伺服器。由於簽名是使用正確的私鑰生成的,驗證將會通過,伺服器隨後回傳 FLAG。
python exploit code
1 | |
FLAG 🚩
{Aff1n3_nounc3s_c@N_bE_broke_ezily...}