[LINE CTF 2022] lazy_stek

Vulnerability is quite simple(nonce reuse in GCM mode) but I was struggled with TCP packet structure. I am newbie in network...

 

I confused that GCM mode is affected on Application data but it wasn't. It is related with PSK Identity field(I realized this by searching aa aa aa aa / bb bb bb bb hex data) 

 

 There are three PSK Identity field data, and IV is collide.

 

First two data are encrypted using $key0$, last one is encrypted using $key1$. It is known that single nonce collision in GCM break downs all. Moreover, $H = AES_K(0^n)$ is revealed by solving univariate equation in GF field. Since $key1 = AES_{key0}(0^n) = H$, everything is clear. I know that below code is ugly and I will refactor someday😅 

 

solver.sage

from binascii import unhexlify, hexlify
import hashlib
from Crypto.Cipher import AES

def slice_and_pad(b_str, bsize=16):
    rem = (bsize - len(b_str) % bsize) % bsize
    b_str += b"\x00" * rem
    return [bytearray(b_str[k:k+bsize]) for k in range(0, len(b_str), bsize)]


def unhex_blocks(h_str, bsize=16):
    h_str = unhexlify(h_str)
    return slice_and_pad(h_str, bsize)

def xor(a, b):
    assert(len(a) == len(b))
    return bytearray([a[i] ^^ b[i] for i in range((len(a)))])

def byte_to_bin(byte):
    b = bin(byte)[2:]
    return "0" * (8 - len(b)) + b

def block_to_bin(block):
    assert(len(block) == 16)
    b = ""
    for byte in block:
        b += byte_to_bin(byte)
    return b

def bytes_to_poly(block, a):
    f = 0
    for e, bit in enumerate(block_to_bin(block)):
        f += int(bit) * a**e
    return f

def poly_to_int(poly):
    a = 0
    for i, bit in enumerate(poly._vector_()):
        a |= int(bit) << (127 - i)
    return a

def poly_to_hex(poly):
    return (hex(poly_to_int(poly))[2:])

def GCM_poly(COEF, L_p, C_p, A_p):
    return COEF + L_p * X + sum(C_p[i] * X**(len(C_p)+1-i) for i in range(len(C_p))) + sum(A_p[i] * X**(len(C_p)+len(A_p)+1-i) for i in range(len(A_p)))

# C & AAD are hexstring(i.e. "11223344")
def forge_message(enc_J0_p, H_p, C, AAD, A_p):
    L = unhex_blocks(hex(((int)(160 << 64) | (len(C)*4)))[2:].zfill(32))[0]
    L_p = bytes_to_poly(L, a)
    C_block = unhex_blocks(C)
    C_p = [bytes_to_poly(elem, a) for elem in C_block]
    T = poly_to_hex(GCM_poly(enc_J0_p, L_p, C_p, A_p)(H_p))
    msg = AAD + C + T
    return msg

# packet 4
dat1 = '256f6e3b40c2c006f26dbe24b70c6ed6e875cec70f64aac0de67af2caaaaaaaa450abecfee723cdbe4393bbcf56add91e283615eaa6a5899906a138ce3dbe632ab778328029499c12eceefa0589945f7f3801748be3daa06ace2e682a77649da535f7235aa7ecb60bf0e3d6b7c1012e192411e29e6494c2fa05ce2c5d08d4698a05ffb5fa9ad2b2550737cea3b19ccacfdd93e7d3c3f6e641d5f8793b17261047b160c9acaf891577ef7'
C1 = unhex_blocks(dat1[64:-32])
T1 = unhex_blocks(dat1[-32:])
A1 = unhex_blocks(dat1[:64])
L1 = unhex_blocks(hex(((int)(256 << 64) | 976))[2:].zfill(32))

# packet 10
dat2 = '256f6e3b40c2c006f26dbe24b70c6ed6e875cec70f64aac0de67af2caaaaaaaa450abecfee723cdbe4393bbce26a50c35bd4b250c5395150b62c27d76e20535dea6a129d08c1c31e89475b79d36e45f7f3801748be3daa06ace2e682a77649da535f7235aa7ecb60bf0e3d6b7c1012e192411e29e6494c2fa05ce2c5d08d4698a05ffb5fa9ad2b2550737cea3b19ccacfdd93e7d3c3f6e641d5f1f668e1af6844a40e4cbdb6132cbd395'
C2 = unhex_blocks(dat2[64:-32])
T2 = unhex_blocks(dat2[-32:])
A2 = unhex_blocks(dat2[:64])
L2 = unhex_blocks(hex(((int)(256 << 64) | 976))[2:].zfill(32))

F, a = GF(2**128, name="a").objgen()
R, X = PolynomialRing(F, name="X").objgen()
    
A1_p = [bytes_to_poly(elem, a) for elem in A1]
C1_p = [bytes_to_poly(elem, a) for elem in C1]
T1_p = [bytes_to_poly(elem, a) for elem in T1]
L1_p = [bytes_to_poly(elem, a) for elem in L1]

A2_p = [bytes_to_poly(elem, a) for elem in A2]
C2_p = [bytes_to_poly(elem, a) for elem in C2]
T2_p = [bytes_to_poly(elem, a) for elem in T2]
L2_p = [bytes_to_poly(elem, a) for elem in L2]

print(L1_p)

f1 = GCM_poly(T1_p[0], L1_p[0], C1_p, A1_p)
f2 = GCM_poly(T2_p[0], L2_p[0], C2_p, A2_p)

# 1. Recover H from iv reuse
p1 = f1 + f2
# only one candidate
for root, _ in p1.roots():
    H_p = root
    if H_p == 0: continue
    H = poly_to_hex(H_p)
    print("H", H)
    break

# 2. Recover key1
key1 = hashlib.sha256(bytes.fromhex(H)).digest()

# 3. Extract keyname, aeskey
dat3 = 'ffd08593ad673b9005296a50f603af28c336d16a10aac82969a59560bbbbbbbb6fe550ba6db4b6a2af74f6f0454d82d959daa387f694685dec4c1ff7c36e40d3b9fe6e4fd41596035a594f8b599b89c47c84aa66d6d63ef3999de5041f0c3b7598b1811012399575a0c442c1c364f669ecf7fd5dfbb06bc37fd830c03e3dde20c98bc747d74d0ac196936f364c2e81338fca4bdb193d52e19f23295fc9e7546288a7464baa258fcd5542'
C = bytes.fromhex(dat3[64:-32])
T = bytes.fromhex(dat3[-32:])
AAD = bytes.fromhex(dat3[:64])
iv = bytes.fromhex(dat3[32:32+24])
chk = hashlib.sha512(key1).digest()
keyname = chk[:16]
aeskey = chk[16:32]
assert(keyname == AAD[:16])

# 4. decrypt
cipher = AES.new(aeskey, AES.MODE_GCM, iv)
cipher.update(AAD)
plain_data = cipher.decrypt_and_verify(C, T)
print(plain_data)

'CTF > Crypto' 카테고리의 다른 글

[RCTF 2022] guess  (2) 2022.12.13
[SECCON CTF 2022] janken vs kurenaif  (0) 2022.11.13
[SECCON CTF 2022] this_is_not_lsb  (0) 2022.11.13
[LINE CTF 2022] Forward-or  (0) 2022.03.27
[LINE CTF 2022] X Factor  (0) 2022.03.27
[LINE CTF 2022] ss-puzzle  (0) 2022.03.27
  Comments