# Cyber Apocalypse 2023 # Ancient encoding ```python from Crypto.Util.number import bytes_to_long, long_to_bytes from base64 import b64encode, b64decode FLAG = b"HTB{??????????}" def encode(message): return hex(bytes_to_long(b64encode(message))) def main(): encoded_flag = encode(FLAG) with open("output.txt", "w") as f: f.write(encoded_flag) if __name__ == "__main__": main() ``` Ciphertext: ```python 0x53465243657a467558336b7764584a66616a4231636d347a655639354d48566664326b786246397a5a544e66644767784e56396c626d4d775a4446755a334e665a58597a636e6c33614756794d33303d ``` Cơ chế mã hóa sẽ như sau: flag → base64 → long → hex Vậy ta chỉ cần giải mã ngược lại là xong. ```python print(b64decode(long_to_bytes(int(0x53465243657a467558336b7764584a66616a4231636d347a655639354d48566664326b786246397a5a544e66644767784e56396c626d4d775a4446755a334e665a58597a636e6c33614756794d33303d)).decode()) ) ``` Flag: HTB{1n_y0ur_j0urn3y_y0u_wi1l_se3_th15_enc0d1ngs_ev3rywher3} # Small steps ``` from Crypto.Util.number import getPrime, bytes_to_long FLAG = b"HTB{???????????????}" assert len(FLAG) == 20 class RSA: def __init__(self): self.q = getPrime(256) self.p = getPrime(256) self.n = self.q * self.p self.e = 3 def encrypt(self, plaintext): plaintext = bytes_to_long(plaintext) return pow(plaintext, self.e, self.n) def menu(): print('[E]ncrypt the flag.') print('[A]bort training.\n') return input('> ').upper()[0] def main(): print('This is the second level of training.\n') while True: rsa = RSA() choice = menu() if choice == 'E': encrypted_flag = rsa.encrypt(FLAG) print(f'\nThe public key is:\n\nN: {rsa.n}\ne: {rsa.e}\n') print(f'The encrypted flag is: {encrypted_flag}\n') elif choice == 'A': print('\nGoodbye\n') exit(-1) else: print('\nInvalid choice!\n') exit(-1) if __name__ == '__main__': main() ``` Challenge này sử dụng RSA làm cơ chế mã hóa. Nhưng với e = 3 và p và q là 2 số nguyên tố 256 bit → N rất lớn. Vậy nên phép mod sẽ không có tác dụng, ta chỉ cần căn mũ e của ciphertext sẽ thu được flag. ```python import gmpy2 from Crypto.Util.number import long_to_bytes N = 7695354928017344933404842600854399632783027664582903001401962817282024975704187988117581437997164424396254352110461605574379481601141346225785175679776343 e = 3 flag = 70407336670535933819674104208890254240063781538460394662998902860952366439176467447947737680952277637330523818962104685553250402512989897886053 print(long_to_bytes(gmpy2.iroot(flag, e)[0])) #HTB{5ma1l_E-xp0n3nt} ``` Flag: HTB{5ma1l_E-xp0n3nt} # Perfect synchroniztion ```python from os import urandom from Crypto.Cipher import AES from string import ascii_uppercase, digits #from secret import MESSAGE #assert all([x.isupper() or x in '{_} ' for x in MESSAGE]) class Cipher: def __init__(self): self.salt = urandom(15) key = urandom(16) self.cipher = AES.new(key, AES.MODE_ECB) def encrypt(self, message): return [self.cipher.encrypt(c.encode() + self.salt) for c in message] def main(): cipher = Cipher() encrypted = cipher.encrypt((ascii_uppercase+digits+'{_} ')) # encrypted = "\n".join([c.hex() for c in encrypted]) print(((encrypted))) # with open("output.txt", 'w+') as f: # f.write(encrypted) if __name__ == "__main__": main() ``` Kèm theo source code ở trên là 1 file ciphertext với rất nhiều đoạn hex với 32 kí tự. ```python dfc8a2232dc2487a5455bda9fa2d45a1 305d4649e3cb097fb094f8f45abbf0dc c87a7eb9283e59571ad0cb0c89a74379 ... ``` Phân tích sorce code ta hiểu được, Flag sẽ được mã hóa từng bytes và thêm vào 15 bytes bằng AES cipher nhưng 15bytes này sẽ không thay đổi trong suốt quá trình giải mã. Vậy, mặc dù dùng AES để mã hóa nhưng bản chất chỉ là 1 biến thể của subcipher và chúng ta có thể dùng tool phân tích kí tự để giải. Như vậy, mỗi đoạn trong ciphertext là một kí tự và ta sẽ tạo ta 1 alphabet sau đó gán từng kí tự cho 1 đoạn trong ciphertext. Sau đó, ta sẽ đoán 3 kí tự có thể xuất hiện ít nhất trong plaintetx và gán trực tiếp cho các đoạn tương ứng trong ciphertext. ```python fbe86a428051747607a35b44b1a3e9e9 -> { a94f49727cf771a85831bd03af1caaf5 -> _ c53ba24fbbe9e3dbdd6062b3aab7ed1a -> } ``` Sau khi, làm xong tất cả các bước ở trên ta sẽ có 1 đoạn subcipher như sau: ```python CFTJFSIVNESEDVWKWNKWNBEWFPN3SNXLFN EIXNXLEXNKSNESVN2KHFSNWXCFXILN3 NGCKXXFSNDES2JE2FNIFCXEKSNDFXXFCWNESPNI31BKSEXK3SWN3 NDFXXFCWN3IIJCNGKXLNHECVKS2N CFTJFSIKFWN13CF3HFCNXLFCFNKWNENILECEIXFCKWXKINPKWXCKBJXK3SN3 NDFXXFCWNXLEXNKWNC3J2LDVNXLFNWE1FN 3CNED13WXNEDDNWE1YDFWN3 NXLEXNDES2JE2FNKSNICVYXESEDVWKWN CFTJFSIVNESEDVWKWNEDW3NMS3GSNEWNI3JSXKS2NDFXXFCWNKWNXLFNWXJPVN3 NXLFN CFTJFSIVN3 NDFXXFCWN3CN2C3JYWN3 NDFXXFCWNKSNENIKYLFCXFOXNXLFN1FXL3PNKWNJWFPNEWNESNEKPNX3NBCFEMKS2NIDEWWKIEDNIKYLFCWN CFTJFSIVNESEDVWKWNCFTJKCFWN3SDVNENBEWKINJSPFCWXESPKS2N3 NXLFNWXEXKWXKIWN3 NXLFNYDEKSXFOXNDES2JE2FNESPNW31FNYC3BDF1NW3DHKS2NWMKDDWNESPNK NYFC 3C1FPNBVNLESPNX3DFCESIFN 3CNFOXFSWKHFNDFXXFCNB33MMFFYKS2NPJCKS2NG3CDPNGECNKKNB3XLNXLFNBCKXKWLNESPNXLFNE1FCKIESWNCFICJKXFPNI3PFBCFEMFCWNBVNYDEIKS2NIC3WWG3CPNYJZZDFWNKSN1ER3CNSFGWYEYFCWNESPNCJSSKS2NI3SXFWXWN 3CNGL3NI3JDPNW3DHFNXLF1NXLFN EWXFWXNWFHFCEDN3 NXLFNIKYLFCWNJWFPNBVNXLFNEOKWNY3GFCWNGFCFNBCFEMEBDFNJWKS2N CFTJFSIVNESEDVWKWN 3CNFOE1YDFNW31FN3 NXLFNI3SWJDECNIKYLFCWNJWFPNBVNXLFNREYESFWFN1FILESKIEDN1FXL3PWN3 NDFXXFCNI3JSXKS2NESPNWXEXKWXKIEDNESEDVWKWN2FSFCEDDVNLXB{E_WK1YDF_WJBWXKXJXK3S_KW_GFEM}NIECPNXVYFN1EILKSFCVNGFCFN KCWXNJWFPNKSNG3CDPNGECNKKNY3WWKBDVNBVNXLFNJWNEC1VWNWKWNX3PEVNXLFNLECPNG3CMN3 NDFXXFCNI3JSXKS2NESPNESEDVWKWNLEWNBFFSNCFYDEIFPNBVNI31YJXFCNW3 XGECFNGLKILNIESNIECCVN3JXNWJILNESEDVWKWNKSNWFI3SPWNGKXLN13PFCSNI31YJXKS2NY3GFCNIDEWWKIEDNIKYLFCWNECFNJSDKMFDVNX3NYC3HKPFNESVNCFEDNYC3XFIXK3SN 3CNI3S KPFSXKEDNPEXENYJZZDFNYJZZDFNYJZZDFAAA ``` và ta đã có format flag trong đó, chỉ là các byte đã bị thay đổi. ```python LXB{E_WK1YDF_WJBWXKXJXK3S_KW_GFEM} ``` Cuối cùng ta cho vào quip quip để giải → get flag. Flag: HTB{A_Simple_Substitution_Is_Weak} # Multipage recycling ```python from Crypto.Cipher import AES from Crypto.Util.Padding import pad import random, os FLAG = b'HTB{??????????????????????}' class CAES: def __init__(self): self.key = os.urandom(16) self.cipher = AES.new(self.key, AES.MODE_ECB) def blockify(self, message, size): return [message[i:i + size] for i in range(0, len(message), size)] def xor(self, a, b): return b''.join([bytes([_a ^ _b]) for _a, _b in zip(a, b)]) def encrypt(self, message): iv = os.urandom(16) ciphertext = b'' plaintext = iv blocks = self.blockify(message, 16) for block in blocks: ct = self.cipher.encrypt(plaintext) encrypted_block = self.xor(block, ct) ciphertext += encrypted_block plaintext = encrypted_block return ciphertext def leak(self, blocks): r = random.randint(0, len(blocks) - 2) leak = [self.cipher.encrypt(blocks[i]).hex() for i in [r, r + 1]] return r, leak def main(): aes = CAES() message = pad(FLAG * 4, 16) ciphertext = aes.encrypt(message) ciphertext_blocks = aes.blockify(ciphertext, 16) r, leak = aes.leak(ciphertext_blocks) with open('output.txt', 'w') as f: f.write(f'ct = {ciphertext.hex()}\nr = {r}\nphrases = {leak}\n') if __name__ == "__main__": main() ''' ct = bc9bc77a809b7f618522d36ef7765e1cad359eef39f0eaa5dc5d85f3ab249e788c9bc36e11d72eee281d1a645027bd96a363c0e24efc6b5caa552b2df4979a5ad41e405576d415a5272ba730e27c593eb2c725031a52b7aa92df4c4e26f116c631630b5d23f11775804a688e5e4d5624 r = 3 phrases = ['8b6973611d8b62941043f85cd1483244', 'cf8f71416111f1e8cdee791151c222ad'] ''' ``` Theo như source code ở trên thì mã hóa AES sẽ chia `pad(FLAG * 4, 16)` thành từng block, mỗi `block[i]` 16 bytes sẽ được mã hóa thành `enc(block[i])` theo công thức như sau: ```python enc(block[1]) = AES_ENC(iv) ^ block[1] enc(block[i]) = AES_ENC(enc(block[i-1])) ^ block[i] => block[i] = enc(block[i]) ^ AES_ENC(enc(block[i-1])) ``` Điều đáng chú ý trong source code là: ```python def leak(self, blocks): r = random.randint(0, len(blocks) - 2) leak = [self.cipher.encrypt(blocks[i]).hex() for i in [r, r + 1]] return r, leak ``` Vậy ta sẽ leak được `AES_ENC(enc(block[r]))` và `AES_ENC(enc(block[r+1]))` ```python ct = bc9bc77a809b7f618522d36ef7765e1cad359eef39f0eaa5dc5d85f3ab249e788c9bc36e11d72eee281d1a645027bd96a363c0e24efc6b5caa552b2df4979a5ad41e405576d415a5272ba730e27c593eb2c725031a52b7aa92df4c4e26f116c631630b5d23f11775804a688e5e4d5624 r = 3 phrases = ['8b6973611d8b62941043f85cd1483244', 'cf8f71416111f1e8cdee791151c222ad'] ``` Vậy ta se có `AES_ENC(enc(block[3]))` và `AES_ENC(enc(block[4]))` Như vậy ta có thể tìm được block 4 và 5 bằng công thức đã phân tích ở trên: `block[i] = enc(block[i]) ^ AES_ENC(enc(block[i-1]))` ```python import random from pwn import * ct = "bc9bc77a809b7f618522d36ef7765e1cad359eef39f0eaa5dc5d85f3ab249e788c9bc36e11d72eee281d1a645027bd96a363c0e24efc6b5caa552b2df4979a5ad41e405576d415a5272ba730e27c593eb2c725031a52b7aa92df4c4e26f116c631630b5d23f11775804a688e5e4d5624" r = 3 phrases = ['8b6973611d8b62941043f85cd1483244', 'cf8f71416111f1e8cdee791151c222ad'] #[block[3], block[4]] def blockify(message, size): return [message[i:i + size] for i in range(0, len(message), size)] enc_blocks = blockify(ct, 32) print(xor(bytes.fromhex(phrases[0]),bytes.fromhex(enc_blocks[4]))) print(xor(bytes.fromhex(phrases[1]),bytes.fromhex(enc_blocks[5]))) #HTB{CFB_15_w34k_w17h_l34kz} ``` Ta sẽ thu được: ```python b'_w34k_w17h_l34kz' b'}HTB{CFB_15_w34k' ``` và ghép lại ta sẽ có Flag: HTB{CFB_15_w34k_w17h_l34kz} # Inside The Matrix ```python from sage.all_cmdline import * # from utils import ascii_print import os FLAG = b"HTB{????????????????????}" assert len(FLAG) == 25 class Book: def __init__(self): self.size = 5 self.prime = None def parse(self, pt: bytes): pt = [b for b in pt] return matrix(GF(self.prime), self.size, self.size, pt) def generate(self): key = os.urandom(self.size**2) return self.parse(key) def rotate(self): self.prime = random_prime(2**6, False, 2**4) def encrypt(self, message: bytes): self.rotate() key = self.generate() message = self.parse(message) ciphertext = message * key return ciphertext, key def menu(): print("Options:\n") print("[L]ook at page") print("[T]urn page") print("[C]heat\n") option = input("> ") return option def main(): book = Book() ciphertext, key = book.encrypt(FLAG) page_number = 1 while True: option = menu() if option == "L": # ascii_print(ciphertext, key, page_number) print(ciphertext, key, page_number) elif option == "T": ciphertext, key = book.encrypt(FLAG) page_number += 2 print() elif option == "C": print(f"\n{list(ciphertext)}\n{list(key)}\n") else: print("\nInvalid option!\n") if __name__ == "__main__": try: main() except Exception as e: print(f"An error occurred: {e}") ``` Cơ chế mã hóa của challenge này là 25 bytes của flag sẽ được săp xếp thành một ma trận 5x5 trong một trường hữu hạn GF(prime) với prime là một số nguyên tố ngẫu nhiên trong khoảng 2^4 đến 2^6. Một cơ chế sinh khóa sẽ tạo ra 25 bytes ngẫu nhiên và sắp xếp thành ma trận như đã làm với flag. Sau cùng ta sẽ có: ```python ciphertext = Matrix(GF(prime), flag) * Matrix(GF(prime), key) ``` Với `flag` và `key` là ma trận 5x5 thì tất nhiên `ciphertext` cũng là 1 ma trận 5x5: Vậy ta có thể tìm flag theo phép nhân ma trận nghịch đảo : ```python Matrix(GF(prime), flag) = ciphertext * Matrix(GF(prime), key)^(-1) ``` Điều quan trọng trong phép tính này là ta phải tìm đúng `prime` , nhưng với giới hạn nhỏ như `2^4 → 2^6` ta có thể brute force và kiểm tra được `(bytes(H) mod prime = flag[0][0])`. Cụ thể là từ phần tử lớn nhất của key đến 2^6 Nhưng khi thu được `Matrix(GF(prime), flag)` thì các bytes trong flag đã bị thay đổi bằng phép `flag[i] mod prime` Như vậy ta sẽ thu thập 3 ciphertext khác nhau với 3 prime khác nhau để giải định lý thằng dư trung hoa. ```python flag[i] mod Prime1 = Flag1[i] flag[i] mod Prime2 = Flag2[i] flag[i] mod Prime3 = Flag3[i] ``` Sau khi giải xong 25 bytes của flag ta sắp xếp lại thì sẽ thu được flag gốc. ```python from sage.all import * from Crypto.Util.number import * from sympy.ntheory.modular import crt FLAG = b"HTB{????????????????????}" def parse(prime, pt: bytes): pt = [b for b in pt] return matrix(GF(prime), 5, 5, pt) C1 = [(10, 34, 32, 0, 12), (0, 26, 33, 9, 33), (7, 0, 6, 2, 25), (8, 14, 0, 1, 5), (21, 35, 35, 15, 9)] #ciphertext K1 = [(20, 14, 8, 31, 30), (19, 20, 14, 28, 17), (23, 0, 16, 2, 29), (30, 35, 12, 16, 26), (19, 1, 36, 8, 10)] #key C2 = [(22, 16, 22, 14, 16), (0, 20, 14, 4, 15), (15, 20, 5, 20, 11), (20, 20, 7, 5, 7), (18, 1, 22, 4, 2)] K2 = [(4, 20, 0, 11, 16), (19, 12, 17, 21, 16), (20, 17, 11, 12, 0), (14, 15, 1, 10, 7), (4, 9, 10, 8, 14)] C3 = [(11, 16, 1, 6, 21), (4, 3, 10, 24, 14), (1, 18, 10, 18, 18), (25, 10, 25, 14, 5), (9, 10, 3, 3, 2)] K3 = [(12, 0, 7, 7, 6), (7, 28, 15, 8, 19), (11, 25, 5, 3, 2), (16, 19, 15, 22, 8), (13, 11, 21, 9, 17)] C1 = Matrix(C1) C2 = Matrix(C2) C3 = Matrix(C3) FLAG1 = [] FLAG2 = [] FLAG3 = [] p = [] #FLAG*KEY = CIPHERTEXT => FLAG = CIPHERTEXT*KEY^-1 #flag mod p = CIPHERTEXT * KEY^-1 #H = 72, T = 84, B = 66, for i in range(max(max(K1))+1, 2**6): if isPrime(i): K1_GF = Matrix(GF(i), K1) if K1_GF.is_invertible(): K1_INV = K1_GF.inverse() FLAG1 = C1 * K1_INV if (72 % i) == int(FLAG1[0][0]): p.append(i) break for i in range(max(max(K2))+1, 2**6): if isPrime(i): K2_GF = Matrix(GF(i), K2) if K2_GF.is_invertible(): K2_INV = K2_GF.inverse() FLAG2 = C2 * K2_INV if (72 % i) == int(FLAG2[0][0]): p.append(i) break for i in range(max(max(K3))+1, 2**6): if isPrime(i): K3_GF = Matrix(GF(i), K3) if K3_GF.is_invertible(): K3_INV = K3_GF.inverse() FLAG3 = C3 * K3_INV if (72 % i) == int(FLAG3[0][0]): p.append(i) break FLAG1 = FLAG1.list() FLAG2 = FLAG2.list() FLAG3 = FLAG3.list() for i in range(25): v = [int(FLAG1[i]),int(FLAG2[i]), int(FLAG3[i])] print(chr(crt(p, v)[0]), end='') #HTB{l00k_@t_7h3_st4rs!!!} ``` Flag: HTB{l00k_@t_7h3_st4rs!!!}