第一次參加為期三天AIS3 pre-exam,酷ㄛ > <
不過我這歲數來說好像有點晚了,看到好多高中生參賽不禁回憶自己高中到底在幹嘛www
平常在CTFTime的線上賽已經習慣找stavhaygn求開示,這次個人賽只能靠自己了 QwQ
Preface
先放上人權圖(Rank 113/427),雖然成績不怎麼樣www


這份writeup基本上把300分以下的補完了,如果有其他建議還請不吝指教。
(各題原始分數為500分,根據CTFd的計分規則,題目到180人解就會降到最底100分。)
官方Writeups
Misc
💤 Piquero (100 points, 347 solves)
Description
Solution
利用線上工具 Braille Decoder 即可,要注意的是點字的大寫規則(.a = A),包含我在內很多人的錯誤率都從這邊來的XD
若想瞭解一下細節不妨可以參考 啾啾鞋 - ⠓⠥⠈⠨⠐⠙⠌⠂⠙⠯⠈⠙⠞⠈⠓⠱⠐⠕。
🐥 Karuego (100 points, 245 solves)
Description
題目給了一張Karuego圖(捏他「入間同學入魔了!」),想玩的請自取 HERE。
Solution
直覺就是圖片隱寫術(Steganography),通常都會先用binwalk
看看有沒有藏檔案,不過這裡我直接拿去丟stegsolve
神器,從LSB不小心挖到奇怪的key,這才回頭用foremost
找藏在圖片中的壓縮檔XD。
Solution 2
賽後和stavhaygn聊才知道用binwalk
找到壓縮檔後可以用KPA(known plaintext attack)工具PKcrack去爆密碼,在官方的Discord群組也有看到同樣說法,看來也是預期解之一。
🌱 Soy (139 points, 172 solves)
Description
Solution
利用線上工具 qrazybox 將QRcode還原。從QRcode長寬可知ver.2,至於 format info 可以透過 Error Correction Level 和 Mask Pattern 的找到和題目相同的組合,剩下就格子點一點就有Flag了。
👑 Saburo (359 points, 108 solves)
Description
題目需要nc到官方Server,透過輸入字串後會得到”Haha, you lose in xx milliseconds.”,其中的xx是隨機變動的數字(同樣輸入字串”a”,回傳的數字範圍會在11-15浮動),透過測試會發現輸入越接近flag數字會越大,直到猜到flag為止。
PS. flag is printable characters with AIS3{…}
Solution
對於要用來測試的table,考慮到flag除了英文字母及數字,可能還會有{
, }
, _
之類的,可以利用python string library。 之後逐步猜字,用統計的方式把table每個字元都測10遍找回傳ms最大的,如果最後抓不到回傳的數字代表戳到flag了XD。
另外還有一點,由於過程中需要不斷nc官方Server,因此需要特別用conn.close()
關閉連線,不然跑久了會報錯,個人在這邊吃鱉一陣子…可見寫程式的習慣還是很重要的。
1 | from pwn import * |
👿 Shichirou (Post-solved, 450 points, 65 solves)
Description
同樣是需要nc到官方Server,透過瞭解source code想辦法取得flag。
1 | #!/usr/bin/env python3 |
Solution
賽後聽stavhaygn說這題對MacacaHub算考古題… 來自去年金盾獎決賽= =
不過從遊記108金盾獎決賽看得出來我沒有參與到解題過程,可惡QwQ
簡單解釋一下上方的code:
- line 12 基本上就是輸入數字,即檔案大小。
- line 17, 18 輸入tar包裝檔案(可以利用pwntool),然後在line 23進行解包。
- line 29 - 35 針對官方Server的兩個檔案
guess.txt
和flag.txt
進行sha1比對,若相同就吐出flag。
我起初乍看之下還以為是sha1 bypass,但我們並不能改變Server的檔案,那我們到底是要上傳啥哩?
關鍵就是 Symbolic link,有點像是建立捷徑的概念,把 guess.txt
和 flag.txt
連結在一起。
首先,在本地端用指令建立連結,透過ls
查看。
1 | ln -s flag.txt guess.txt |
再來就是進行tar打包(無壓縮)。
1 | tar cvf guess.tar guess.txt |
如此我們就能得到一個tar檔案,再來把扣摳一摳就行了吧。
1 | from pwn import * |
BOOOOOOOM,RUN起來結果看起來是爛掉了。
從第四行看得出來 guess.txt
和 flag.txt
似乎不在同一個目錄中。
不過錯誤提示也把路徑告訴我們了,簡單講大guy長得像底下這樣。
1 | /home/ctf/ |
那就把連結的部分刪掉重來一遍!
1 | rm guess.txt guess.tar # 記得把舊檔砍掉 |
再一次把py檔run起來就能拿flag了~
Reverse
🍍 TsaiBro (100 points, 281 solves)
Description
題目給了一份ELF檔 TsaiBro 和一份密語(?) TsaiSaid。
Solution
題目幾乎同去年AIS3 pre-exam,似乎是出題者被上層指示每項分類要有一題和往年方向相同,差別在ELF檔內的table不同。
如果直接執行ELF檔,可以發現輸出除了第一句之外,之後的”發財…”會隨著輸入的字串而改變(如下),因此我們的目標就是要把題目另外附的密語還原成FLAG。
1 | chmod +x ./TsaiBro # 給檔案可執行的權限 |
針對TsaiBro,我們打開IDA Pro好朋友版使用F5大法,可以發現程式內主要轉換的方式如下:
1 | table = "56789{}_WXY0yzABabcdmnopSTUVGHIJKLMNuvwxefghqrstijklOPQRCDEF1234"; |
針對輸入argv[1]
的每個字元,程式會先查表(table),然後分別算出 j / 8 + 1
個點 和 j % 8 + 1
個點。
由此可知道我們可以反過來把密語中的”…”兩兩一組回推 j
再回頭查表就能把 FLAG 推出來了!
1 |
|
🎹 Fallen Beat (144 points, 171 solves)
Description
題目給了一份用JAVA寫的音樂遊戲 Fallen Beat,必須要達到 FC(Full Combo, 全音符皆有按到)才會給FLAG,事後聽說似乎是出自中大計概的期末project,好猛XDDD
遊戲方式如下
P.S. 注意遊戲音量,不過因為我的電腦喇叭爛掉所以我也沒開過聲音就是了wwwwww
Solution
如果遊戲方式只有單純D, F, J, K
四鍵或許我還會拼拼看,不過加上space
其實還蠻干擾的QwQ
既然這題分類都在Reverse,本來就不是給人直接玩出FLAG… 如果有誰錄手元我還是蠻期待的wwww
註: 「手元」指的是用錄影方式將實際遊玩指法紀錄起來
回歸正題,既然要逆jar,當然就找JAVA Decompiler,這裡我用 JD-GUI,然後把jar扔進去觀察一下,雖然說class不多到處亂翻也會有線索,不過既然是FC結束才出現FLAG,自然可以往PanelEnding.class
的方向挖,嗯就是有個FLAG放在那裡(笑)
利用關鍵字循線往下看… 果然還是會經過一些計算,不會這麼容易直接把FLAG給出來QQ
仔細看一下可以發現我們還少了很重要的變數cache
資訊。
這裡其實我卡了一小段時間,因為回追總是會在某個環節就追丟,可能也是自己對JAVA不太熟的緣故吧 = v =
最後還是讓我找到了一個文字檔hell.txt
,看起來蠻像是遊戲用的譜面(?)
而關於這個hell.txt
是個1475行的純數字檔,節錄開頭如下。
1 | 186 |
搭配前面所找到的FLAG的計算,該有的線索都有就能直接回推結果了。
1 |
|
Solution 2
和Lab朋友聊過才知道原來還有其他解法,就是直接用patch的方式改成無視FC,不過似乎還會被排版搞(?) 要再手動調位置。賽後官方Discord群組對話出現下面這張,如果真的有人手動FC看到這排版應該會哭出來吧XDDDDD
P.S. 歌曲小科普(待補)
🧠 Stand up!Brain (455 points, 62 solves)
Description
這題給了一份ELF檔 joke,並且在引言中表示「這次輪到你說個笑話來聽聽了」。
Solution
(細節待補)
其實原本想照IDA Pro的規則先手動推推看,沒想到就把「笑話」推出來了 ㄏ
Pwn
部分題目需要召喚 stavhaygn‘s writeup (`・ω・´)
👻 BOF (100 points, 189 solves)
Description
(待補)
Solution
(待補)
Crypto
🦕 Brontosaurus (100 points, 380 solves)
Description
題目給了一份字數多達42k的純文字檔 HERE,以下僅節錄開頭一部分。
)()]]][+[+][+!+][+![)]]][+!+[)][+][!!(+]][+!+][+!+][+![)][+][!!(+]][+[)][+][!!(+]][+!+][+![)][+][!(+]]][+[+][+!+[)]][[][+]][![(+]][+[)][+][!([][+][!!(+]]][+!+][+![+][+!+[)(]]][+!+[)][+][!!(+]]][+[+][+!+[)]]][+!+[)][+][!!(+]][+!+][+!+][+![)][+][!!(+]][+[)][+][!!(+]][+!+][+![)][+][!(+]]][+[+][+!+[)]][[][+]][![(+]][+[)][+][!([][+][!!(+]][+!+][+![)][+][!(+]]][+[+][+!+[)]]][+!+[)][+][!!(+]][+!+][+!+][+![)][+][!!(+]][+[)][+][!!(+]][+!+][+![)][+][!(+]]][+[+][+!+[)]][[][+]][![(+]][+[)][+][!([][+][!!(+]][+!+][+!+][+![)][+]]][+!+[)][+][!!(+]][+!+][+!+][+![)][+][!!(+]][+[)][+][!!(+]][+!+][+![)][+][!(+]]][+[+][+!+[)]][[][+]][![(+]][+[)][+][!([][(+]][+[)][+] |
Solution
事後發現題目同去年AIS3 pre-exam,似乎是出題者被上層指示每項分類要有一題和往年方向相同。
不過當下其實就看得出是JSFuck
(被身邊朋友荼毒過了Zzzzz),只是直接複製到 jsfuck decoder 會失敗,正當我覺得納悶的時候才發現字首是 )
,索性開python做字串反轉[::-1]
,再一次decode就有flag了~
事後和沒有參加pre-exam的朋友分享才被點出檔名”KcufSJ”就提示要反轉了XD
🦖 T-Rex (100 points, 381 solves)
Description
題目同樣給了一份純文字檔 HERE,而且也不難看出上半部和下半部之間的關聯。
1 | ! @ # $ % & |
Solution
簡單講就是查表回推flag,只是在群組真的聽到不少人直接用紙筆硬推(而且還推歪),怕爆。
既然都身為資訊科系寫個程式不為過吧 XDDDD
|
看看這充滿惡意的flag… 根本就沒打算讓人用手推XDDDDD
AIS3{TYR4NN0S4URU5_R3X_GIV3_Y0U_SOMETHING_RANDOM_5TD6XQIVN3H7EUF8ODET4T3H907HUC69L6LTSH4KN3EURN49BIOUY6HBFCVJRZP0O83FWM0Z59IISJ5A2VFQG1QJ0LECYLA0A1UYIHTIIT1IWH0JX4T3ZJ1KSBRM9GED63CJVBQHQORVEJZELUJW5UG78B9PP1SIRM1IF500H52USDPIVRK7VGZULBO3RRE1OLNGNALX}
Web
🦈 Shark (100 points, 261 solves)
Description
打開畫面後有個Link Shark never cries?
,點開如下。
Solution
題目幾乎同去年AIS3 pre-exam,似乎是出題者被上層指示每項分類要有一題和往年方向相同。
雖說如此,我在Day1戳半天戳不出結果,到Day2才發現考古題這件事 XD
首先,從Link的後綴可以觀察到?path=hint.txt
,基本上可以聯想到LFI
相關的題型,透過幾個基本的 php://filter
嘗試撈出source code。
例如 php://filter/convert.base64-encode/resource=index.php
,把拿到的base64還原可以得到下面code。
1 |
|
不過看起來沒有我們想要的資訊,只知道檔了很多存取目錄的方式,而確定php://filter
方法可行。
再來就是題目一開始給的提示”Please find the other server in the internal network! (flag is on that server)”
那就來翻翻看 /etc/hosts
… 不過沒有我們要的資訊。
Day1我就就卡在這裡,直到找到去年writeup才發現有/proc/net/fib_trie
可查到內網的ip位址。
1 | Main: |
之後就照著writeup走,找找172.22.0.3
附近的ip,例如 172.22.0.2
。
最後直接 ?path=http://172.22.0.2/flag
就能拿flag了 ~
🐿 Squirrel (Post-solved, 100 points, 220 solves)
Description
點進連結 畫面就是一堆松鼠帶著系統目錄跑來跑去…
題外話… 同樣用Chrome不同系統看的emoji都不太一樣 蠻有趣的wwwww
Solution
解題人數明明破200位,但直到競賽結尾我還是沒解出來,事後證明我想太多了 QwQ
首先,從網頁source code可以看到些許端倪。
透過網址後綴 /api.php?get=/etc/passwd
可以撈到一些資訊,但這題看起來是沒什麼用。
基本上賽中我這裡就卡住了,一直想從網頁source code 的 Error Handling 下手 …
其實,仔細想一下就可以發現,?get=
+ file name
能夠查看檔案內容… 那就不就是command cat
嗎!!!!
既然如此,那來看看cat api.php
應該也沒有問題吧!
透過網址後綴 /api.php?get=api.php
還真的把 api.php
source code 撈出來了XD
從這份source code可以看到是用shell_exec
執行指令,並且用 '
前後閉合起來。
那我們下一步就能利用 command injection 的 ;
截斷執行我們想要的command ~
例如 /api.php?get=';ls'
,記得要加上 '
將前後閉合ㄛ (同SQLi的概念)
能夠列出當前目錄,那就能繼續深入找找是否有些好玩的東西(?)
例如 /api.php?get=';ls ../../../'
好像… 有個可疑的檔案?
嘿嘿那就來看看裡面有沒有flag吧!
開心使用 /api.php?get='; cat ../../../5qu1rr3l_15_4_k1nd_0f_b16_r47.txt'
,有了!!!
不過打了這麼長一串… 是不是忘記那個 ?get=
其實就等同 cat
功能 www
直接在後面接目錄檔名就行了 /api.php?get=../../../5qu1rr3l_15_4_k1nd_0f_b16_r47.txt
🐘 Elephant (168 points, 165 solves)
Description
點進連結,可以看到是個登入頁面(還有需要反白的彩蛋??)
隨意輸入後,下方說明文字說明token不足以讀flag(同樣有需要反白的彩蛋??)
Solution
這題當初真的是超乎預期地通靈拿到flag,完全不明白考點在哪 = =
「所以才說有些題目就是知識斷片,才要用通靈的方式跳上去」 by 逆逆
因此賽後找了stavhaygn完整還原一下這題的重點。
首先簡單講個人的通靈解法 XD
登入後可以把cookies的字串拿去base64 decode得到下列字串。
1 | O:4:"User":2:{s:4:"name";s:4:"haha";s:11:"Usertoken";s:32:"6cdc79ae4ac3bd4ade8162dc68d2d50d";} |
格式上看得出是type:length:data
,但我發現中間的s:11:"Usertoken"
… 算算長度應該是 9 吧(?)
嗯。我就真的手動改成s:9:"Usertoken"
再base64 encode後填回去cookies,重新整理頁面後就噴flag了…
(???黑人問號)
其實上面的字串格式叫做「序列化(serialization)」,我平常沒什麼摸真的不太瞭解 www
正確的作法應該是從/.git
,可以發現並非 404 not found 而是 403 forbidden。
利用GitHack,把檔案還原出來。
1 |
|
這份source code有幾個重點:
- line 8 token的類別定義為
private
(稍後會提到) - line 11 token產生的方式為Random + MD5 Hash
- line 14 拿flag的條件:
$flag == $this->token
- line 28 serialize function
這題關鍵在strcmp bypass
。
而$flag
寫在後端無法修改,我們也沒辦法控制token的random值。
從網路找的資訊為strcmp
在遇到字串和陣列比較時將回傳NULL,利用PHP的弱型別特性 NULL == 0
會成立。
那我們不就只要把拿到的序列化字串手動修改不就行了嗎 ~
1 | O:4:"User":2:{s:4:"name";s:4:"haha";s:11:"Usertoken";a:0:{};} |
很遺憾… 不行。如果改完再encode貼到cookies會被登出。
問題在於一些程式產生的不可視字元,同時也是我一開始發現字串長度很奇怪的部分。
關於PHP序列化格式,可以參考這篇 一文让PHP反序列化从入门到进阶
其中類別中的三種權限的表示方式:
public:
data
private: %00class name
%00member name
protected: %00*%00member name
也能夠說明為何我看到的字串長度少 2(%00
* 2),同時也無法直接修改。
那這些看不見的字元自然不能被手動複製貼上,那怎麼辦哩? … 既然是由程式產生的,那就寫回去ㄚ。
可以直接照搬上面的產生serialize的程式碼,再將原本token產生的方式改成array即可!
1 |
|
用PHP online之類的執行完就能把拿到的base64 code貼回去cookies,重新整理頁面拿flag ~
🐍 Snake (Post-solved, 272 points, 137 solves)
Description
點開連結,題目直接把code以純文字顯示在頁面上。
1 | from flask import Flask, Response, request |
Solution
簡單瞭解這份code:
- line 10 表示我們可以透過後綴
/?data=
接一些字串 - line 14 表示字串必須為base64格式
- line 15 pickle … ? 原來是肚子餓的部分(X)
- line 17 永遠不可能成立的條件
用Google搜尋 “pickle ctf” 可以找到不少資料,可以參考這篇 pickle反序列化初探。
簡單講pickle
是python用來序列化及反序列化的 Python library。
當我們透過網址傳送序列化字串後,Server端會執行反序列化並執行物件中的__reduce__
,因此我們這次將__reduce__
內容設計成system read /flag file,Server回傳的就會是flag的內容,另外要注意的是回傳格式(callable, tuple
),後者的參數會交給前者執行。
1 | import pickle |
剩下就是把產生出來的base64 code丟到網址後綴拿flag ~/?data=Y19fYnVpbHRpbl9fCmV2YWwKcDAKKFMib3BlbignL2ZsYWcnKS5yZWFkKCkiCnAxCnRwMgpScDMKLg==