# National Cyber Week 2k23 CTF Writeup [QUALS] ## Crypto ### Simple ``` python from Crypto.Cipher import AES from Crypto.Util.Padding import * import string import random import os from Crypto.Util.number import * from secrets import FLAG, enc def generate_random_string(length): characters = string.ascii_letters + string.digits + string.punctuation return ''.join(random.choice(characters) for _ in range(length)) def encrypt(msg,key,iv): ############################################################################### # # # Haduh kena prank opo iki rek jadi ilang # # Seinget gw ini AES jg deh cm entah apa ini # # Gudlak All!! :) # # # ############################################################################### return enc.hex() key = os.urandom(16) iv1 = os.urandom(16) iv2 = os.urandom(16) plainkey = os.urandom(16) enckey = encrypt(plainkey + os.urandom(16), key, iv1) code = ("Very simple, " + generate_random_string(random.randint(50,60))).encode() cipher = AES.new(plainkey + (os.urandom(2)*8),AES.MODE_CBC, iv2) enccode = cipher.encrypt(pad(code,16)) print(f'enckey = {enckey}') print(f'enccode = {enccode}') print(f'iv2 = {iv2}') while True: print(""" ======================================== 1. Tes Enkripsi 2. Tebak kode 3. Exit ============================================""") choose = input(">> ") if choose == "1": plaintext = input("Masukan pesan: ") try: plaintext = bytes.fromhex(plaintext) ciphertext = encrypt(plaintext, key, iv1) print(f'Ciphertext = {ciphertext}') except: print("woila...") elif choose == "2": cobaan = input("Masukkan kode: ").encode() if cobaan == code: print(f'dahlah, {FLAG}') exit(1) else: print("salah :(") exit(0) elif choose == "3": print("Bye!") exit(1) else: print("woi!") exit(0) ``` Diberikan sebuah program untuk menebak kode yang dienkripsi menggunakan AES-CBC dengan kunci plainkey+$os.urandom(2)*8$. Kemudian plainkey dilakukan enkripsi dengan AES yang tidak diketahui modenya. Oleh karena itu, saya mencoba menganalisis untuk testing enkripsi dari mode AES kedua. ![bukti1](https://hackmd.io/_uploads/r1wp8N_Ep.jpg) Dari beberapa percobaan saya mendapatkan kesimpulan pada AES tersebut tidak harus 16 bytes. Selain itu ketika melakukan $xor(plaintext, ciphertext)$ untuk bytes pertama akan menghasilkan nilai yang sama untuk kunci yang sama. Dari hal tersebut dapat saya simpulkan mode yang digunakan adalah CFB-8 yang melakukan operasi per-bytes. Kemudian untuk melakukan recover plainkey tinggal menggunakan Differential Crypanalysis masing-masing output dari menu 1. Setelah didapatkan nilai plainkey dimungkinkan untuk menebak nilai dari Kode karena kunci dari AES-CBC bisa dilakukan bruteforce sebanyak $(2**16)$ yaitu nilai dari $os.urandom(2)$. ```python # solver.py from pwn import * from Crypto.Util.number import long_to_bytes import string from Crypto.Cipher import AES from Crypto.Util.Padding import * characters = (string.ascii_letters + string.digits + string.punctuation).encode() io = remote("103.145.226.209","1945") io.recvuntil(b'enckey = ') hfile = io.recvline().decode().strip() print(hfile) enckey = bytes.fromhex(hfile) with open("cred.py","w") as f: f.write(io.recvline().decode()) f.write(io.recvline().decode()) from cred import enccode, iv2 print(enccode, iv2) def sendX(data): io.recvuntil(b'>> ') io.sendline(b'1') io.recvuntil(b' pesan: ') io.sendline(data.hex().encode()) io.recvuntil(b'rtext = ') return bytes.fromhex(io.recvline().decode().strip()) ft = b'' for i in range(32): for chs in range(256): mb = ft+long_to_bytes(chs) dat = sendX(mb) # print(enckey[i]) if(dat[i]==enckey[i]): ft += long_to_bytes(chs) print(ft) break print(ft) print("Starting Brute") for i in range(256): for j in range(256): ch = long_to_bytes(i)+long_to_bytes(j) crs = ft[:16]+ch*8 cipher = AES.new(crs ,AES.MODE_CBC, iv2) ftr = cipher.decrypt(enccode) if(b'simple' in ftr): print("FOUND!!") kode = unpad(ftr,16) io.recvuntil(b">> ") io.sendline(b'2') io.recvuntil(b'kode: ') io.sendline(kode) flag = io.recvline() print(flag) break ``` ![ss](https://hackmd.io/_uploads/H1J9UVuVa.jpg) > NCW23{kenapa_bocor_lagi_yak_keynya?_yang_penting_soalnya_simple_dah} ### Wangsaf Diberikan beberapa source code yang terdiri dari client, server, dan attacker. Service yang ada merupakan hasil running dari sisi attacker. ```python # attacker.py import socket import threading import os SERVER_SOCK = "/tmp/server" ATTACKER_SOCK = "/tmp/attacker" class Attacker: def handle_client(self): try: while True: client_data = self.client_socket.recv(4096) if not client_data: print('disconnected') exit() print('client:', client_data.decode()) client_to_server = input("client (tamper): ").encode() if client_to_server == b"fw": #forward message client_to_server = client_data self.server_socket.sendall(client_to_server) self.attacker_socket.close() except: print("something's wrong") def handle_server(self): try: while True: server_data = self.server_socket.recv(4096) if not server_data: print('disconnected') exit() print('server:', server_data.decode()) server_to_client = input("server (tamper): ").encode() if server_to_client == b"fw": #forward message server_to_client = server_data self.client_socket.sendall(server_to_client) self.attacker_socket.close() except: print("something's wrong") def handle_incoming(self): server_thread = threading.Thread(target=self.handle_server) server_thread.start() client_thread = threading.Thread(target=self.handle_client) client_thread.start() def main(self): try: os.remove(ATTACKER_SOCK) except OSError: pass try: attacker_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) attacker_socket.bind(ATTACKER_SOCK) attacker_socket.listen(1) self.client_socket, client_addr = attacker_socket.accept() self.server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.server_socket.connect(SERVER_SOCK) self.handle_incoming() except: print("something's wrong") if __name__ == '__main__': attacker = Attacker() attacker.main() ``` ```python # server.py import socket, threading from Crypto.Util.number import * import base64 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.asymmetric import rsa, dh from cryptography.hazmat.primitives import serialization, hashes, padding from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.kdf.hkdf import HKDF import time import os import random from server_messages import server_messages SERVER_SOCK = "/tmp/server" class Server: def __init__(self): SERVER_HOST = '127.0.0.1' SERVER_PORT = 12345 try: os.remove(SERVER_SOCK) except OSError: pass self.server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.server_socket.bind(SERVER_SOCK) self.server_socket.listen(1) self.client_socket, self.client_addr = self.server_socket.accept() def generateKey(self): self.loadparam() self.private_key = self.parameters.generate_private_key() self.public_key = self.private_key.public_key() def deriveKey(self): self.derived_key = HKDF( algorithm=hashes.SHA256(), length=32, salt=None, info=b'handshake data', ).derive(self.shared_key) def sendparam(self): self.serialized_parameters = self.parameters.parameter_bytes(serialization.Encoding.PEM, serialization.ParameterFormat.PKCS3) self.send(b"PARM||" + self.serialized_parameters.hex().encode()) responseData = self.receive() if b"PARM_ACC" not in responseData: self.server_socket.close() self.client_socket.close() exit() def loadparam(self): responseData = self.client_socket.recv(1024) if b"PARM||" in responseData[:6]: self.parameters = serialization.load_pem_parameters(bytes.fromhex(responseData[6:].decode())) self.send(b"PARM_ACC") else: self.server_socket.close() self.client_socket.close() exit() def keyExchange(self): self.recvpubkey() self.sendpubkey() self.deriveKey() def sendpubkey(self): self.serialized_public_key = self.public_key.public_bytes(serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo) self.send(b"PUBK||" + self.serialized_public_key.hex().encode()) responseData = self.receive() if b"PUBK_ACC" not in responseData: self.server_socket.close() self.client_socket.close() exit() def recvpubkey(self): responseData = self.receive() if b"PUBK||" in responseData[:6]: self.holder_public_key = serialization.load_pem_public_key( bytes.fromhex(responseData[6:].decode()), backend=default_backend(), ) self.shared_key = self.private_key.exchange(self.holder_public_key) self.send(b"PUBK_ACC") else: self.server_socket.close() self.client_socket.close() exit() def initialization(self): self.hello() self.generateKey() self.keyExchange() def hello(self): response = self.receive() if response == b'hello!': self.send(b'hello!') return else: self.server_socket.close() self.client_socket.close() exit() def main(self): self.initialization() while True: self.recvmessage() self.sendmessage() self.server_socket.close() self.client_socket.close() exit() def encrypt(self, message): iv = os.urandom(16) cipher = Cipher(algorithms.AES(self.derived_key), modes.CBC(iv), backend=default_backend()) encryptor = cipher.encryptor() padder = padding.PKCS7(algorithms.AES.block_size).padder() padded_message = padder.update(message.encode()) + padder.finalize() ciphertext = iv + encryptor.update(padded_message) + encryptor.finalize() return base64.b64encode(ciphertext) def decrypt(self, message): message = base64.b64decode(message) iv = message[:16] message = message[16:] cipher = Cipher(algorithms.AES(self.derived_key), modes.CBC(iv), backend=default_backend()) decryptor = cipher.decryptor() plaintext = decryptor.update(message) + decryptor.finalize() unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() unpadded_message = unpadder.update(plaintext) + unpadder.finalize() return unpadded_message def send(self, message): time.sleep(1) self.client_socket.sendall(message) def sendmessage(self): message = random.choice(server_messages) data = self.encrypt(message) self.send(data) def receive(self): response = self.client_socket.recv(4096) if not response: self.server_socket.close() self.client_socket.close() exit() return response def recvmessage(self): try: response = self.receive() data = self.decrypt(response).decode() self.checkflag(data) except: self.server_socket.close() self.client_socket.close() exit() def checkflag(self, message): if message == "giv me the flag you damn donut": flag = "NCW23{REDACTED}" data = self.encrypt(flag) self.send(data) exit() if __name__ == '__main__': server = Server() server.main() ``` Antara server dan client, mereka melakukan pertukaran pesan yang dienkripsi menggunakan AES dengan dengan secret sharing key dengan Diffie Hellman. Untuk pihak yang memiliki FLAG yaitu pada server yang diharuskan client mengirimkan pesan "giv me the flag you damn donut". Pada source code attacker kita bisa melihat dan memodifikasi data yang nantinya dikirim ke server. Hal ini merupakan bentuk serangan dari Man In The Middle Attack pada skema Diffie Hellman. Oleh karena itu, langsung saja sebagai attacker kita menggantikan posisi client dan menggenerate kunci sendiri dan berinteraksi dengan server untuk mendapatkan flag. ```python # solver.py from pwn import * from Crypto.Util.number import * import base64 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.asymmetric import rsa, dh from cryptography.hazmat.primitives import serialization, hashes, padding from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.kdf.hkdf import HKDF import random io = remote("103.145.226.209","1965") # io.interactive() def recc(): io.recvuntil(b'(tamper): ') io.sendline(b"fw") return io.recvline().decode().strip() def recc2(data): io.recvuntil(b'(tamper): ') io.sendline(data) return io.recvline().decode().strip() datas = [] for i in range(4): datas.append(recc()) parms = serialization.load_pem_parameters(bytes.fromhex(datas[1][8:][6:])) private_key = parms.generate_private_key() public_key = private_key.public_key() serialized_public_key = public_key.public_bytes(serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo) sksend = b"PUBK||" + serialized_public_key.hex().encode() rt = recc2(sksend) datas.append(rt) for i in range(2): datas.append(recc()) print(datas) pubkClient = datas[-2][8:] holder_public_key = serialization.load_pem_public_key( bytes.fromhex(pubkClient[6:]), backend=default_backend(), ) shared_key = private_key.exchange(holder_public_key) print(shared_key) def deriveKey(shared_key): return HKDF( algorithm=hashes.SHA256(), length=32, salt=None, info=b'handshake data', ).derive(shared_key) def decrypt(message, derived_key): message = base64.b64decode(message) iv = message[:16] message = message[16:] cipher = Cipher(algorithms.AES(derived_key), modes.CBC(iv), backend=default_backend()) decryptor = cipher.decryptor() plaintext = decryptor.update(message) + decryptor.finalize() unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() unpadded_message = unpadder.update(plaintext) + unpadder.finalize() return unpadded_message def encrypt(message, derived_key): iv = os.urandom(16) cipher = Cipher(algorithms.AES(derived_key), modes.CBC(iv), backend=default_backend()) encryptor = cipher.encryptor() padder = padding.PKCS7(algorithms.AES.block_size).padder() padded_message = padder.update(message.encode()) + padder.finalize() ciphertext = iv + encryptor.update(padded_message) + encryptor.finalize() return base64.b64encode(ciphertext) message = "giv me the flag you damn donut" dk = deriveKey(shared_key) ciphert = encrypt(message, dk) encs = [] for i in range(1): hasil = recc() print(ciphert) rt = recc2(ciphert)[8:] print(rt) encs = [rt] for k in encs: print(decrypt(k, dk)) ``` ![ss](https://hackmd.io/_uploads/Skn7_NO4a.jpg) > NCW23{bro_pikir_dia_sesungguhnya_adalah_whitfield_diffie_dan_martin_hellman} ### Not Simple ```python from Crypto.Util.number import * from Crypto.Util.Padding import * import string import random import time from secrets import FLAG def generate_random_string(length): characters = string.ascii_letters + string.digits + string.punctuation return ''.join(random.choice(characters) for _ in range(length)) def enc1(kode): m = bytes_to_long(kode.encode()) p,q = getPrime(600),getPrime(600) n = p*q opr1 = inverse(pow(p,5),q) opr2 = (pow(p,7,n) - pow(q,65537,n)) % n c = pow((pow(opr1 * m * opr2, 65537, n) * pow(inverse(opr1,n),2,n) * pow(inverse(opr2, n),3,n)) % n,3,n) return n,65537,opr1,opr2,c def wow(): wow = [random.getrandbits(32) << (128*pow(2,i)) for i in range(0, 4)] wadidaw = 0 for i in wow: wadidaw |= i return wadidaw def enc2(opr1,opr2,e,p,q): n = p*q c = pow(bytes_to_long(long_to_bytes(opr1) + b'bebek' + long_to_bytes(opr2)) + wow(), e, n) return c,n,e kode = generate_random_string(50) n1,e1,opr1,opr2,c1 = enc1(kode) e = input("Masukkan e = ") foul = 0 while e.isdigit() == 0 or int(e) % 2 == 0 or int(e) <= 13: foul += 1 print("Masukkan lagi") e = input("Masukkan e = ") if(foul == 3): print("Males sayah..") exit(0) p,q = getPrime(1024),getPrime(1024) e = int(e) while True: print(""" 1. Liat nilai wadidaw 2. Melihat enkripsi dari kode1 dan kode2 3. Input kode1 and kode2 4. Exit """) choose = input(">> ") if choose == "1": print(f'Nilai wadidaw = {hex(wow())}') elif choose == "2": c2,n2,e2 = enc2(opr1,opr2,e,p,q) print(f'c = {c2}') print(f'n = {n2}') print(f'e = {e2}') elif choose == "3": init = time.time() cobaopr1 = input("opr1 = ") cobaopr2 = input("opr2 = ") try: if (time.time() - init > 15): print(f"waktu anda kelamaan {time.time() - init}") exit() elif int(cobaopr1) == opr1 and int(cobaopr2) == opr2: print("Nice, skarang lagi satu tebak kode berikut") print(f'n = {n1}') print(f'e = {e1}') print(f'c = {c1}') cobakode = input("Kode = ") if (time.time() - init > 15): print(f"waktu anda kelamaan {time.time() - init}") exit() elif cobakode == kode: print(f"Dahlah, gw dah capek {FLAG}") else: print("Nope") exit() else: print("Nope") exit() except: print("Nope") exit() elif choose == "4": print("bye!!") exit() else: print("We..") exit() ``` Diberikan double skema RSA yang memiliki beberapa fitur. Penjelasan Fitur: 1. Nilai e bisa diatur dengan syarat bukan genap dan lebih dari 13 2. Bisa melakukan banyak request pada menu 1 dan menu 2 3. Nilai wadidaw bisa disebut wow dibangkitkan secara random dengan module random 4. Flow mendapatkan Flag dengan menebak nilai secret code yang dienkrip dengan RSA kedua, namun sebelum itu harus menebak secret key (opr1 dan opr2) RSA pertama. 5. Secret key (opr1 dan opr2) merupakan leak information dari RSA pertama Dari beberapa fitur tersebut kita bisa menganalisis karena fitur 1 skema tersebut memiliki kerentanan Low Exponent Public Key. Kemudian, karena fitur 2 dan 3 dimungkinkan nilai wow dilakukan predict karenan menggunakan Pseudo Random Mersenne Twister Algorithm. Adanya fitur 2 dan kerentanan Low Exponent Public Key untuk merecover secret key (opr1 dan opr2) dapat menggunakan Franklin Reiter Attack. Persamaan yang digunakan: $$f1=(X+WOW1)^e-c1$$$$f2=(X+WOW2)^e-c2$$$$X=Franklin(f1,f2)$$ Dimana $X$ merupakan secret key (opr1 dan opr2). Setelah mendapatkan nilai opr1 dan opr2 kita bisa mendapatkan nilai p dan q dengan persamaan berikut: $$opr1=p^{-5}{\space}(mod{\space}q)$$$$opr2=p^7{\space}-q^e{\space}(mod{\space}n)$$$$opr1^7{\space}.{\space}opr2^5=p^{-35}{\space}.{\space}p^{35}{\space}-{\space}q^k{\space}(mod{\space}q)$$$$opr1^7{\space}.{\space}opr2^5=1{\space}(mod{\space}q)$$$$q=GCD(opr1^7{\space}.{\space}opr2^5{\space}-{\space}1,{\space}n)$$ Kemudian setelah mendapatkan nilai $q$ tentunya bisa mendapatkan Kode untuk mendapatkan flag.'><' ```python # solver.py from sage.all import PolynomialRing, Zmod, ceil, floor from pwn import * from randcrack import RandCrack from Crypto.Util.number import * import math import gmpy2 io = remote("103.145.226.209","1928") def parsInt(x): reds = [] for i in range(4): pd = 128*pow(2,i) temp = ((x>>pd)%(2**33)) reds.append(temp) return reds def shoot(): io.recvuntil(b'>> ') io.sendline(b'1') io.recvuntil(b'wadidaw = ') return int(io.recvline().decode().strip(),16) io.recvuntil(b'kan e = ') io.sendline(b'17') rc = RandCrack() for i in range(624//4): if(i%25==0): print("Train",i,"data") hasil = shoot() for j in parsInt(hasil): rc.submit(j) def wow(): wow = [rc.predict_getrandbits(32) << (128*pow(2,i)) for i in range(0, 4)] wadidaw = 0 for i in wow: wadidaw |= i return wadidaw def gcd(a, b): while b: a, b = b, a % b return a.monic() def franklinreiter(C1, C2, e, N, b, c): X = PolynomialRing(Zmod(N),names="X").gen() g1 = (X + b)**e - C1 g2 = (X + c)**e - C2 result = -gcd(g1, g2).coefficients()[0] return int(hex(int(result))[2:].replace("L",""),16) hasil = shoot() print(hex(hasil)) print(hex(wow())) dats = [] for i in range(2): io.recvuntil(b'>> ') io.sendline(b'2') c = int(io.recvline().decode().strip().split('= ')[1]) n = int(io.recvline().decode().strip().split('= ')[1]) e = int(io.recvline().decode().strip().split('= ')[1]) dats.append([n,e,c, wow()]) hasil = long_to_bytes(franklinreiter(dats[0][2], dats[1][2], 17, dats[0][0], dats[0][3], dats[1][3])) opr = hasil.split(b'bebek') opr1 = bytes_to_long(opr[0]) opr2 = bytes_to_long(opr[1]) io.recvuntil(b'>> ') io.sendline(b'3') io.recvuntil(b'opr1 = ') io.sendline(str(opr1).encode()) io.recvuntil(b'opr2 = ') io.sendline(str(opr2).encode()) io.recvuntil(b'berikut\n') n = int(io.recvline().decode().strip().split('= ')[1]) e = int(io.recvline().decode().strip().split('= ')[1]) c = int(io.recvline().decode().strip().split('= ')[1]) mayp = math.gcd(n, opr1**7*opr2**5-1) mayq = n//mayp phi = (mayp-1)*(mayq-1) d = pow(65537, -1, phi) cc = 1 d1 = pow(3, -1, phi) cc = pow(c, d1, n) r1 = inverse(pow(inverse(opr1,n),2,n), n) r2 = inverse(pow(inverse(opr2, n),3,n), n) kate = (r1*r2*cc)%n ccc = pow(kate, d, n) r1 = inverse(opr1, n) r2 = inverse(opr2, n) m = (ccc*r1*r2)%n kode = long_to_bytes(m) io.recvuntil(b'Kode = ') io.sendline(kode) flag = io.recvline() print(flag) ``` ![ss](https://hackmd.io/_uploads/SyH__VdEa.jpg) > NCW23{Double_RSA_Very_Different_Concept}