この大会は2024/3/30 8:00(JST)~2024/4/1 8:00(JST)に開催されました。
今回もチームで参戦。結果は4138点で854チーム中141位でした。
自分で解けた問題をWriteupとして書いておきます。
Beginner: Basic Reversing Problem (Reverse Engineering)
Ghidraでデコンパイルする。
undefined8 main(EVP_PKEY_CTX *param_1,EVP_PKEY *param_2) { keygen(param_1,param_2); return 0; } int keygen(EVP_PKEY_CTX *ctx,EVP_PKEY *pkey) { long in_FS_OFFSET; undefined local_28 [24]; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); l1(local_28); if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return 0; } void l1(undefined *param_1) { *param_1 = 0x75; l2(param_1 + 1); return; } void l2(undefined *param_1) { *param_1 = 0x74; l3(param_1 + 1); return; } void l3(undefined *param_1) { *param_1 = 0x66; l4(param_1 + 1); return; } void l4(undefined *param_1) { *param_1 = 0x6c; l5(param_1 + 1); return; } void l5(undefined *param_1) { *param_1 = 0x61; l6(param_1 + 1); return; } void l6(undefined *param_1) { *param_1 = 0x67; l7(param_1 + 1); return; } void l7(undefined *param_1) { *param_1 = 0x7b; l8(param_1 + 1); return; } void l8(undefined *param_1) { *param_1 = 0x69; l9(param_1 + 1); return; } void l9(undefined *param_1) { *param_1 = 0x5f; l10(param_1 + 1); return; } void l10(undefined *param_1) { *param_1 = 99; l11(param_1 + 1); return; } void l11(undefined *param_1) { *param_1 = 0x34; l12(param_1 + 1); return; } void l12(undefined *param_1) { *param_1 = 0x6e; l13(param_1 + 1); return; } void l13(undefined *param_1) { *param_1 = 0x5f; l14(param_1 + 1); return; } void l14(undefined *param_1) { *param_1 = 0x72; l15(param_1 + 1); return; } void l15(undefined *param_1) { *param_1 = 0x33; l16(param_1 + 1); return; } void l16(undefined *param_1) { *param_1 = 0x76; l17(param_1 + 1); return; } void l17(undefined *param_1) { *param_1 = 0x21; l18(param_1 + 1); return; } void l18(undefined *param_1) { *param_1 = 0x7d; l19(param_1 + 1); return; } void l19(undefined *param_1) { *param_1 = 0; return; }
順にコードを文字にしていけばよい。
>>> codes = [0x75, 0x74, 0x66, 0x6c, 0x61, 0x67, 0x7b, 0x69, 0x5f, 99, 0x34, 0x6e, 0x5f, 0x72, 0x33, 0x76, 0x21, 0x7d] >>> ''.join([chr(code) for code in codes]) 'utflag{i_c4n_r3v!}'
utflag{i_c4n_r3v!}
Beginner: Off-Brand Cookie Clicker (Web)
HTMLソースを見ると、以下のスクリプトが書かれている。
<script> document.addEventListener('DOMContentLoaded', function() { var count = parseInt(localStorage.getItem('count')) || 0; var cookieImage = document.getElementById('cookieImage'); var display = document.getElementById('clickCount'); display.textContent = count; cookieImage.addEventListener('click', function() { count++; display.textContent = count; localStorage.setItem('count', count); if (count >= 10000000) { fetch('/click', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'count=' + count }) .then(response => response.json()) .then(data => { alert(data.flag); }); } }); }); </script>
クリック回数が10000000回以上の場合の処理を実行する。
$ curl http://betta.utctf.live:8138/click -H "Content-Type: application/x-www-form-urlencoded" -d "count=10000000" {"flag":"Wow, you beat me. Congrats! utflag{y0u_cl1ck_pr3tty_f4st}"}
utflag{y0u_cl1ck_pr3tty_f4st}
Contracts (Forensics)
https://www.extractpdf.com/で画像を抽出すると、フラグが書かれた画像が抽出できた。
utflag{s1mple_w1z4rding_mist4k3s}
RSA-256 (Cryptography)
Nをfactordbで素因数分解する。
N = 1025252665848145091840062845209085931 * 75575216771551332467177108987001026743883
あとは通常通り、RSA暗号の復号をする。
#!/usr/bin/env python3 from Crypto.Util.number import * with open('vals.txt', 'r') as f: params = f.read().splitlines() N = int(params[0].split(' ')[-1]) e = int(params[1].split(' ')[-1]) c = int(params[2].split(' ')[-1]) p = 1025252665848145091840062845209085931 q = 75575216771551332467177108987001026743883 assert p * q == N phi = (p - 1) * (q - 1) d = inverse(e, phi) m = pow(c, d, N) flag = long_to_bytes(m).decode() print(flag)
utflag{just_send_plaintext}
Beginner: Anti-dcode.fr (Cryptography)
シーザー暗号26パターンを実行し、"utflag{"が含まれるものを探し、フラグを抽出する。
#!/usr/bin/env python3 import string import re def caesar(s, key): d = '' for c in s: code = ord(c) if c in string.ascii_uppercase: code = code - key if code < ord('A'): code += 26 elif c in string.ascii_lowercase: code = code - key if code < ord('a'): code += 26 d += chr(code) return d with open('LoooongCaesarCipher.txt', 'r') as f: enc = f.read() for i in range(26): dec = caesar(enc, i) if 'utflag{' in dec: pattern = '(utflag\{[a-z_]+\})' m = re.search(pattern, dec) flag = m.group(1) print(flag) break
utflag{rip_dcode}
numbers go brrr (Cryptography)
サーバの処理概要は以下の通り。
・seed: 0以上10**6以下のランダム整数 ・以下繰り返し ・user_input: 数値入力 ・user_inputが1の場合、繰り返し終了 ・message: 入力 ・encrypt(message.encode())を表示 ・key = b'' ・8回以下繰り返し ・keyにget_random_number() % (2 ** 16)の2バイト文字を追加 ・ciphertext: messageをパディングし、AES暗号化 ・ciphertextを16進数表記で返却 ・flag: フラグ ・encrypt(flag.encode())を表示
seedをブルートフォースし、平文と暗号文の組み合わせが合う鍵を探し、フラグを復号する。
#!/usr/bin/env python3 import socket from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from Crypto.Random import random def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) def get_random_number(): global seed seed = int(str(seed * seed).zfill(12)[3:9]) return seed def encrypt(message): key = b'' for i in range(8): key += (get_random_number() % (2 ** 16)).to_bytes(2, 'big') cipher = AES.new(key, AES.MODE_ECB) ciphertext = cipher.encrypt(pad(message, AES.block_size)) return ciphertext.hex() s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('betta.utctf.live', 7356)) data = recvuntil(s, b'\n').rstrip() print(data) try_pt = '1234' data = recvuntil(s, b'\n').rstrip() print(data) print('2') s.sendall(b'2\n') data = recvuntil(s, b'\n').rstrip() print(data) print(try_pt) s.sendall(try_pt.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) try_ct_hex = data.split(' ')[-1] data = recvuntil(s, b'\n').rstrip() print(data) print('1') s.sendall(b'1\n') data = recvuntil(s, b'\n').rstrip() print(data) enc_flag = bytes.fromhex(data.split(' ')[-1]) print() for seed in range(10 ** 6 + 1): ct_hex = encrypt(try_pt.encode()) if ct_hex == try_ct_hex: break key = b'' for i in range(8): key += (get_random_number() % (2 ** 16)).to_bytes(2, 'big') cipher = AES.new(key, AES.MODE_ECB) flag = unpad(cipher.decrypt(enc_flag), AES.block_size).decode() print(flag)
実行結果は以下の通り。
Thanks for using our encryption service! To get the encrypted flag, type 1. To encrypt a message, type 2. What would you like to do (1 - get encrypted flag, 2 - encrypt a message)? 2 What is your message? 1234 Here is your encrypted message: a8254764914d607479e2a1f3a48fd44c What would you like to do (1 - get encrypted flag, 2 - encrypt a message)? 1 Here is the encrypted flag: 6f6a9ce8ada3ef9d09a1c953332ec706b0c6a37b57fcd406c4b3e8435c649a2320920190e4656fca057271a8aa2d206f utflag{deep_seated_and_recurring_self-doubts}
utflag{deep_seated_and_recurring_self-doubts}
bits and pieces (Cryptography)
1つ目のRSA暗号はFermat法でnを素因数分解して復号する。また2つ目と3つ目のRSA暗号は共通する素数をGCDを使用して割り出し、それぞれ素因数分解して復号する。
#!/usr/bin/env python3 from Crypto.Util.number import * def isqrt(n): x = n y = (x + n // x) // 2 while y < x: x = y y = (x + n // x) // 2 return x def fermat(n): x = isqrt(n) + 1 y = isqrt(x * x - n) while True: w = x * x - n - y * y if w == 0: break elif w > 0: y += 1 else: x += 1 return x - y, x + y with open('vals.txt', 'r') as f: params = f.read().splitlines() n1 = int(params[0].split(': ')[1]) e1 = int(params[1].split(': ')[1]) c1 = int(params[2].split(': ')[1]) n2 = int(params[4].split(': ')[1]) e2 = int(params[5].split(': ')[1]) c2 = int(params[6].split(': ')[1]) n3 = int(params[8].split(': ')[1]) e3 = int(params[9].split(': ')[1]) c3 = int(params[10].split(': ')[1]) assert e1 == e2 == e3 p, q = fermat(n1) phi = (p - 1) * (q - 1) d = inverse(e1, phi) m = pow(c1, d, n1) flag1 = long_to_bytes(m).decode() p = GCD(n2, n3) assert p != 1 q2 = n2 // p phi = (p - 1) * (q2 - 1) d = inverse(e2, phi) m = pow(c2, d, n2) flag2 = long_to_bytes(m).decode() q3 = n3 // p phi = (p - 1) * (q3 - 1) d = inverse(e3, phi) m = pow(c3, d, n3) flag3 = long_to_bytes(m).decode() flag = flag1 + flag2 + flag3 print(flag)
utflag{oh_no_it_didnt_work_</3_i_guess_i_can_just_use_standard_libraries_in_the_future}
Cryptordle (Cryptography)
サーバの処理概要は以下の通り。
・wordlist: 単語の配列 ・wordlistの各単語wordについて、以下を実行 ・wordの長さが5であることをチェック ・wordの各文字letterが英小文字であることをチェック ・3回以下繰り返し ・answer: wordlistから1つ選択 ・num_guesses = 0 ・以下繰り返し ・num_guesses += 1 ・guess: 入力 ・guessがanswerと一致している場合、繰り返し終了 ・response = 1 ・5回以下繰り返し(x) ・a: guess[x]の英小文字インデックス ・b: answer[x]の英小文字インデックス ・response = (response * (a-b)) % 31 ・responseを表示 ・num_guessesが6より大きい場合終了 ・フラグを表示
1箇所変更し、何倍になるかで答えとなる単語を導き出す。
#!/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(('betta.utctf.live', 7496)) aj = ord('j') - ord('a') ak = ord('k') - ord('a') for attempt in range(3): answer = '' data = recvuntil(s, b'\n').rstrip() print(data) guess_base = 'jjjjj' print(guess_base) s.sendall(guess_base.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) base = int(data) for x in range(4): data = recvuntil(s, b'\n').rstrip() print(data) guess = 'j' * x + 'k' + 'j' * (4 - x) print(guess) s.sendall(guess.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) val = int(data) for b in range(26): if (aj - b) * val % 31 == (ak - b) * base % 31: char = chr(ord('a') + b) answer += char break base_response = 1 for x in range(4): a = ord(guess_base[x]) - ord('a') b = ord(answer[x]) - ord('a') base_response = (base_response * (a - b)) % 31 for b in range(26): response = base_response a = ord(guess_base[x]) - ord('a') response = (response * (a - b)) % 31 if response == base: char = chr(ord('a') + b) answer += char break data = recvuntil(s, b'\n').rstrip() print(data) print(answer) s.sendall(answer.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
What's your guess? jjjjj 5 What's your guess? kjjjj 17 What's your guess? jkjjj 16 What's your guess? jjkjj 10 What's your guess? jjjkj 24 What's your guess? grimy Good job! Onward... What's your guess? jjjjj 4 What's your guess? kjjjj 6 What's your guess? jkjjj 1 What's your guess? jjkjj 7 What's your guess? jjjkj 16 What's your guess? hasty Good job! Onward... What's your guess? jjjjj 9 What's your guess? kjjjj 17 What's your guess? jkjjj 8 What's your guess? jjkjj 8 What's your guess? jjjkj 10 What's your guess? essay Good job! Onward... Nice! You got it :) Have a flag: utflag{sometimes_pure_guessing_is_the_strat}
utflag{sometimes_pure_guessing_is_the_strat}
numbers go brrr 2 (Cryptography)
サーバの処理概要は以下の通り。
・seed: 0以上10**6以下のランダム整数 ・以下3回繰り返し ・seed: 0以上10**6以下のランダム整数 ・key = encrypt(b"random text to initalize key")[0] ・以下繰り返し ・user_input: 数値入力 ・user_inputが1の場合、繰り返し終了 ・message: 入力 ・key, ciphertext = encrypt(message.encode()) ・ciphertextを表示 ・found = False ・以下250回繰り返し ・guess: 入力 ・guessとkeyが一致している場合 ・found = True ・繰り返し終了 ・guessとkeyが一致していない場合 ・該当メッセージを表示 ・foundがFalseの場合終了 ・フラグを表示
seedをブルートフォースし、平文と暗号文の組み合わせが合う鍵を探す。
#!/usr/bin/env python3 import socket from Crypto.Cipher import AES from Crypto.Util.Padding import pad from Crypto.Random import random def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) def get_random_number(): global seed seed = int(str(seed * seed).zfill(12)[3:9]) return seed def encrypt(message): key = b'' for i in range(8): key += (get_random_number() % (2 ** 16)).to_bytes(2, 'big') cipher = AES.new(key, AES.MODE_ECB) ciphertext = cipher.encrypt(pad(message, AES.block_size)) return key.hex(), ciphertext.hex() s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('betta.utctf.live', 2435)) data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data) for i in range(3): data = recvuntil(s, b'\n').rstrip() print(data) try_pt = '1234' data = recvuntil(s, b'\n').rstrip() print(data) print('2') s.sendall(b'2\n') data = recvuntil(s, b'\n').rstrip() print(data) print(try_pt) s.sendall(try_pt.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) try_ct_hex = data.split(' ')[-1] for seed in range(10 ** 6 + 1): key_hex, ct_hex = encrypt(try_pt.encode()) if ct_hex == try_ct_hex: break data = recvuntil(s, b'\n').rstrip() print(data) print('1') s.sendall(b'1\n') data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data) print(key_hex) s.sendall(key_hex.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
Thanks for using our encryption service! To get the start guessing, type 1. To encrypt a message, type 2. You will need to guess the key (you get 250 guesses for one key). You will do this 3 times! Find the key 1 of 3! What would you like to do (1 - guess the key, 2 - encrypt a message)? 2 What is your message? 1234 Here is your encrypted message: c262a1a250dc9c1c3b60940c0c8c4bc5 What would you like to do (1 - guess the key, 2 - encrypt a message)? 1 You have 250 guesses to find the key! What is your guess (in hex)? 38106fde83a77b6a724ac4ac5ce5a11f You found the key! Find the key 2 of 3! What would you like to do (1 - guess the key, 2 - encrypt a message)? 2 What is your message? 1234 Here is your encrypted message: 5dd809df595f3f7ad9193d84ad104e54 What would you like to do (1 - guess the key, 2 - encrypt a message)? 1 You have 250 guesses to find the key! What is your guess (in hex)? a6d9e9fe301ccf6409e3f7616e3d0f67 You found the key! Find the key 3 of 3! What would you like to do (1 - guess the key, 2 - encrypt a message)? 2 What is your message? 1234 Here is your encrypted message: 3fc72ed1725ec4597dfb15459aa3b0ff What would you like to do (1 - guess the key, 2 - encrypt a message)? 1 You have 250 guesses to find the key! What is your guess (in hex)? ad9b5968466e0f5f9b6bbb8bb7cfb0a3 You found the key! Here is the flag: utflag{ok_you_are_either_really_lucky_or_you_solved_it_as_intended_yay}
utflag{ok_you_are_either_really_lucky_or_you_solved_it_as_intended_yay}
simple signature (Cryptography)
$ nc betta.utctf.live 4374 Welcome to the signature generator! This service generates signatures for nonnegative integer messages. Today's RSA parameters are: n = 21256683315158182129815856331774550219736491659822013777716561403821759106942281553515127548337855434048450386309203634846966770626707737976786578578333663561713725571747549027632868189683318875229850994034558102034412982079423514302939077444742503129302726410235332341952073804606455906118875300358250493598578145242327054365472161513043655796279793521090367257196871638408895409825645953298225120130288671527609928671935379050436498665770230821360288608340575254789593194298367560007619012294850204968087401519447108433848214210445628715721764885069947977533038779890839352843397198704785901489138850520012604735409 e = 65537 Enter a message as an integer (enter 0 to stop): 2 Your signature is: 9008713126753812606011450560795999983628754565781192321534488829838369501952103936146474773001285659513690501893121485196095055975697286303499698153613457741924379705227475544080178997110805379992972098139290290226431777397101866595061818137546060738499225208101502055574688482077678941436271342264447208867798805722791535086091682216242244033815057186892893010980193607422167617166235239381106596853232383017014930464444923074422979433762404293220768415327177933596487826362256186740360846899990120510905871952493666779745057815770355935843560053301001534775647803868595046018020772095448598122540322023235388466469 Enter a message as an integer (enter 0 to stop): 0 Now, come up with your own pair! Enter a message: 2 Enter a signature: 9008713126753812606011450560795999983628754565781192321534488829838369501952103936146474773001285659513690501893121485196095055975697286303499698153613457741924379705227475544080178997110805379992972098139290290226431777397101866595061818137546060738499225208101502055574688482077678941436271342264447208867798805722791535086091682216242244033815057186892893010980193607422167617166235239381106596853232383017014930464444923074422979433762404293220768415327177933596487826362256186740360846899990120510905871952493666779745057815770355935843560053301001534775647803868595046018020772095448598122540322023235388466469 Cannot enter a message that you already requested.
すでに指定したメッセージは指定できない。
以下のように掛け算で対応する。
sign(2) * sign(3) = sign(2 * 3) = sign(6)
つまり、2のsignatureと3のsignatureを掛け算して、nで割った余りは6のsignatureになる。
#!/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(('betta.utctf.live', 4374)) for _ in range(3): data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data) n = int(data.split(' ')[-1]) data = recvuntil(s, b'\n').rstrip() print(data) e = int(data.split(' ')[-1]) msg1 = 2 data = recvuntil(s, b': ') print(data + str(msg1)) s.sendall(str(msg1).encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) sign1 = int(data.split(' ')[-1]) msg2 = 3 data = recvuntil(s, b': ') print(data + str(msg2)) s.sendall(str(msg2).encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) sign2 = int(data.split(' ')[-1]) msg = msg1 * msg2 sign = sign1 * sign2 % n data = recvuntil(s, b': ') print(data + '0') s.sendall(b'0\n') data = recvuntil(s, b': ') print(data + str(msg)) s.sendall(str(msg).encode() + b'\n') data = recvuntil(s, b': ') print(data + str(sign)) s.sendall(str(sign).encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
Welcome to the signature generator! This service generates signatures for nonnegative integer messages. Today's RSA parameters are: n = 22745040857603356094950654119013238723747220178610034852956242041562118237276081614630730515116320707642464350162664776909328033671621035970037099984090401985036500786215882362673949544724701351702912751994243681832886951060143576518069616159575153003220437956455716817271687585305182748114221157079466544219358552743750071158665042390352892225780880626672435090222836535227896004995605606638385751930854647803703207804978831496876933751774423816093334998692863816963960154660924426693605540706759797424401196054144615532821154267469412900552576245981069208807704613152854077917101279909066353977957463864413560023021 e = 65537 Enter a message as an integer (enter 0 to stop): 2 Your signature is: 9771728127420216438291835326570484519213948796085940733565111554302250933329663350576417117750608579719613061331441446546314316153007856956324333962551856613848550753055848284993047255748151261189822986688297942376939191547592466495993733461389396328425724049649199526958459886324852435254646078883654540905122012907094091320955847826798682115946480116758659104870829753562606678981451474118954274496785971392186312570363019066197559892733076246596277730910105487653771365696167538182632442947783322211008677008544608073938497673793908781559416785872041634531173325540113460746309660795478581331374905376345742352104 Enter a message as an integer (enter 0 to stop): 3 Your signature is: 15184926436984207327413599111688835154553124148571788663482988884563402565970130089438457037102287842549020950017784430846786223384555754774570708289943532606790760532829767385581104136340138806851828151461412594086502230267691292199154726841639558992521953053027264432147362707536762715897873598893894463881511315877054246526706413757426178807393768173530208334140095076590954658577489109139787717676418546920303709944283550402937938790112338850053872100781180257764868762894031181040639411839173677256161368277480431358717312962075225713465769247303150138469667451618563103163474871034508347120243927294355269289274 Enter a message as an integer (enter 0 to stop): 0 Now, come up with your own pair! Enter a message: 6 Enter a signature: 16235471999399052729022938376393109605567026071458583804559271990193467410073795381846501865827643540851419074009527643292839224065809948450423857203043598084832480568186578361637368585968559426397302661365563429713694975808010388433552999440641045697632334380163883415746830091922515758005222043603264915039782423068309087020523916089816408778463170651918121856553919915383102039188993936162422992730214593525338147092972428373731285352025588698703399984020753514983821282995427516251661888455604952762268026558670747724249577969128422578136151189780316619099973132967922260109552080743844094221685368487561433642384 Congrats! Here is the flag: utflag{a1m05t_t3xtb00k_3x3rc153}
utflag{a1m05t_t3xtb00k_3x3rc153}
Survey (Misc)
アンケートに答えたら、以下のようなメッセージが表示された。
thank yoU for filling ouT our Feedback form! pLeAse rate the ctf on ctftime when you Get a chance. rememBer that we will accept Your writEups for forTy-eigHt hours After the competitioN for writeup prizes. hooK em hornS!
大文字を並べる。
UTFLAGBYETHANKS
utflag{byethanks}