---
# System prepended metadata

title: 計算機安全 HW0 Writeup
tags: [CTF]

---

---
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輕鬆解決～
