--- tags: CTF --- # 計算機安全 HW0 Writeup - Real name: 張瀚文 - Nickname on course website: Hwww - Student ID: b07505027. ## Web ### 解法 點進去網頁後,直接看Source Code,發現是後端伺服器的code。 研究一下code,發現`/auth`這個公開的api會先驗證傳過去的`username`及`cute`是否正確,然後在發送一個request到不公開的api去取得flag或是圖片。 因為我們沒有辦法直接發送request到不公開的api,所以要想辦法讓我們傳過去`/auth`的東西,可以覆蓋掉已經寫死的程式碼,有點類似SQL Injection。其中有兩個地方需要去覆蓋: 1. `userInfo` 中的 `"admin": false`:利用「json對於相同的鍵,後面的值會覆蓋前面的值」這點,只要想辦法在他後面塞進 `"admin”: true` 就好了。這裡看起來可以利用 `"cute":${cute}` 去植入資料,只要簡單讓 `cute` 是 `true, “admin”: true` 這個字串就可以了,測試一下,果然讓<(_ _)>出來了 2. 接下來是api中的 `givemeflag=no`:這個沒有辦法用覆蓋的,因為node.js中重複的query string會被解析成一個陣列,而且就算換成其他語言,因為寫死的程式碼放在最後面,也沒有辦法覆蓋掉。所以我們要想辦法讓他「註解」掉,而這裡我們就需要知道,request中如果含有 `#` (URI fragment),`#` 後面的資料只會作為瀏覽器使用,並不會傳出去到伺服器,因此就可以註解掉寫死的 `givemeflag`,在前面插入偽造的資料,比如 `givemeflag=yes#givemeflag=no` 然後就可以開始想要怎麼把這些東西放進去,因為 `/auth` 對 `username` 的檢查比較嚴格,不能包含特殊字元,而且他的位置也不是很好,所以不考慮他,考慮 `cute`。`cute` 的檢查只有結尾是不是 `true` 或 `false` 而已,所以我們可以在前面塞上任何想塞的東西,只要在後面放上 `true` 就好了,比如 `cute=abcd#true`。 綜合上面的觀察,現在 `cute` 應該要放: ``` cute=true, “admin”: true}&givemeflag=yes#true ``` 並且要讓他在伺服器端是一個字串的形式 但是,如果在第一次發送request的時候直接把 `&` 跟 `#` 寫上去,會讓瀏覽器當成query string,被解析成如下: ![](https://i.imgur.com/OpR0SXH.png) 要傳入特殊字元,必須要將 `&` 改成 `%26`,`#` 改成 `%23`,改完後整個request變成: ``` /auth?username=aaa&cute=true,"admin":true}%26givemeflag=yes%23true ``` 送過去之後就會顯示flag! ![](https://i.imgur.com/zdr21Vm.png) ### 過程及心得 這題卡最久的地方是沒有想到可以用 `#` 來卡掉 `givemeflag=no`,前面admin的<(_ _)>很快就弄出來了,本來還想是不是像SQL Injection一樣,要傳入`//` 等註解符號讓他把 `givemeflag` 註解掉,但javascript好像不吃這一套,後來試了很久才想到可以用#來卡掉。 ## Pwn ### 解法 `nc` 過去後只是輸入名字,然後什麼都沒有發生 把檔案下載下來後是一個執行檔,先用ida開開看,是ELF檔,翻翻看有沒有可疑的字串,找到有個可疑的 `func1`,裡面似乎有Here you go跟Not quite right等字串,最重要的是有 `system("/bin/sh")` 這行可以讓我們取得ssh 反編譯成C code後,可以看出要傳一個參數等於 `0xCAFECAFECAFE` 進去,讓他等式成立,執行到 `system("/bin/sh")` 取得ssh 但是後來發現,`func1` 根本沒有被呼叫的機會,那要怎麼讓 `func1` 被執行呢......? 上網查了一下發現,這題其實是典型的buffer overflow題,只要讓輸入的name溢位,把return address覆蓋成 `func1` 的位置,`main` 執行完後就會跳到`func1` ,也就是我們想要的結果。 用gdb在本地跑,先輸入很多a,試著讓他溢位看看,結果真的出現 `SIGSEGV` 現在要戳戳看怎麼樣剛好填到return address 丟丟看 `AAAAAAAAAAAAAAAABCDEFGHIJKLMNOPQRSTUVWXYZ`,用 `info frame` 看看結果會如何: ``` saved rip = 0x51504f4e4d4c4b4a ``` 發現前16個A會被丟到buffer裡面,接著後8個字會被放到Arglist裡(B為0x42),再後8個字會被放到rip裡,也就是我們的目標位置。用ida複製func1的位置 `0x0000000000401176`,在丟進去之前要先轉換一下格式,本來想直接用手打看看,但似乎沒辦法手打,只好用pwntools寫程式試試看,完整程式碼請參考`expo.py`,主要的步驟就是連上去主機,然後自動輸入名字,名字裡送了24個padding加上 `func1` 的位置,成功執行到了 `func1`,輸出 `Not quite right`,接下來要想辦法讓條件成立,執行到 `system("/bin/sh")`。 把 `0xCAFECAFECAFE` 放到padding中看看,發現放在開頭(`args + padding + target_address`)可以成功讓等式成立,輸出 `Here you go!` ![](https://i.imgur.com/LuMTcXr.png) 這裡發生了問題,程式在呼叫 `system("/bin/sh")` 的時候壞掉了,用pwntools的gdb來debug看看發現(debug程式碼為 `expo.local.py`),程式在呼叫 `system("/bin/sh")` 前,用 `info all-registers` 看一下 ![](https://i.imgur.com/qq6pAzz.png) 因為程式的檢查機制,rbp要是0x10的倍數,也就是要以0結尾,而我們從`0x401176` 開始執行,push rbp後會讓rsp跟rbp的位置變得不合法,執行完後會直接壞掉,沒有辦法拿到ssh…… 後來戳戳看func1中的其他位置,試試看`0x401177`,沒有戳壞rsp及rbp,都是以0結尾,才終於正常取得ssh。 ![](https://i.imgur.com/kqNJJ35.png) 拿到後就翻翻看資料夾,在`/home/Cafeoverflow/`底下找到flag,`cat`後就出來了! ![](https://i.imgur.com/PAYRxwQ.png) ### 心得 這題對我來說滿難的,因為以前沒有看過類似的東西,又需要用到很多工具,不過還好上課有教,題型又很常見,整個學起來還滿順的,卡最大的地方是明明出現Here you go卻沒辦法拿到ssh,感覺快要過了卻不知道為什麼錯,本來想說是不是把stack的哪裡給戳爛了,一直在試padding要改成什麼比較好,比如說應該要把padding改成一個合理函數的位置,讓程式不會crash等,後來才知道,是因為系統會檢查位址,rbp要以0結尾,所以亂戳會失敗。 ## Misc ### 解法 開source code後發現,是一個買賣東西的程式,一個東西88.88塊,我們剛開始有0元,必須要憑空生出3000元才會得到flag,而且必須要12次買賣嘗試內。照著程式正常的設計是沒辦法拿到3000塊的,因此這裡看起來就是利用浮點數的誤差,來憑空生出3000塊,然後限制在12次以內,所以如果一輪買賣大約 3~4 次會得到一些錢的話,必須要在3~4輪拿到3000塊。試了幾組後找到一組操作可以花費4次賺到約1024塊。詳細嘗試過的組合放在 `pC.txt` 中 試了3輪之後果然過了!拿到3072塊 ### 心得 這題感覺滿簡單的,很容易聯想到跟浮點數有關,甚至題目就提示了,然後隨意嘗試了幾組後也可以很快發現可以賺到錢的組合,所以很快就解完了~ ## Cryptography ### 解法 看一下程式碼,發現裡面有加密函數,跟正轉換函數與逆轉換函數,分別用來將位元組轉成整數、將整數轉成位元組。轉成中文的部分跟 `curse` 不影響程式的功能。 這題的觀念很簡單:只要知道加密程式碼就有辦法寫出解密程式碼。因為這題的`_加密`函數是用位元運算寫的,而且每一次迴圈只會與上一個狀態有關,而且對於`向量[i]`的上一個狀態又恰好是`向量[!i]`,也就是上一個狀態被存起來了,所以只要把程式碼倒著寫就可以寫成`_解密`函數,加法的部分變成減法,`累加`要改成`累減`,`得優塔`跟`遮罩`不用變,詳細請見`decrypt.py`中的`_解密`。而`加密`函數只是把長度為16的明文分成兩部分加密而已(8, 8),所以需要更改的不大,大致上把`_加密`改成`_解密`、`明文`與`密鑰`顛倒就好,而正逆轉換因為只是轉換單位,所以也不影響過程。寫個程式驗證解密函數是不是對的,請見`verify.py`,其中把加密跟解密的過程也輸出出來了。 處理完解密後,另一個問題是我們不知道密鑰是什麼,而密鑰又是隨機生成的。幸好大家都知道利用`random.seed`產生的亂數,只要seed相同,每次程式執行的亂數順序都會是一樣的,而且這題的seed又是利用`time.time()`來取得,`time.time()`會回傳從1970年到現在經過了多少秒,也就是說,如果要暴搜的話,最多只要搜約$1.6*10^9$次,而且因為生成密鑰的程式也很有可能是最近執行的,所以也很有可能只要暴搜到幾個月就好。 現在問題變成要怎麼驗證一個暴搜出來的密鑰是不是真正的密鑰。我們知道明文的開頭,幾乎都是flag{或是FLAG{等,所以只要比對解密出的前5個字,是不是flag{或FLAG{等就好。 寫程式來暴搜,搜到9/13後就搜到正解了。詳見`decrypt.py`,只要執行等他跑就好了。 ![](https://i.imgur.com/Uxy40ZG.png) ![](https://i.imgur.com/SGhFcAN.png) ### 心得 這題剛開始看也是毫無頭緒,剛開始覺得加密函數太複雜,甚至以為forbiddenfruit是出題者寫的套件,我們要去猜裡面做了什麼,但後來才知道那是一個用來修改Python內建東西的套件,在這裡用意只是拿來把英文變成中文而已......後來慢慢去理解程式就覺得其實也滿簡單的,知道有加密就可以寫解密後也很快就寫出解密來了。後來卡在驗證密鑰,本來以為flag{一定是小寫的,搜了好久都沒搜到,後來改成大寫FLAG{一下就搜到了。 ## Reversing Engineering ### 解法 透過正常手段沒有辦法完成拼圖。程式是 .NET,直接拿dnspy反編譯看看(要用32位元開),反編譯成功後,想辦法修改拼圖的順序。在`Form1`中的建構子找到了初始化拼圖的地方,直接把調換的兩行刪掉 ![](https://i.imgur.com/Z00rMfu.png) 儲存之後執行,拼圖是拚好的,左右移動來讓他觸發`samonCheck`就拿到flag了!幸好`samonCheck`中是沒有問題的。 ![](https://i.imgur.com/jg9VchR.png) ### 心得 這題用dnspy後就變得非常簡單,除了調換的兩行程式碼之外其他都沒有問題。剛開始用strings跟ida看,都看不出什麼端倪,只看到`this is your flag`,但卻沒看到flag,後來才知道可以用dnspy輕鬆解決~