KalmarCTF 2023 Writeup

この大会は2023/3/4 2:00(JST)~2023/3/6 2:00(JST)に開催されました。
今回もチームで参戦。結果は800点で905チーム中83位でした。
自分で解けた問題をWriteupとして書いておきます。

sewing-waste-and-agriculture-leftovers (forensic)

全てUDPの通信。一つ一つdataを送るのに失敗しているような箇所がある。各UDPストリームを見てみる。

 0: ..l..r{....t..i........d0.._.....e..m...e...u.......g.....
 1: .a..a.{.._4..........._d0.._..c.......y.e..ou.._.s........
 2: k..m....f_....ir........0.............y.......e..s1.......
 3: k..m.......t......_..u..0.........d.m.......ur.............
 4: ...m.r....4....r...yo..d0.....c..e...a............1...u....
 5: .....r..._...fi..._....d..._.ucc........e..ou......n...dp}
 6: ....a.....4._.i.s._..u...nt_.....e........y...........ud...
 7: k..m....._.......t...u.......ucc....m................_.....
 8: ..l...{....t..i.s..y.u...n._..........y.......e........d..
 9: ...ma......._....t..........s...e.......e.............u..}.
10: k.l.a.{i...._....t.....d..._.................r.............
11: ..l............r......_d.n..s..............o..........u.p..
12: ...m.......t.f............t.............e....r..us.........
    ....a........fir.t..o..d....s.cc....m.yb..y.........g..d...
13: .alma.....4...i........d.n........._..........._...n.......
14: ...............rs.........._....e.........yo........g..d..
15: k................._..._d....s..........b......e_..1.g_....
16: ...m...i...._..rs....._.......c..ed.may..........s1...u....
17: .a..............s......d..t.....eed.......yo....u.....u.p.
18: .............f...._..u...n......e.._.......o........._.....
19: ...m.r{............y.....n._s..........b..y.u.._..1.....p..
20: ..l.a.........i...........t.s..........b....u..........dp.
21: .a.....i......i......u....t....c.e...........r......g..dp..
22: ..l.a...._4.........o..d0.t..u......ma.b._.........n.....}.
        :
        :

文字が送信できているものを結合する。

kalmar{if_4t_first_you_d0nt_succeed_maybe_youre_us1ng_udp}

lleHSyniT! (forensic)

$ file proc.dmp
proc.dmp: Mini DuMP crash report, 17 streams, Thu Feb 23 07:38:10 2023, 0x469925 type

$ strings proc.dmp | grep kalmar
password:kalmar{My_F4v0r1t3_G4m3_1s_Cobalt_Strike:gL0b4l_0p3r4t0rs}
kalmar{My_F4v0r1t3_G4m3_1s_Cobalt_Strike:gL0b4l_0p3r4t0rs}

BabyOneTimePad (crypto)

サーバの処理概要は以下の通り。

・PASS_LENGTH_BYTES = 128
・password: ランダム128バイト文字列
・enc, _ = encrypt_otp(password, key = os.urandom(PASS_LENGTH_BYTES))
 ・ciphertext: passwordの16進数表記文字列とkey*2のXOR
 ・ciphertext, keyを返却
・encの16進数表記を表示
・permutation: 入力
・permutation: permutationの","区切りの数値配列
 ※permutationは0~127が1つずつ含まれる数値配列
・enc, _ = encrypt_otp(bytes([password[permutation[i]] for i in range(PASS_LENGTH_BYTES)]))
・encの16進数表記を表示
・password_guess: 入力
・password_guess: password_guessをhexデコード
・password_guessとpasswordが一致している場合、フラグを表示

passwordの16進数表記文字列の前半と後半でXOR鍵が同じで、さらに1回目と2回目のXORで鍵は同じ。2回目のpasswordの順序を入れ替えた場合の情報も使って、z3で制約条件を加え、パスワードを求める。

#!/usr/bin/env python3
import socket
import random
from string import *
from z3 import *

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

x = [BitVec('x%d' % i, 8) for i in range(256)]
k = [BitVec('k%d' % i, 8) for i in range(128)]
slv = Solver()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('3.120.132.103', 13337))

#### 1st encryption ####
for _ in range(3):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

data = recvuntil(s, b'\n').rstrip()
print(data)
enc0 = bytes.fromhex(data.split(': ')[1])

enc0_0 = enc0[:128]
enc0_1 = enc0[128:]

for i in range(128):
    cons = []
    for hd in hexdigits[:16]:
        key = ord(hd) ^ enc0_0[i]
        p = enc0_1[i] ^ key
        if chr(p) in hexdigits[:16]:
            cons.append(And(x[i] == ord(hd), x[i+128] == p, k[i] == key))
    slv.add(Or(cons))

permutation = list(range(128))
random.shuffle(permutation)
permutation2 = []
for p in permutation:
    permutation2.append(p * 2)
    permutation2.append(p * 2 + 1)
permutation = ','.join(map(str, permutation))

#### 2nd encryption ####
data = recvuntil(s, b': ')
print(data + permutation)
s.sendall(permutation.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
enc1 = bytes.fromhex(data.split(': ')[1])

enc1_0 = enc1[:128]
enc1_1 = enc1[128:]

for i in range(128):
    cons = []
    for hd in hexdigits[:16]:
        key = ord(hd) ^ enc1_0[i]
        p = enc1_1[i] ^ key
        if chr(p) in hexdigits[:16]:
            idx1 = permutation2[i]
            idx2 = permutation2[i + 128]
            cons.append(And(x[idx1] == ord(hd), x[idx2] == p, k[i] == key))

    slv.add(Or(cons))

r = slv.check()
assert r == sat
m = slv.model()

password = ''
for i in range(256):
    password += chr(m[x[i]].as_long())

data = recvuntil(s, b': ')
print(data + password)
s.sendall(password.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)

実行結果は以下の通り。

According to Wikipedia:
"In cryptography, the one-time pad (OTP) is an encryption technique that cannot be cracked, but requires the use of a single-use pre-shared key that is not smaller than the message being sent."
So have fun trying to figure out my password!
Here is my password encrypted with a one-time pad: 6394c16295487d432bf306bbeac7c2cdbb399c8ed8803423d3447ec667c8179e2515f3cf01c9a4bd5f61049ca0c0c347943afef7edbedb3ad8130228c4d30961f30ddb1075fed7a7dc227ff8cb4b475a3060c1a682af06fa8729c76a4cb78c2e0038b94bcbb4ca10018445c598a238a417d11995f7a92a1d1c1021f1aa9ea13435c0c831c3467a452ba151bab89294c8b0389bda8d8e3a77854e7d95619e199f7117a3ca50cea1e90b3000c8f19196159039fcf4bcb98b3cd514027a97d70867a454da4172f9d0f3822d28a29848420f353495f6d1f40fad8428906f44b3de77053dbd1b9bbbc211038a11c799a135a6468718c4f2a1281d1d1325a0fe9cf236
Actually, I will give you my password encrypted another time.
This time you are allowed to permute the password first
Permutation: 56,72,80,107,3,11,33,112,19,45,51,23,58,24,120,89,46,95,61,122,16,101,83,28,100,124,81,9,74,77,40,42,13,106,25,59,82,98,115,14,86,8,121,50,38,4,37,94,84,27,66,6,104,102,17,116,76,21,35,60,1,31,119,67,43,57,92,7,52,2,47,117,48,54,93,18,79,41,85,108,75,68,36,88,110,22,30,114,87,26,34,29,55,63,64,65,127,90,12,96,15,118,125,32,62,70,78,103,97,0,73,91,111,71,49,105,109,10,20,39,113,44,53,69,5,99,126,123
Here is the permuted password encrypted with another one-time pad: 3397c867c14b7e117aa55cedb5c3c0ccec6e9fdf848e6c27d74e29c7639915902940a4c901c8f6e95a625595f4c797129934fea0e9badb69dd4e022ec0d05f67a25ad54c27aa86f4897028fc9e464709626391f6d7f85af2852bc06e41b7892d566bed1dcdb59548058310979ef235aa148c1bc0a7a42f1a1a4671f7aa98f03568c3c031981d7b457ea750eee8c7c199ba3c988ddc866f75dc4f7f9366cf44902743f59b059cf1ed5e33039aa7919740956ef8a7b9badc3dd541067892870361f65d831726add0aa8c2c28adc94d430a333796f1d7f90dafd92a906a14b6de2d0436e84e9cbfc41a04d14296caa237a343d64fc6a6f52c19481227f2fe9cf461
What is my password: 2f175634bbccfa5233f7e79572b67b6857ddbcdd042157a157650efd5dac2f2d3a9edac5f64bd5716eccdb91f7ef9bda2622e99976bdb486be0d382f7abb52e3d28dc842b04b44c782ac097aa8ae1489a54a3da0de6edf4c1446ab6b8ca1ab3bd884cfda89c8762d3173790fe62c1f68736b5618586fc7543315600f6bf3a061
The flag is kalmar{why_do_default_args_work_like_that_0.0}
kalmar{why_do_default_args_work_like_that_0.0}

EasyOneTimePad (crypto)

BabyOneTimePadとは、2回の暗号化で鍵が異なる部分だけが違う。鍵の条件をはずしたスクリプトで、何回か実行したら、フラグが得られた。

#!/usr/bin/env python3
import socket
import random
from string import *
from z3 import *

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

x = [BitVec('x%d' % i, 8) for i in range(256)]
slv = Solver()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('3.120.132.103', 13338))

#### 1st encryption ####
for _ in range(3):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

data = recvuntil(s, b'\n').rstrip()
print(data)
enc0 = bytes.fromhex(data.split(': ')[1])

enc0_0 = enc0[:128]
enc0_1 = enc0[128:]

for i in range(128):
    cons = []
    for hd in hexdigits[:16]:
        key = ord(hd) ^ enc0_0[i]
        p = enc0_1[i] ^ key
        if chr(p) in hexdigits[:16]:
            cons.append(And(x[i] == ord(hd), x[i+128] == p))
    slv.add(Or(cons))

permutation = list(range(128))
random.shuffle(permutation)
permutation2 = []
for p in permutation:
    permutation2.append(p * 2)
    permutation2.append(p * 2 + 1)
permutation = ','.join(map(str, permutation))

#### 2nd encryption ####
data = recvuntil(s, b': ')
print(data + permutation)
s.sendall(permutation.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
enc1 = bytes.fromhex(data.split(': ')[1])

enc1_0 = enc1[:128]
enc1_1 = enc1[128:]

for i in range(128):
    cons = []
    for hd in hexdigits[:16]:
        key = ord(hd) ^ enc1_0[i]
        p = enc1_1[i] ^ key
        if chr(p) in hexdigits[:16]:
            idx1 = permutation2[i]
            idx2 = permutation2[i + 128]
            cons.append(And(x[idx1] == ord(hd), x[idx2] == p))

    slv.add(Or(cons))

r = slv.check()
assert r == sat
m = slv.model()

password = ''
for i in range(256):
    password += chr(m[x[i]].as_long())

data = recvuntil(s, b': ')
print(data + password)
s.sendall(password.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)

フラグが得られた時の実行結果は以下の通り。

According to Wikipedia:
"In cryptography, the one-time pad (OTP) is an encryption technique that cannot be cracked, but requires the use of a single-use pre-shared key that is not smaller than the message being sent."
So have fun trying to figure out my password!
Here is my password encrypted with a one-time pad: 35a303e22a741108d27de835d2e1bff347d4515e7ae245ea7ee6a67c6e6d320299992c575961af53a49e5207071f873dd20d908411cf68d125fbcad102e059019ec4b331a8721574443e484868413ab58bf289df329252da1c8b05b2fd0ed7c69d6027cab5682b6017d34d750e6eb0a993ac7b36eefe58d0eb5bfaba8698628736f351b42b29120ed428e53081b4b9f543df50087eeb41b47db4f02f6a3b3d0b9f9a2b5f5a3cff55a8cd03060249d738d40cc18545cb6f8a2afa978300b60f50cbcce435fe20447a466c491f6e466db38ea2d4db6597088a1ddb01eefc5cd3c19f337dcdb3382f6246871b2f5530b2aec0fd2f33e8f85d87e25af6e087ce31d7
Actually, I will give you my password encrypted another time.
This time you are allowed to permute the password first
Permutation: 15,85,25,5,92,71,52,3,44,65,106,49,11,91,16,66,110,105,75,68,23,100,57,101,35,103,43,58,61,76,55,6,21,10,126,72,94,67,64,86,111,9,104,113,96,37,74,26,24,40,84,34,127,33,19,90,109,59,20,32,97,108,38,2,53,0,117,120,115,78,83,118,18,89,69,22,7,99,81,107,12,73,79,122,56,112,77,51,13,70,63,50,60,48,80,14,42,41,30,1,116,4,17,95,87,27,45,8,62,123,98,47,31,54,125,28,93,121,114,39,29,124,82,119,36,88,46,102
Here is the permuted password encrypted with another one-time pad: 5907e002e371cf4db6d0c3f95266439375d515f4ebf5b277c1e85397ac9ef816805c07da2fa258ca3a41aa4af307f30bb1c885c3f23c39857d03eabb5ed590f1b2930aab000f0a75a44d06ee3cc02bd8d24b91214543dca246c93ce60092cfae46d9b8a4f385d1c2db08d2b8fa7ed468a0e6d56775e274c0ec5f4c92adca0cfe040fb351e42b941db9d390ae52674c9a728946a8eba5bd7591e053ccfd95fe11860d55db2efc5bca6f45a84ea555a20ce7cfd0c7fa6e3981795fedb95c8493f2e49405ac005f5f75a71d04bc6b962b8bd54cc77c44148aff10936ee70298cfaf41deeea0ff8381c08009d1edac2cd63df1bdd8667bb12197e90e1a95fc985bf0
What is my password: 76d2e9617e9304b0597d6028baa0378153c048d0965d6ad6b24972a96cebdee5f16afa77d4b340ac4b97cc833d0eeb73f19c422c47e9bf1a12a1465e1e59ec0a4f6ddd571046cad61262296fa37c7a7830d87e465ede3743d3e8c6fb9b80f33d39ae03f9ffcd276e12d34fbc2449d034dbcd2b6aec3c983fbc5420028d9cd5c1
The flag is kalmar{guess_i_should_have_read_the_whole_article}
kalmar{guess_i_should_have_read_the_whole_article}