USTC Hackergame 2018 小結
最近一邊在前後端開發實習一邊在複習準備考試,還是挺忙的,或者說,嗯,忙到爆炸。拖這麼久才更新博客,順便也更新了自己的 Journal. 主題。
不過既然是想着找點事情分散注意力,還是重在參與,解題的過程不還是挺有趣的麼。
簽到題#
是個 <Input/> 的小把戲,找個帶 Inspector 的現代瀏覽器改一下即可。你非要 cURL 發 POST 請求也不是不可以。
知識問答#
答案略。有沒有找回小時候抄答案結果「答案略」的失落感?
藉助 Google 即可。
Puzzle 拼個圖#
上來就是一個壓縮包,解壓之後就是如下的碎片,這不明擺着要拼圖麼。

呃,我好久不玩拼圖了。要打印出來麼?算了,用 Adobe Illustrator。

羣裏有人直接取消桌面圖標對齊,在桌面上用縮略圖拼,我覺得點子不錯,是個人才。
被貓貓玩壞的代碼#
其實仔細觀察就不難發現它只是一個被按照某種方式變換了列的文件。

VIM 是好東西,但是後面別的題我們用 VIM,這道題就讓 VSC 出場吧。
幾番折騰之後,代碼看起來像正經 C++ 了,應該可以用了。

不過我的 Clang 比較舊,C++17 有些無力,於是我隨便找了個 Online compiler 跑一下。

能直接跑 C++17 的網站還是不多,雖然磕磕絆絆,但至少還是吐出了我們想要的 Flag。
面對 0 Errors, 9999 Warnings 毫不慌張是一個程序員的基本素養。
我是誰#
說實話,我也不知道我是誰,我可能是瑪奇朵,也可能是你們熟知的杯裝飲料。
他們這個頁面原始的 padding-top 看的我特別難受。

上來便是一個簡單的表單,出於直覺應該打開 DevTools 碰碰運氣。

拿到 Flag,頁面給出了通向下一個謎題的鏈接。然而……

其他 Methods,看來直接 GET 還是太天真了。想到這裏,我們還要考慮到這個小傢伙可能是個茶壺,我們是不是應該用…… BREW 方法來請求它呢?畢竟上文的 RFC 2324 中也有提到過這個方法。
2.1.1 The BREW method, and the use of POST
...
A coffee pot server MUST accept both the BREW and POST method
equivalently. However, the use of POST for causing actions to happen
is deprecated.
...那就來試一下……

似乎少了一些重要的東西,根據提示應該就是 Headers 的鍋啦。
1. The "message/coffeepot" media type
The entity body of a POST or BREW request MUST be of Content-Type
"message/coffeepot". Since most of the information for controlling
the coffee pot is conveyed by the additional headers, the content of
"message/coffeepot" contains only a coffee-message-body:
...那我們帶上 Content-Type: message/coffeepot 的 Header 看看。

人家是茶壺啦,那就改成 Content-Type: message/teapot 好了。

再根據 Response headers 中的提示,順藤摸瓜。

We have got two flags in a row. Yay!
Office Open XML#
下載到的是名爲 OfficeOpenXML.docx 的文件,可以在 Office 中打開,文檔表面看只是維基百科 Office Open XML 條目[5]中的內容 ,我無趣的用某個 Hex editor 在文件中尋找了一下包含 flag 的字符串,發現了 flag.txt。既然這個形似文件名東西被找到了,就直接解壓好了,得到 Flag。

祕籍殘篇#
這啥啊……
Malbolge,一種編程語言,可以參考它的 Wikipedia 頁面[6],我不想說,太燒腦。類似的語言還有 Brainf**k。
文件是可以使用 Malbolge interpreter 運行的,比如這個用 C 語言寫的原版[7],但是也可以隨便找一個網站在線執行。
執行的結果是如下:
$ ./mal malbolge.txt
Key: 12345678
Wrong!隨便輸入不小於八字符的 Key 便會出現結果,這裏沒什麼思路,就看看第一問好了。
文件空格比較多,文字編輯器字號放到最小,感覺有點 ASCII art 的意思,於是……

中國滑稽大學,有點意思,不過真的很難對齊,這是我對齊最清楚的一次結果。
連猜帶看的,拼湊出來 flxg{University_of_Ridiculous}。
貓咪銀行#
你們最好的語言 PHP,好就好在它經常被拿來出 CTF 題。(滑稽)
進入之後是一個簡陋的頁面,定義了幾種貨幣和轉換關係。

