CTF
以第一次打 EOF 來說我覺得不錯了,希望明年能進前十
加入 discord & 設定好身分組之後,在 2024-Qual 的 #announcement 頻道就能看到 flag
AIS3{W3lc0mE_T0_A1S5s_EOF_2o24}
這題是 h4ck3r 的類似題目,總之是一個可以查 DNS 的網站,但是在之前的版本中有 command injection 的漏洞
以下是他的 source code 的關鍵部分,可以看到他仍使用黑名單的方式進行過濾,包含像是 |
, &
, ;
, >
, <
, \n
, 等常見的 payload 字元,此外還有 flag
, *
, ?
這些
但可以看到他沒有過濾 $()
以及 "`" (backtick) 字元,因此我們還是有辦法透過這些字元來 bypass 過濾進而執行指令
不過由於他不會將執行結果回傳給我們,只會說是 valid 或是 invalid,因此我們需要找一個可以讓他回傳結果的方式,而這邊我是利用之前 CGGC 的 bossti 那時的方法,使用 curl 來將指令的結果送到 webhook 上,一個 POC 的 payload 如下
可以看到我們確實收到了資料,代表我們的確可以用這個方法來取得指令的結果,不過當我測試一些會輸出多行結果的指令如 ls -al
時發現資料不會傳回來,跟 CGGC 那時一樣,不過這時我們沒有 |
可以包成 base64 了
而經過一些測試之後,我發現只要將執行指令的 $()
外面包上雙引號即可正常的回傳資料了,而因此我們可以更肆無忌憚地執行任意的指令
由於 flag 在根目錄,首先我先用 ls /
來看一下根目錄的內容
可以看到 flag 檔案的名稱是 flag_CV3BZGq43QmVxKCd
,因此使用以下的 payload 即可取得 flag 的內容,這邊我用 ''
會串接字串的方式繞過 flag
這個關鍵字的偵測
AIS3{jU$T_3@SY_cOmM4ND_InJ3c7I0N}
首先一進入網頁,可以看到只會單純的印出 Hello world!
,沒有其他的了,因此這邊我們只好先來看一下原始碼
程式碼的部分如下
首先在 nginx 的 default.conf
可以看到,他在瀏覽時會將所有的請求都轉發到 http://web:7777
,不過當路徑為 /flag
時,會設定 internal
代表這個請求只能由內部來訪問,因此當我們直接訪問時會出現以下的 404 Not Found
錯誤
在 server.py
的部分可以看到,如果我們成功的訪問到了 /flag
的話,他會回傳 FLAG
的內容,而另外在其他路徑中,假如有 redir
的參數的話,他會將我們的請求重新導向到 redir
的網址,而他也會去檢查 redir
的內容是否符合 URL_REGEX
的格式
不過這邊的檢查只會去判斷 redir
是不是有長得像 url 的格式出現,而沒有更進一步的檢查了,此外我們可以看到這個 redir
會使用 self.send_header("Location", redir)
來設定到 response header 的部分,因此這邊我們可以發現有 CRLF injection 的漏洞,讓我們可以注入任意的 header
以下是測試的 payload
可以看到我們確實能去竄改 response header 的部分
而根據這篇文章可以看到,只要在 response header 中有一項 X-Accel-Redirect
的 header,nginx 就會將這個請求轉發到 internal
的部分,因此我們可以透過這個方式來繞過 internal
的限制
因此我們可以透過以下的 payload 來取得 flag
AIS3{JU$T_sOm3_fuNny_n91nx_FEature}
以下是題目原始碼
首先我們可以看到,他定義了一個 ECB mode 的 AES,並實作了三種不同的區塊加密模式
接著在主程式的部分可以看到他會生成一個隨機的 16 bytes counter
與兩個隨機的 32 bytes 的字串 c1
與 c2
,並將他們與 flag 做 xor 之後存放於 c3
中,並且會對於 c1
, c2
, c3
分別使用 CFB, OFB, CTR 這三種模式加密,最後會將他們的結果印出來,此外在每次的加密之後,counter
會被加一
而當他把結果印出來之後,程式會進入一個可以執行 5 次的迴圈,而在每次的迴圈中,首先他會將 counter
加上 1 之後讓我們可以做一次加密,並且會讓我們選擇要使用 CFB
, OFB
或 CTR
模式,我們只要將輸入包成 base64 之後送進去即可。當他運算完之後,會將 counter
與加密後的結果印出來
而從上面的程式碼可以觀察到,這個程式有一些漏洞,首先他只會使用同一個 AES 做加密,因此不管是在哪一個模式下只要 AES 的輸入相同都會得到相同的 AES 輸出,而另外一個漏洞是在像是 CTR
模式中,雖然在函式中有做 counter_add
的動作,但是由於函式中沒有設定是 global 的 counter
,因此每次在主程式中的 counter
不會受到子函式裡面的影響,也就是說 counter
的值有可能會被重複使用
因此,我們可以利用這兩個漏洞來進行攻擊,我們首先可以先使用 CTR
mode 並設定輸入是 \x00
* 16 * 5 個 bytes,而這麼一來在輸出的部分我們就能直接拿到 AES 的輸出,也就是 AES(init_counter+3)
, AES(init_counter+4)
, …, AES(init_counter+7)
的值
而接下來回到主程式執行第二次的迴圈,此時 counter
變成 init_counter+4
,而我們也知道了 AES(init_counter+4)
的值,因此我們可以透過修改 plaintext 變成 AES(init_counter+4)^(init_counter+0)
並串接一個 \x00
* 16 的字串,這麼一來我們就能控制 CFB mode 的第二個 block 的 AES 輸入是 init_counter+0
,我們也就能在第二個 block 的輸出中拿到 AES(init_counter+0)
的值,我們也就能與 CFB(c1)
的前 16 bytes 做 xor 操作取得 c1
前 16 bytes 的值 (當然我們也可以直接在第二個 block 的輸入設定成是 CFB(c1)
的前 16 bytes,這麼一來直接在輸出部分就能拿到 c1
前 16 bytes 的值了)
而接下來在第三次迴圈,我們要來復原 c1
的後 16 bytes,此時 counter
是 init_counter+5
,我們可以透過前面那樣的方式控制 CFB mode 的第二個 block 的 AES 輸入是 CFB(c1)
的前 16 bytes,就如同一般的 CFB mode 那樣,而我們在第二個 block 的輸入中一樣也是給 \x00
* 16 bytes 的字串,此時我們就能在第二個 block 的輸出中拿到之前在 CFB 加密時的第二個 block 的 AES 輸出一樣的數值,而我們只要再將該數值與 CFB(c1)
的後 16 bytes 做 xor 操作,就能得到 c1
的後 16 bytes 的值 (當然我們也可以像是前面那樣直接在第二個 block 的輸入設定成是 CFB(c1)
的後 16 bytes 直接拿結果)
此時我們已經復原了 c1
的值
接下來在第四次迴圈,我們需要恢復 c2
的值,此時 counter
是 init_counter+6
,我們可以從 OFB
的架構圖看到我們需要 AES(counter+1)
以及 AES(AES(counter+1))
的值,而我們可以像是前面一樣依樣畫葫蘆修改 CFB mode 第二個 block 的 AES 輸入為 AES(counter+1)
,並且在第二個 block 的輸入中給 \x00
* 16 bytes 的字串,這樣一來我們就能在第二個 block 的輸出中拿到 AES(counter+1)
的值,而此時由於 CFB mode 的規則,他會將輸出的 AES(counter+1)
餵給第 3 個 block 的 AES 輸入,因此我們在設定第 3 個 block 的輸入一樣給 \x00
* 16 bytes 的字串,這樣一來我們就能在第 3 個 block 的輸出中拿到 AES(AES(counter+1))
的值,而我們只要將 AES(counter+1)
與 OFB(c2)
的前 16 bytes 做 xor 操作,並再拿 AES(AES(counter+1))
與 OFB(c2)
的後 16 bytes 做 xor 操作,兩者串接起來就能得到 c2
的值了
在最後第五次的迴圈,我們必須得要復原 c3
的值了,此時 counter
是 init_counter+7
,而從 CTR 架構圖中可以看到我們需要 AES(counter+2)
與 AES(counter+3)
的值,後者我們已經從第一次迴圈的輸出中拿到了,而前者我們就像是前面一樣修改 CFB mode 第二個 block 的 AES 輸入為 AES(counter+2)
,並且在第二個 block 的輸入中給 \x00
* 16 bytes 的字串,這樣一來我們就能在第二個 block 的輸出中拿到 AES(counter+2)
的值,我們只要將 AES(counter+2)
與 CTR(c3)
的前 16 bytes 做 xor 操作,並再拿 AES(counter+3)
與 CTR(c3)
的後 16 bytes 做 xor 操作,兩者串接起來就能得到 c3
的值了
因此有了 c1
, c2
, c3
,我們只要去做 xor 後即可復原 flag
以下是完整的解題程式碼
AIS3{_Bl0Ck_C1PheR_mOde_m@StEr_}
以下是題目原始碼
可以看到首先他生成了 RSA 的參數,他要兩個 1024 bits 的質數作為 p
, q
,並且設定 e
為 3,而後他會將公鑰以及 flag
的加密結果印出來
接著程式讓我們可以做三次的迴圈,在每次迴圈中他會幫我們做解密的動作,並且會將解密後的結果與一個隨機的 8 bytes 的字串做 xor 之後再將結果加密印出來
這題我使用了 unintended 的解法,首先我們知道他的公鑰指數非常的小只有 3,而我們也知道在每次的連線中都會產生不同的 n
參數,而也就成為了一個非常經典的 Broadcast Attack 題目
Broadcast Attack 的原理是這樣的,假設我們有三個不同的 n
,分別為 , , ,而他們有相同的 以及共同的 ,因此我們就有了三個不同的 c
,分別為 , , ,而我們也知道 , , ,因此我們可以透過中國剩餘定理 CRT 來解方程式,而我們就能得到 的值
而由於 ,因此我們可以知道 ,因此這邊我們可以將前面的結果視同於 ,因此我們只要將 開立方根就能得到 的值了,也就是 flag
以下是完整的解題腳本
AIS3{C0pPer5mItHs_Sh0r7_P@D_a7t4CK}
預期解應該是用 coppersmith short pad attack 來解
以下是題目原始碼
可以看到他是一個 RSA 的加密程式,會生成 2048 bits 的 RSA 參數,並且會將 flag 加密之後的結果印出來,而他也會印出 以及 的結果,程式本身沒有漏洞
而另外一個檔案 trace.txt
的內容如下,可以看到他會記錄每一行執行的程式碼
而我們可以從前面的程式看到,他計算 powmod
的方式是使用 Exponentiation by squaring 的方式,也就是說他會將指數轉換成二進位的形式,並且從最低位元開始計算,當遇到 1 時就會將結果乘上 ,而在每次計算之後都會將 做平方
而因此我們可以透過這個特性來得知每一個 bit 的值,得到 powmod
的指數部分的值,而我們也可以看到在程式中有使用到 powmod(m, e, n)
以及 powmod(c, d, n)
,因此我們就能得到 和 的值了
不過在程式中他沒有輸出 n
的值,因此我們還沒辦法快樂的去解 RSA,不過我們可以透過 以及 來得到 的值,因為我們可以知道 以及 ,因此我們可以知道 以及 ,對二者做 GCD 就能得到 的值了,在經過 RSA 的解密計算之後我們就能得到 flag
以下是我的解題腳本,由於出題者有提到 int 在 python 中的運算速度很慢,因此需要使用了 gmpy2 來加速運算,不過儘管如此我還是花了大概 1 ~ 2 個小時才跑出來,不知道是不是哪裡出了問題
拿到 flag 的數值之後,我們只要將他轉成 bytes 就能得到 flag 了
AIS3{51D3_Ch@NneL_15_E4sy_WhEN_7H3_D@TA_LE@ka9E_1s_exAct}
首先我們先將 binary 放進 IDA 做分析,以下是 main
函式的部分
可以看到他會做 calloc
分配一塊記憶體,並在一些位置上塞入看起來像是 elf 的相關格式的東東。此外也塞入了 shellcode 的部分,而最後程式會將這塊記憶體寫入 flag.exe
中
一個簡單分析裡面的 shellcode 的方法是直接去執行這個程式讓他產生 flag.exe
再作分析,不過實際上去執行後會發現檔案裡面是空的,而從以下的 writeFile
程式碼中可以發現他根本就不會寫入任何東西
因此我們只好直接的來分析 shellcode
的部分,不過可以發現 IDA 把它解壞了,變成從中間開始解析,因此我們需要將中間這些解析完成的部分先用 u
還原成 raw bytes 之後在開頭的地方再來用 c
來重新解析
解析完成之後我們就能看到一些特別的字串出現了
而為了讓我們更方便的分析,我們可以在 shellcode
的地方按 P
讓他重新分析函式。而此時我們就能從 main
-> shellcode
的地方看到一個 SHELLCODE_0
的函式,裡面就是 shellcode 的完整邏輯了
我們可以從程式碼中猜到這個 shellcode
會使用 kernel32.dll
的 LoadLibraryA
函式來載入 user32.dll
的 MessageBoxA
函式,很可能他是要顯示一個 message box,而 message box 的文字部分則是一串寫在程式碼中的數值與 PAIN~~!!
的字串計算一些公式之後做 xor 的解密,而經過測試這個公式其實就等同於一般的 xor 加密而已
因此,我們可以寫一個簡單的程式來解密,以下是解題腳本,執行完之後就能拿到 flag
AIS3{U$1ng_WINd0wS_I5_such_@_P@1n....}
附上我拿 firstblood 的圖
首先老樣子我們先將程式丟進 IDA 分析,以下是進入點 WinMain
的部分
可以看到他有使用到 CreateWindowExW
的函式,應該是一個視窗型程式,而我們可以從文件中知道 lpfnWndProc
就是該視窗程式的 callback 函式,裡面會有相關的邏輯,我們可以追入看看
首先我們可以先來看一下該函式的下半部分,如下
可以看到這邊是一些處理各事件的邏輯,包含像是說關閉視窗、滑鼠點擊、滑鼠移動等等,可以看到基本上沒有與 flag 太相關的部分,頂多是說在點擊時會設定 pixel 以及將一個變數做 +1
而接著我可以來看一下這個函式上半部分,如下
可以看到當前面的變數模 600 餘一時,他會執行中間的這段程式,首先會呼叫 sub_140001A60
的函式,並會做一些事情之後進到 27 行的迴圈中並做一些比較,當比較只要不通過時就會輸出 You are bad at clicking pixels
的訊息,而只要比較完 360000 個 pixel 並且都通過時就會輸出 Perdect Match
的訊息,可以看到這是比較關鍵的部分了
而不過在 sub_140001A60
的程式部分較為複雜,我只看得出是去 resource 的部分取得一些資料出來做一些運算,因此我這邊沒有繼續去逆該函式,而是使用動態分析的方法取得資料出來
首先我們可以知道第 10 行的資料應該是程式預期的資料結果,因此我們可以嘗試去 dump 出來,這邊我使用了 x64dbg
下中斷在這個位置 (透過自動中斷於程式進入點得知 code base address 之後去計算 offset 得知要中斷的位置,使用 bp
指令進行中斷),之後我使用 Cheat Engine
透過抓數值的方式去找出 dword_140005708
這個代表點擊次數的變數的所在位置,並且去修改他的值成為 359995 之類的,只要我們再點擊個幾下之後就會因為程式邏輯而進入到這個中斷點,此時再利用外掛的 scylla
來 dump 出該位置的記憶體 (Scylla
-> File
-> dump memory
,Size
的部分寫一個足夠大的數字即可,這邊我填的是 0x01000000
),我們就能拿到要比較的資料了
而我們可以猜得到這個資料應該是一個圖片的每個 bits,且大小應該是 600 * 600,因此我寫了一個簡單的程式來將他轉成圖片,以下是程式的部分
而經過一些測試與修改之後,我找到了最佳的參數並拿到了一張圖片,flag 在圖片中 (雖然我不知道為什麼圖片是綠色的就是了)
AIS3{ju$t_4_5iMPlE_clICKEr_9@m3}
首先一樣我們先將 binary 丟進 IDA 做分析,以下是 main
函式的部分
可以看到首先程式會將 argv 輸入的部分複製到 dest
陣列中,並會將這個陣列傳入 state_machine
的函式,最後會比較執行完之後 dest
陣列值是否與 k_target
相同,相同的話就會輸出 Correct!!!
否則輸出 Wrong!!!
以下是 state_machine
的函式
首先可以看到他會定義一些 state 之後,根據 v5
的值執行不同的函式,並在最後更新 v5
成下一個 state 直到沒有遇到匹配的 state 為止
而以下是每個 state 會執行的函式的範例,可以看到基本上就是單純的對陣列做操作而已
而我們可以嘗試使用 angr
來解,畢竟他沒有太多的 IO,也沒有太多的分支,設定得宜的話應該是可以解出來,不過我實際去測試時發現會執行較久,可能有待優化,因此我採用另一個比較辛苦的方式來解題
首先我們可以用工人智慧的方式去慢慢地將 state 的運算順序取出來,並使用 z3
的方式來設定輸入的 symbol,並根據運算的順序去操作這些符號,最後設定結果要等同於 k_target
的值來做約束,如此一來應該就可以解出原始輸入的 flag
以下是解題的腳本,中間的部分是我取出來的運算順序,執行完之後就能拿到 flag 了
AIS3{Ar3_YoU_@_sTAtEful_Or_S7@T3LeS$_CtF3R}
以下是題目原始碼節錄
首先可以看到該程式有設定 seccomp,基本上限制只能 orw 讀檔案,沒辦法開 shell
接著在主程式中可以看到首先會要求我們輸入一個號碼作為樂透,接著會印出該位置的 index 值,接著會要求我們輸入姓名之後去檢查該 index 值是否等於 jackpot
,如果是的話就會執行 jackpot
印出假的 flag,不然就會印出 You get nothing QQ
以下是 Makefile,可以看到他沒有開 PIE 及 Canary
我們可以從題目原始碼看出有幾個漏洞,首先他沒有檢查輸入 index 的範圍,代表我們有辦法做 OOB read,此外另一個很明顯的是在 name
的部分有一個 stack overflow 的漏洞,他只有 100 bytes 的空間卻可寫入 0x100 bytes (256 bytes),而另外實際上在 binary 中 name
的位置是在 $rbp-0x70
的位置開始,因此我們可以 overflow 1 個 rbp + 17 個 qword
雖然我們可以透過 oob read 去 leak 出 libc base,但是由於我們不能開 shell,因此我們必須使用 orw 讀檔案,而我初步寫的 orw rop chain 需要遠超於 17 個 qword 的大小,因此我們必須要做 stack pivoting
因此我們首先需要去 leak 出 libc base 以及 name
陣列的位置,而經過觀察 stack 上的資料我們可以找到對應的 offset 並取得相關的 leak,以下是解題腳本相關的部分
為了避免因為在 leak 第二次時的 main
函式有一些 libc
函式會檢查 rbp
的值,因此這邊我是先 leak
出 stack 上的資料,並調整 rbp
成為一個合理的值,之後才能正常的跳回去執行 main
函式的部分,leak 出 libc base
而接著是主要的 exploit 部分,由於我的 orw rop chain 需要共 21 個 qword (每個項目各 7 個),不管是全塞在 name
或是 overflow 的 stack 部分都不夠放,因此我將功能切開來做,在 overflow 的 stack 區塊執行完 open 和 read 之後做一個 leave
pivoting 到 name
陣列的位置,而 name
函式繼續執行剩下的 write rop chain,如此一來就能執行完 orw 了,另外我在 read 時拿到的 flag 的內容是放在 bss 區段上面,避免影響到 rop chain 的執行
以下是 exploit 的部分,相關的 gadget 使用 ROPgadget 工具找得
完整的 exploit 如下,執行完之後就能拿到 flag
AIS3{JU5T_a_eA5y_INT_0VeRfloW_4nD_BUf_ovErfL0W}
話說雖然出題者說 flag 在根目錄下,但我是直接去讀取當前目錄的 flag
檔案,不知道為什麼這樣拿到的 flag 也是正確的就是了 🤔