# 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.

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
```

> 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))
```

> 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)
```

> NCW23{Double_RSA_Very_Different_Concept}