當然,下面有我們想要的商品 —— Flag。

完整的買不起,買碎片是沒有任何效果的(我試過了)。到這裏就要想一下,難道是使用某種兌換和存入的方案可以使得玩家剛好可以賺得足夠的虛擬貨幣來買 Flag 麼?似乎不是,畢竟 PHP 是最好的語言。拋開弱類型,最基本的測試大概也就是「溢出」了。
那我就來試試……

呃……

似乎溢出使可以取出收益的時間變得更早了,直接取出即可拿到所有收益。
買爆……

還有冷卻時間,不過已經不算什麼了。
解決。
黑曜石瀏覽器#
你收到一個女裝紅包,請使用女裝版手機啾啾查看。

HEICORE 還行,出題人都在玩梗。既然沒有什麼其他線索,那就 Google 一下瞧瞧黑曜石瀏覽器是啥子。

還有官網,厲害了。但當你準備打開熟悉的 DevTools 時,就會發現網頁瞧瞧地清除掉了你的 Console,甚至玩起了 404。


但是網頁的源代碼還是比較容易弄到的,可以看到有對於 HEICORE 瀏覽器 UA 的定義。(也是玩梗很開心的樣子)
function isLatestHEICORE() {
var ua = navigator.userAgent
var HEICORE_UA = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) HEICORE/49.1.2623.213 Safari/537.36'
return ua === HEICORE_UA
}
那就換下 UA 吧……(嫌棄)

解決。沒啥技術含量……
回到過去#
是一個上個世紀的大叔用 ed 編輯文件的故事,按照文件提供的內容重放(重新操作)一下就會把正確的 Flag 拼裝出來。不過文件中包含 ^C,直接用文件內容代替自己操作或是用了部分不顯示 0x1B 的編輯器都有可能把你帶到坑中。就像下圖一樣。

最終 Flag 是 flag{t4a2b8c44039f93345a3d9b2}。
生活在今天,我們總是理所當然地認爲文本編輯器可以輕鬆選擇光標位置、查找替換、自動保存,這道題倒是爲我提供了一個可以去體驗歷史久遠的編輯器是如何工作的機會。這種形似考古的體驗也足以讓我感到興奮。
貓咪遙控器#
在家裏玩貓貓的時候,激光筆是逗貓貓的神器。
題目給出的文件打開後都是類似 RRRRRRRDDDDLLLLLLLLLLLLLLLLDDDDDDRRRRRRRR 的序列,只由 L、U、R 和 D 四個字母組成,總感覺是可以畫出來的方向。那就來畫畫看。

