DEF CON CTF Qualifier 2022 WriteUps
這次在 Balsn.217@TSJ.tw 的聯隊中拿下了第二名,不過身為一個 web/crypto player 在這場比賽中真的覺得做不了什麼事,因為題目都太 rev/pwn heavy 了。這邊也稍微粗略紀錄些有參與的題目的解法,因為很多題目也都是靠多人合作才完成的。
sameold
這題要找到一個字串 x
以隊伍名稱的 punycode
為開頭,後面可以接任意的 alphanumeric 字元,要使得
crc32(x) == crc32("the")
。
最簡單的解法就是直接
bruteforce,這個也是預期解法。不過有趣的一個地方是這次比賽有隊伍名稱就叫
the
,所以可以直接輕鬆解掉這題。
不過我後來還有弄個使用 crc 性質的數學解法,需要一些線性代數去處理。對於 m bits -> n bits 的 crc 來說
其中
我有為這個寫了個 sage script: crcmat.sage
所以將 concat 後的字串視為
可知當正常情況下
去問了 Utaha 才知道可以利用 ASCII 0~7
字元的 ascii code
都是 00110???
的格式,所以每個字元都能視為是一個
00110000
和 3 個向量的組合,append n 個字元的話就是個常數
k
向量 (0011000000110000....
),然後和多出來的
3n 個自由向量。這邊暫且把那些自由向量稱做 basis
,所以有
d + span(kernel) = k + span(basis)
,因此 kernel 和 basis
整理到同一邊去解這個 linear system,然後取
sol[:dim(kernel)] * kernel + d
就會是個 alphanumeric
的向量,同時也能保持 crc 相同。
Solver: DEF CON Quals 2022- sameold
Hash it
reverse 它的 binary 可以知道它會拿 input bytes 兩兩 hash,取 hash 的第一個 byte 組成 shellcode 最後執行。hash 部分是 md5, sha1, sha256, sha512 輪迴,所以就按照它的邏輯寫個腳本把 shellcode 爆破回原本的 input bytes 即可。
腳本: DEF CON Quals 2022 - Hash It
discoteq
這題關於 Flutter Web/Desktop 寫的一個聊天軟體,admin (bot) 使用了 desktop 版本接收你的訊息。
通訊部分使用的是 websocket,傳送和接收的部分都大概屬於這個格式:
1 | {"type":"widget","widget":"/widget/charmessage","author":{"user":"aaaa#8763","platform":"web"},"recipients":["admin#13371337"],"data":{"message":"abc"}} |
其中 widget
部分是在 client 接收的時候會進到
getChatWidget
處理:
1 | getChatWidget(url) { |
url
部分是直接 concat 的,所以如果 widget
是 @attacker.host/malicious_file
就能控制進入
RemoteWidgetLibrary
的 payload。
這個功能是 Flutter 的 Remote Flutter Widgets 功能,它算是個 serialization 格式。把那個 library 抓下來跑,然後嘗試把它原本的 rfw 檔案也抓下來解析並比較看看它有什麼功能。
總之最後可以知道它有自己加個 ApiMapper
可以讓你 get json
然後抓某個 key 出來設定到 data 中,另外還有 post_api
的
event 可以 POST data 的資料出去到其他地方,url 一樣能透過 @
讓它將資料傳到自己的 server。
所以就想辦法組合讓 admin 把 /api/token
的
new_token
傳到自己的 server,然後拿 token 去 POST
/api/flag
即可。
Payload 和其他相關的腳本: DEF CON Quals 2022 - discoteq
admad
這題就一個 Python 的 pyc reverse 題目,flag checker 類型的,但是它提供了自己編譯的一個 python interpreter,還有很多的額外 library 也都被移除掉了。
題目的 Python 版本它有直接說是 702e0da000bf28aa20cb7f3893b575d977506495,所以可以抓下來編譯,不過編譯之後嘗試跑它的
chall.pyc
會直接 segfault,只能用它給的 python 跑才行。
splitline 說這題會炸是因為它的 bytecode 有置換過,但 binary 是 stripped 的很難直接 reverse 出來哪些有改過。解決辦法是參考 kholia/dedrop 去寫個包含(幾乎)所有 opcode 的 python 檔,然後讓他們生成兩個 pyc,之後透過比較 opcode 去造一個 mapping 出來。
得到 mapping 之後可以把整個 pyc 重新寫過讓它能在正常的 interpreter
執行,然後自己 dis.dis
。得到的 bytecode
簡單讀一下可以發現它就只是取 flag index,然後利用 &
運算和一個 mask 去比較而已,這部分可以很容易的將它還原回能夠通過檢查的
input。
這部分可以參考 rev.py。
不過還原之後會發現它的輸入像是亂碼,根本不是 flag,後來還是靠隊友通靈出它可以用這樣的方法還原:
1 | test = b'\x8e\x86\x8d\x95\xbb\xaa\xb9\xb2\xc8\xb3\xba\xc5\xaf\xc3\xc8\xc7\xc5\xb9\xcf\xe3\xe3\xe6\xd3\xd3\xd1\xe4\xe1\xcd\xe3\xf2\xed\xfa\xe0\xe9\xea\xddC0EH\xe7\xf3\x0f\xed\x06\x17\x02\xf5_Z^\x13`\x16\x15fhj)' |
結果後來才知道它的 interpreter 也有把 bytes
修改過,就是如果參數是 list 且 list[0] == ord('F')
的話就會把它的值亂搞,使用了類似上面的公式去輸出的。
router-niii
這題是 router 系列的第三題,主要是要去打某個從 /vm
下載的 binary,它是處理 update 資料的程式,實際上也是個 interpreter
有自己的 opcode。
我在這邊一開始有注意到它的 binary 的 call
都被混淆成了一些奇怪的東西,所以就嘗試利用 capstone 和 pwntools
把它修回正常的 call
,也能正常執行和在 IDA 中反編譯: vm_patch.py。
然後後來 orange 成功將它 reverse 出來,然後生出可以讀
/flag3
的 payload,然而它的 vm 還會接受個 seed
參數 (./vm <seed>
) 當作
srand(seed)
,然後利用輸出的值亂序執行
opcode,所以必須要知道 server 端是怎麼執行 ./vm
的才能知道
seed。
不過要做到這件事基本上需要能利用 OOB 讀取 (/ping?id=???
)
把 server binary dump 下來然後 reverse 才行。不過由於它 dump 的時候一次
4096 bytes,沒能讓我把 ELF header 也 dump 回來去拚回一個可以 reverse 的
ELF,所以也沒能 reverse 成功,這也是解第二題 router-nii 所需的關鍵。
不過後來亂試之後發現 /ping?id=1000
的地方有奇怪的資料在,發現它基本上除了前四個 bytes 以外大部分都是
null,所以我就通靈說這該不會就是 ./vm <seed>
的
seed。然後寫了個腳本整合 orange 的 payload
就發現說真的是這樣,然後就很幸運的拿到了第三題的 flag ==。
腳本可以參考 solve.py。