# [EN] Lazy Nonce ###### tags: `Writeup` `Crypto` `English` > [name=Curious] ## Train Of Thought & Solution The operation of GCM can be referenced as follows: ![](https://hackmd.io/_uploads/BJA1rXnPn.png) > SCIST 2nd Crypto's presentation ![](https://hackmd.io/_uploads/r1Xxr7hPh.png) > SCIST 2nd Crypto's presentation Alternatively, you can directly refer to the code in `pycryptodome`. This challenge is a Nonce Reuse Attack in GCM. The difficulty lies in the implementation. Here, I will provide my implementation and Solve Script for reference. You can also visit [Github](https://github.com/Curious-Lucifer/CTFLib/blob/master/Crypto/Block_Cipher.py#L40) to see it directly. ```python= class GCM_Forbidden_Attack: """ - method - `append` : add new `AAD`, `cipher`, `auth_tag` to the instance (their nonce and key need to be the same) - `calc_H` : calc the posible `H` for the data in the instance - `calc_EJ0` : use `H` to calc the corresponding `EJ0` - class method - `calc_auth_tag` : use the `H`(represent the same key), `EJ0`(respresent the same nonce) to calc `auth_tag` for new `AAD` and `cipher` """ x = var('x') K = GF(2 ** 128, name='a', modulus=x ** 128 + x ** 7 + x ** 2 + x + 1, names=('a',)) P = PolynomialRing(K, names=('x',)) a = K._first_ngens(1)[0] x = P._first_ngens(1)[0] def __init__(self, AAD_list: list[bytes]=[], cipher_list: list[bytes]=[], auth_tag_list: list[bytes]=[]): """ - input - `AAD_list (list[bytes], [AAD1, AAD2, ...])` - `cipher_list (list[bytes], [cipher1, cipher2, ...])` - `auth_tag_list (list[bytes], [auth_tag1, auth_tag2, ...])` """ self.AAD_list = AAD_list self.cipher_list = cipher_list self.auth_tag_list = auth_tag_list def append(self, AAD: bytes, cipher: bytes, auth_tag: bytes): """ - input : `AAD (bytes)`, `cipher (bytes)`, `auth_tag (bytes)` """ self.AAD_list.append(AAD) self.cipher_list.append(cipher) self.auth_tag_list.append(auth_tag) @staticmethod def bytes2polynomial(bytes_string: bytes): """ - input : `bytes_string (bytes, 16 bytes)` - output : `polynomial` , polynomial of `a` that in `GF(2 ** 128, name='a', modulos=y ** 128 + y ** 7 + y ** 2 + y + 1, names=('a',))` """ assert len(bytes_string) == 16 bin_bytes_string = bin(bytes_to_long(bytes_string))[2:].rjust(128, '0') return sum(int(bit) * GCM_Forbidden_Attack.a ** i for i, bit in enumerate(bin_bytes_string)) @staticmethod def polynomial2bytes(polynomial): """ - input : `polynomial` , polynomial of `a` that in `GF(2 ** 128, name='a', modulos=y ** 128 + y ** 7 + y ** 2 + y + 1, names=('a',))` - output : `bytes_string (bytes, 16 bytes)` """ term_list = str(polynomial).split(' + ') bin_list = ['0'] * 128 if '1' in term_list: bin_list[0] = '1' term_list.pop(-1) if 'a' in term_list: bin_list[1] = '1' term_list.pop(-1) for term in term_list: bin_list[int(term.split('^')[1])] = '1' return long_to_bytes(int(''.join(bin_list), base=2), 16) @staticmethod def merge_AAD_cipher(AAD: bytes, cipher: bytes): ''' - input : `AAD (bytes)`, `cipher (bytes)` - output : `payload (bytes)` ''' payload = AAD if (len(payload) % 16) != 0: payload += b'\x00' * (16 - len(payload) % 16) payload += cipher if (len(payload) % 16) != 0: payload += b'\x00' * (16 - len(payload) % 16) return payload + long_to_bytes(len(AAD) * 8, 8) + long_to_bytes(len(cipher) * 8, 8) def calc_H(self): """ - input : `None` - output : `H_list (list[bytes])` , [H1, H2, ...] and one of it is the real H """ for i in range(len(self.cipher_list) - 1): payload1 = GCM_Forbidden_Attack.merge_AAD_cipher(self.AAD_list[i], self.cipher_list[i]) payload2 = GCM_Forbidden_Attack.merge_AAD_cipher(self.AAD_list[i + 1], self.cipher_list[i + 1]) poly1 = sum(GCM_Forbidden_Attack.bytes2polynomial(payload1[j * 16: (j + 1) * 16]) * (GCM_Forbidden_Attack.x ** (len(payload1) // 16 - j)) for j in range(len(payload1) // 16)) poly2 = sum(GCM_Forbidden_Attack.bytes2polynomial(payload2[j * 16: (j + 1) * 16]) * (GCM_Forbidden_Attack.x ** (len(payload2) // 16 - j)) for j in range(len(payload2) // 16)) f = poly1 - poly2 + GCM_Forbidden_Attack.bytes2polynomial(self.auth_tag_list[i + 1]) - GCM_Forbidden_Attack.bytes2polynomial(self.auth_tag_list[i]) root_list = [GCM_Forbidden_Attack.polynomial2bytes(root) for root, _ in f.roots()] if i == 0: H_list = set(root_list) else: H_list &= set(root_list) return list(H_list) def calc_EJ0(self, H: bytes): """ - input : `H (bytes)` - output : `EJ0 (bytes)` """ payload = GCM_Forbidden_Attack.merge_AAD_cipher(self.AAD_list[0], self.cipher_list[0]) H = GCM_Forbidden_Attack.bytes2polynomial(H) EJ0 = GCM_Forbidden_Attack.bytes2polynomial(self.auth_tag_list[0]) - sum(GCM_Forbidden_Attack.bytes2polynomial(payload[j * 16: (j + 1) * 16]) * (H ** (len(payload) // 16 - j)) for j in range(len(payload) // 16)) return GCM_Forbidden_Attack.polynomial2bytes(EJ0) @classmethod def calc_auth_tag(cls, H: bytes, EJ0: bytes, AAD: bytes, cipher: bytes): """ - input : `H (bytes)`, `EJ0 (bytes)`, `AAD (bytes)`, `cipher (bytes)` - output : `auth_tag (bytes)` , (AAD & cipher)'s auth tag """ payload = cls.merge_AAD_cipher(AAD, cipher) H = cls.bytes2polynomial(H) auth_tag_poly = sum(cls.bytes2polynomial(payload[j * 16: (j + 1) * 16]) * (H ** (len(payload) // 16 - j)) for j in range(len(payload) // 16)) auth_tag_poly += cls.bytes2polynomial(EJ0) return cls.polynomial2bytes(auth_tag_poly) ``` Solve Script : ```python= from CTFLib.Crypto.Block_Cipher import GCM_Forbidden_Attack from pwn import * r = remote('lotuxctf.com', 30002) enc = bytes.fromhex(r.recvline().strip().split(b': ')[1].decode()) def get_data(): r.sendlineafter(b': ', b'encrypt') AAD = bytes.fromhex(r.recvline().strip().split(b': ')[1].decode()) cipher = bytes.fromhex(r.recvline().strip().split(b': ')[1].decode()) auth_tag = bytes.fromhex(r.recvline().strip().split(b': ')[1].decode()) return AAD, cipher, auth_tag attack_obj = GCM_Forbidden_Attack() for i in range(2): attack_obj.append(*get_data()) H_list = attack_obj.calc_H() while len(H_list) > 1: attack_obj.append(*get_data()) H_list = attack_obj.calc_H() H = H_list[0] EJ0 = attack_obj.calc_EJ0(H) new_auth = attack_obj.calc_auth_tag(H, EJ0, b'authenticate message for flag decryption', enc) r.sendlineafter(b': ', b'decrypt') r.sendlineafter(b': ', enc.hex().encode()) r.sendlineafter(b': ', new_auth.hex().encode()) r.interactive() ``` {%hackmd M1bgOPoiQbmM0JRHWaYA1g %}