niteCTF Writeup

この大会は2021/12/10 21:00(JST)~2021/12/12 21:00(JST)に開催されました。
今回もチームで参戦。結果は1370点で331チーム中72位でした。
自分で解けた問題をWriteupとして書いておきます。

baby reversing (rev)

$ strings baby_rev 
    :
b@by_R3v
enter the password to get the flag: 
congratulations! here's the flag: nite{
wrong password!
r3@l_
    :

パスワードが分断されているように見える。

$ ./baby_rev 
enter the password to get the flag: 
r3@l_b@by_R3v
congratulations! here's the flag: nite{baby_r3v_champ}
nite{baby_r3v_champ}

Qurious Case (Forensics)

Stegsolveで開き、Red plane 0を見ると、QRコードが一部欠けた状態になっている。
f:id:satou-y:20211216124408p:plain
不明な箇所は"?"にし、テキストに落とし、読み込む。

$ cat qr.txt
XXXXXXX_????_XXXX_XXXXXXX
X_____X_????X__X__X_____X
X_XXX_X_????__X___X_XXX_X
X_XXX_X_????_XX_X_X_XXX_X
X_XXX_X_????XXX_X_X_XXX_X
X_____X_????___X__X_____X
XXXXXXX_????X_X_X_XXXXXXX
????????????X_X__________
????????????X_X_XX_X_X_X_
????????????_XX_X______X_
????????????X__XXXX__X_XX
__X_XX___XXX__XX_X_X___XX
X_____XX_X_X_XXX__XX_XX__
X__XXX_XX_X_XXX_X__X_____
X___XXX_X_X____X_XX____XX
X_X__X____X_X_X___X______
X__XXXX_XX__XXXXXXXXX_XX_
________X____XX_X___XXX__
XXXXXXX_X__XX_XXX_X_X_XXX
X_____X__XXX__X_X___X____
X_XXX_X_XXXX___XXXXXX_XXX
X_XXX_X_X_X_XX___XX____XX
X_XXX_X_XX___XXX_XXX_X__X
X_____X_X___XX__XX______X
XXXXXXX_X_X_X_X__X__XX_XX
$ python sqrd.py qr.txt
nite{tH@T'$_qRazzYyYy}
nite{tH@T'$_qRazzYyYy}

Variablezz (Crypto)

各文字について、以下の式で暗号化している。

a * pow(ord(x), 3) + b * pow(ord(x), 2) + c * ord(x) + d

まずフラグの先頭4文字で方程式にしてa, b, c, dを求める。あとはブルートフォースでフラグを復号する。

#!/usr/bin/env python3
from sympy import *

with open('ciphertext.txt', 'r') as f:
    enc = eval(f.read().split(' = ')[1])

a = symbols('a')
b = symbols('b')
c = symbols('c')
d = symbols('d')

flag_head = 'nite'

eq = []
for i in range(len(flag_head)):
    x = flag_head[i]
    eq.append(Eq(a * pow(ord(x), 3) + b * pow(ord(x), 2) + c * ord(x) + d, enc[i]))
sol = solve(eq, [a, b, c, d])
a = int(sol[a])
b = int(sol[b])
c = int(sol[c])
d = int(sol[d])

flag = ''
for i in range(len(enc)):
    for code in range(32, 127):
        if a * pow(code, 3) + b * pow(code, 2) + c * code + d == enc[i]:
            flag += chr(code)
            break

print(flag)
nite{jU5t_b45Ic_MaTH}

Rabin To The Rescue (Crypto)

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

・p: 256bit素数(4で割った余りが3)
・q: pの次の素数(4で割った余りが3)
・n = p * q
・e = int(mixer(q - p)) * 2
・以下繰り返し
 ・choice: "E", "G", "X"選択
 ・"E"の場合
  ・m: 16進表記入力→10進数数値化
  ・ciphertext = pow(m, e, n)
   →16進表記で表示
 ・"G"の場合
  ・flag: フラグの数値化
  ・t: 2~10のランダム整数
  ・ciphertext = pow(flag, e, n)
   →16進表記で表示
 ・"X"の場合、終了

eは必ず2になる。p, qの条件から暗号はRabin暗号になる。以下の方針でフラグを復号する。

・"E"で2乗した値がnの数倍を超える値に抑えるよう指定し、暗号化結果との差分がnの倍数になることを利用して、nを求める。
・p, qは近い数値のため、Fermat法でnを素因数分解する。
・"G"でフラグの暗号化を取得し、Rabin暗号の復号方法で復号する。
#!/usr/bin/env python3
import socket
from Crypto.Util.number import *

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

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

def egcd(a, b):
    if a == 0:
        return b, 0, 1
    else:
        gcd, y, x = egcd(b % a, a)
        return gcd, x - (b // a) * y, y

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('rabin.challenge.cryptonite.team', 1337))

data = recvuntil(s, b'E[x]it\n').rstrip()
print(data)
data = recvuntil(s, b'\n').rstrip()
print(data)

data = recvuntil(s, b'>>')
print(data + 'E')
s.sendall(b'E\n')

data = recvuntil(s, b':\n').rstrip()
print(data)
m = 2**256 - 1
print(hex(m)[2:])
s.sendall(hex(m)[2:].encode() + b'\n')

data = recvuntil(s, b'\n').rstrip()
print(data)
data = recvuntil(s, b'\n').rstrip()
print(data)
c = int(data, 16)
n = m**2 - c

for i in range(100, 1, -1):
    if n % i == 0:
        n = n // i

p, q = fermat(n)
assert p % 4 == 3
assert q % 4 == 3

data = recvuntil(s, b'>>')
print(data + 'G')
s.sendall(b'G\n')

data = recvuntil(s, b'\n').rstrip()
print(data)
data = recvuntil(s, b'\n').rstrip()
print(data)

c = int(data, 16)

r = pow(c, (p + 1) // 4, p)
s = pow(c, (q + 1) // 4, q)

gcd, c, d = egcd(p, q)
x = (r * d * q + s * c * p) % n
y = (r * d * q - s * c * p) % n
plains = [x, n - x, y, n - y]

for plain in plains:
    flag = long_to_bytes(plain)
    if flag.startswith(b'nite{'):
        print(flag.decode())
        break

実行結果は以下の通り。

== proof-of-work: disabled ==
________________________________________________________________________________________________



-----------------------------------
-----------------------------------
-----------------------------------
               /\
            __/__\__
            \/    \/
            /\____/\
            ``\  /``
               \/
-----------------------------------
-----------------------------------
-----------------------------------



Welcome to the Encryption challenge!!!!


Menu:
=> [E]ncrypt your message
=> [G]et Flag
=> E[x]it
________________________________________________________________________________________________
>>E
Enter message in hex:
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
Your Ciphertext is:
245de4b42302114a104d2f73f16b4667120f562d1bfa6c79880c93c33dba2eefec35ecf57e80f950013a58492990a9d053f0a5f634204bacce2c35077fb64bff
>>G
Your encrypted flag is:
4d34144fc1b3d401b93cd06e4090cf583548b6f12480c79cb16c05d53805778069c14fa934bac7b109164bd97cf3826e05ab58a7cc63476c63b8bf5b08f3c5d3
nite{r3p34t3d_r461n_3ncrypt10n_l1tr4lly_k1ll5_3d6f4adc5e}
nite{r3p34t3d_r461n_3ncrypt10n_l1tr4lly_k1ll5_3d6f4adc5e}

Flip Me Over (Crypto)

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

・KEY: ランダム16バイト
・entropy: ランダム128バイトのmd5ダイジェスト
・username: 16進表記で入力
・token = generate_token(username)
 ・iv: ランダム16バイト
 ・pt: usernameのhexデコード
 ・ptに'gimmeflag'が含まれていたら終了
 ・ct: ptをパディングして、AES-CBC暗号化
 ・tag: ctをブロックごとに分け、すべてXORする。
 ・tag: さらにiv, entropyをXORする。
 ・tag + ctの16進文字列を返す。
・tokenを表示
・以下繰り返し
 ・token: 16進表記で入力
 ・tag: 16進表記で入力
 ・verify(tag, token)の結果に'gimmeflag'が含まれている場合、フラグを表示
 ・verify(tag, token)の結果に'gimmeflag'が含まれていない場合、その値を表示
 ・entropy: entropyのmd5ダイジェスト
平文1ブロック目 ^ iv --(AES-CBC暗号化)--> 暗号1ブロック目
tag = 暗号1ブロック目 ^ iv ^ entropy

適当な16バイト未満のusername('a' * 9)を指定し、次にivを変えて、'gimmeflag'が含まれるようにしたい。このとき、tagを変えることによって、ivを変える。

iv ^ entropy = tag ^ ct
new_iv = pad('a' * 9) ^ iv ^ pad('gimmeflag')
→ new_iv ^ pad('a' * 9) ^ pad('gimmeflag') ^ entropy = tag ^ ct
→ new_iv ^ entropy = (pad('a' * 9) ^ pad('gimmeflag') ^ tag) ^ ct
→ new_tag = pad('a' * 9) ^ pad('gimmeflag') ^ tag, new_ct = ct
#!/usr/bin/env python3
import socket
from Crypto.Util.Padding import pad, unpad
from Crypto.Util.strxor import strxor

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(('flipmeover.chall.cryptonite.team', 1337))

pt = b'a' * 9
username = pt.hex().encode()
data = recvuntil(s, b'hex():\n').rstrip()
print(data)
print(username)
s.sendall(username + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
tag = bytes.fromhex(data[:32])
ct = bytes.fromhex(data[32:])

new_pt = b'gimmeflag'
new_tag = strxor(strxor(pad(pt, 16), pad(new_pt, 16)), tag)
new_ct = ct

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

for _ in range(4):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

実行結果は以下の通り。

== proof-of-work: disabled ==
Hello new user
We shall allow you to generate one token:
Enter username in hex():
b'616161616161616161'
542b5c295fc6baa997cd90cac24c72f12bd4852ae0d03813acebe02d665f71df
Validate yourself :)
Enter token in hex():
2bd4852ae0d03813acebe02d665f71df
Enter tag in hex():
522350255bc1b7a991cd90cac24c72f1
Oh no u flipped me...
I am now officially flipped...
Here's ur reward...
nite{flippity_floppity_congrats_you're_a_nerd}
nite{flippity_floppity_congrats_you're_a_nerd}