我選擇了在 JSFiddle 上[8]使用 JavaScript 的 Canvas,當然 Python 也可以,不過和 VIM 一樣,Python 我們後面也會用到,這裏就先用 JavaScript 吧。
貓咪電路#
Minecraft 的紅石電路,先看一遍邏輯電路演示,然後慢慢操作就可以了。最後輸出到信標的信號是 1 時,所有開關的狀態轉寫爲 0 和 1 組成的字符串就是 Flag 本體啦。
Flag 是 flag{0110101000111100101111111111111111111010}。
有 3D 眩暈症的瑪奇朵做這道題的時候真的很難受,平時 FPS 不敢玩,MC 也不敢玩太久,玩久就會暈的超難受。
貓咪剋星#
其實這道題就是製造一個透過 Socket 來接收 Python 表達式、eval 並把結果發回去的程序,人力 eval 就不要想了,會超時的。(笑)
def netcat(hostname, port, content):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((hostname, port))
if content is not None:
s.sendall(content)
data = s.recv(1024)
if data == '':
return
while 1:
line = s.recv(1024).decode("utf-8").replace('\n', '')
if data == '':
break
print(">>> %s" % line)
result = eval(line)
s.sendall(('%s\n' % result).encode('utf-8'))
print("Connection closed.")
s.close()
netcat("202.38.95.46", 12009, None)但是別高興得太早,這道題壞就壞在它下發的表達式可能長這樣:
(((int(5!=**import**('time').sleep(100))|1)*(int(**import**('os').system('find ~')==57)+14))|((int(83!=exit())-4)&(91**82)))總之就是會摻着耗時操作 and/or 退出,會導致超時,得不到最終的 Flag。
但是 Python 下 hook 一些 built-in 函數實在是太簡單了吧,直接 def 函數即可:
class Import:
def system(self, **args):
pass
def sleep(self, **args):
pass
def __import__(x):
return Import
def exit():
pass最後的表達式是:
>>> flag{'Life_1s_sh0rt_use_PYTH0N'*1000}也就是 Flag 了。
她的詩#
拜我對編碼產生懷疑之心所賜,這道題我卡關蠻久。
題目給出了一個 Python 文件和一個文本文件,Python 文件的內容主要是按行解碼 Uuencode 編碼的文本。
#!/usr/bin/env python3
# This script helps you decode "her poem"
from codecs import decode
fin = open("poem.txt", "r")
fout = open("poem.out", "w")
for i in fin:
data = "begin 666 <data>\n" + i + " \nend\n"
decode_data = decode(data.encode("ascii"), "uu")
print(decode_data)
fout.write(decode_data.decode("ascii") + "\n")
fin.close()
fout.close()文本內容如下:
)+2TM+2TM+2TM
@5&AE<F4@:7,@<V]M971H:6YG(&EN('1H:7,@=V]R;&1F
A=&AA="!N;R!O;F4@:&%S(&5V97(@<V5E;B!B969O<F4N
7270@:7,@9V5N=&QE(&%N9"!S=V5E="YL
:36%Y8F4@:68@:70@8V]U;&0@8F4@<V5E;BQA
...直接運行 Python 程序會得到 poem.out 文件,內容如下:
---
There is something in this world
that no one has ever seen before.
It is gentle and sweet.
Maybe if it could be seen,
everyone would fight over it.
That is why the world hid it,
so that no one could get their hands
on it so easily.
However, someday, someone will find it.
The person who deserves it the most
will definitely find it.
...這首詩解碼後的幾個小節都出自『龍與虎』、『Clannad』和『氷菓』,但是題目中有文字提醒大家不要在字面意思上糾結,這樣看來還並不是和它的內容或是背後的作品有關。
於是參考 Wikipedia 對於 Uuencoding 的介紹[9],其中有一個對於每行編碼格式的定義:
<length character><formatted characters><newline>由此可見,每一行首個字符的 ASCII 值便是這一行包含字節的數量加上 32 得到的。
疑點#
好像哪裏不對勁?
以原文第二行爲例:
7270@:7,@9V5N=&QE(&%N9"!S=V5E="YL7 的 ASCII 值爲 0x37 即 55,也就是說它標記這一行除了長度字元應包含 55 - 32 = 23 bytes = 184 bits,而這一行除去長度字元還有 32 個字符。又由 Uuencoding 是每四個由 6 bits 表示的字符轉換爲三個由 8 bits 表示的字符,所以 32 個字符對應 24 個原文字符。
It is gentle and sweet.現在來看一下這一行解碼後的結果,只有 23 個字符,你可能會說 \n 也算一個字符,但仔細看一下上面的解碼代碼就會發現每行的換行符並不是解碼出來的,而是在 write 的時候加上的。
看來還是藏了一些不得了的東西。
想個辦法#
編碼後的文本通過改小每行首部長度標記的方法來夾帶「私貨」的事情敗露了,現在我們就要想個辦法來把它的「私貨」揪出來。
其實做法也不難,因爲每行我們只需要改動第一個字符:
#!/usr/bin/env python3
# This script helps you decode "her poem"
from codecs import decode
fin = open("poem.txt", "r")
fout = open("poem.out", "w")
for i in fin:
i = chr(ord(i[0]) + 1) + i[1:] # add this line
data = "begin 666 <data>\n" + i + " \nend\n"
decode_data = decode(data.encode("ascii"), "uu")
print(decode_data)
fout.write(decode_data.decode("ascii") + "\n")
fin.close()
fout.close()對原有的 Python 進行小改動即可,之後查看輸出的文件可能包含一些無法顯示的字符,但我們可以確認的是,它的私貨暴露了出來,也就是我們想要的 Flag。
---
There is something in this worldf
that no one has ever seen before.
It is gentle and sweet.l
Maybe if it could be seen,a
everyone would fight over it.g
That is why the world hid it,{
so that no one could get their hands
on it so easily.ST
However, someday, someone will find it.
The person who deserves it the moste
will definitely find it
...把每行尾部多出來的字符都串起來,得到 flag{STegAn0grAPhy_w1tH_uUeNc0DE_I5_50_fun}。
It is painful! Not fun at all!!
不過不得不承認出題人還是挺機智的。
FLXG 和六十四卦#
故事講一大堆,主要線索只有一個文件,來看看內容。

我隨便截的,都差不多,截哪裏不重要了。根據題目線索這應該是六十四卦,一提到卦,就想到不少把卦和二次元二進制聯繫到一起的故事。
解密碼要碼錶,現在我們需要一個卦表,於是我在萬能的 GitHub 上找到了一個開源項目[10],其中有一個記錄卦名與其對應二進制值的 JSON 文件。
我也寫了一個粗糙的 Python 程序[11]來轉譯這個文件中的卦名到二進制數據,在輸出時反轉了文件的順序,這樣在頭部附近就可以找到 Flxg(Flag)。

See what we have found!
不反轉的話,你可以把文件拉到底,就是正序的 Flag 啦。
Pwn Me: Calc#
我連 checksec 都懶得看,這應該也算是個有點不爭氣的 Pwn 題吧?
這種用 nc 來玩的東西,要麼是像 Python 那道題交互操作最後給 Flag,要麼是放在服務器上讓你自己想辦法看文件。既然題目都瘋狂暗示是後者了,那我們就開始挖洞吧。
題目給了一個 calc 程序,但是我當時只有 macOS,無法運行,只能 IDA Pro 走起。
現在建起來了在 macOS 下很方便用的 Kali 虛擬機,在近期解決 CCUT 的一道 Pwn 題時很方便,後續可能會單獨寫一篇 CCUT 的小結。(雖然我不是報名選手)
進來先搜 Strings,要是直接能搜出來 Flag 字符串,這題也就沒啥好做的了。
其實流程也沒什麼好看的,我想找找可以構造格式化字符串溢出的地方,但是並沒有找到。

再看看有沒有 system 和 sh 相關的東西,結果找到了 execlp 的調用。

這不明擺着是扯着脖子喊「Pwn me pls!」麼?
還可以看到它註冊了幾個 Signal handler:

感覺能用上的也就是 SIGFPE 和 SIGSEGV。但之前看格式化字符串溢出的機會幾乎沒有,於是我只好把希望寄託於 SIGFPE 之上,最經典的方法是把一個數除以零,但是我們能看到代碼中已經加了(一部分)對於這個情況的檢查。
但是還存在一個可能會引發異常的做法,那就是 INT_MIN / -1。
本地和服務器上測試一下:

It works again!
值得一提的是,因爲我當時不能在本地直接執行 calc 這個程序,於是我便照着 IDA 的結果實現了一個供測試用的版本,並且忘記了把原來程序中的
alarm加進去,直到在服務器上測試時我才意識到原來的程序只有五秒的操作時間,在本地測試只能LD_PRELOADhook 掉alarm這個東西。
既然可以執行命令了,那我們是不是可以拿到 Flag 了呢?
試試 ls:
Program crashed! You can run a program to examine:
ls -
bin
calc
dev
flag
lib
lib32
lib64看來還是有 flag 文件存在的,那我們再試試 cat flag:
Program crashed! You can run a program to examine:
cat flag
flag好像哪裏不對勁。再去看下 IDA 中的結果,就會發現輸入的命令會截斷爲 10 字符。並且能存在空格,在 alarm 設置爲 5 秒的情況下,暴力測試還是比較難受的。
還記得我們之前說過,VIM 會在後面使用,那我們就試試 VIM。

啊哈。VIM 真是太棒了,還幫我們解除掉了 alarm 的問題。那我們就直接用它打開 flag 好了。
RIP, Bram Moolenaar.

VIM: Not here!

Got it!!
後記#
草草地總結了一下自己做過的題,和其他做出來計算和密碼類題目的大佬相比,是在慚愧。當時比賽時實習那邊還搞出了點部署的失誤,心亂亂的。不過回想這次經歷也是蠻有趣的,賽後看到大家寫的 Write-up 感覺很多地方都可以學習。說到這裏,我在大概兩年前結識了另一位好友,他讓我感受到了信息安全這一領域的樂趣,從那之後我也打開了通往新世界的大門,還去 SRC 提交過一次 XSS 漏洞,現在雖然有些迷茫,但也願他一切安好。深夜編輯,如有錯誤歡迎指正。
Keep thinking & hack for fun.