This challenge was the continuation of r0system and also wasn’t involved with crypto that much.
I still don’t know if they release r1system as the “real” final stage of r0system but they had a mistake or if the mistake was actually intended:
elif option == 3:
username = bytes.fromhex(input(b"Username[HEX]: ".decode()))
if username == AliceUsername or username == AliceUsername:
print(b"You can't!")
returnr1system had a few differences from r0system, the main one was being able to send messages through the “PublicChannel”.
What I did was register as bob and ten just send a message to alice, this would show the shared AB key:
change_key = USER.ecdhs[USERNAME].exchange_key(ToPublickey)
print((b"Exchanged Key is: " + change_key.hex().encode() ) .decode())This was still executed:
def Alice_transfer_flag_to_Bob(AliceUsername,BobUsername):
global PublicChannels
PublicChannels += transfer_A2B(USER,AliceUsername,BobUsername,b" Halo bob, I will give your my flag after we exchange keys.")
PublicChannels += transfer_A2B(USER,BobUsername,AliceUsername,b" OK, I'm ready.")
PublicChannels += transfer_A2B(USER,AliceUsername,BobUsername,b" My Pubclic key is: " + USER.getsb_public_key(AliceUsername).hex().encode())
PublicChannels += transfer_A2B(USER,BobUsername,AliceUsername,b" My Pubclic key is: " + USER.getsb_public_key(BobUsername).hex().encode())
PublicChannels += transfer_A2B(USER,AliceUsername,BobUsername,b" Now its my encrypted flag:")
PublicChannels += transfer_A2B(USER,AliceUsername,BobUsername, FLAG1 ,enc=True)
PublicChannels += transfer_A2B(USER,BobUsername,AliceUsername,b" Wow! I know your flag now! ")Then with the AB key you just decrypt the AES encrypted flag.
Here is the final exploit:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This exploit template was generated via:
# $ pwn template server.py --host localhost --port 1337
from pwn import *
from Crypto.Cipher import AES
from hashlib import md5
# Set up pwntools for the correct architecture
context.update(arch='i386')
exe = 'server.py'
# Many built-in settings can be controlled on the command-line and show up
# in "args". For example, to dump all data sent/received, and disable ASLR
# for all created processes...
# ./exploit.py DEBUG NOASLR
# ./exploit.py GDB HOST=example.com PORT=4141 EXE=/tmp/executable
host = args.HOST or 'ctf2024-entry.r3kapig.com'
port = int(args.PORT or 30718)
def start_local(argv=[], *a, **kw):
'''Execute the target binary locally'''
if args.GDB:
return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
else:
return process(['python3'] + [exe] + argv, *a, **kw)
def start_remote(argv=[], *a, **kw):
'''Connect to the process on the remote host'''
io = connect(host, port)
if args.GDB:
gdb.attach(io, gdbscript=gdbscript)
return io
def start(argv=[], *a, **kw):
'''Start the exploit against the target.'''
if args.LOCAL:
return start_local(argv, *a, **kw)
else:
return start_remote(argv, *a, **kw)
# Specify your GDB script here for debugging
# GDB will be launched if the exploit is run via e.g.
# ./exploit.py GDB
gdbscript = '''
continue
'''.format(**locals())
#===========================================================
# EXPLOIT GOES HERE
#===========================================================
ALICE=b'416c6963654973536f6d65426f6479'
BOB =b'426f6243616e4265416e79426f6479'
BOB_PUBK=''
BOB_PRVK=''
ALICE_PUBK=''
ALICE_PRVK=''
ENC_FLAG=''
def register(username, password):
io.sendlineafter(b'Now input your option:', b'3')
io.sendlineafter(b'Username[HEX]:', username)
io.sendlineafter(b'Password[HEX]:', password)
log.info(f"Registered {username} with password: {password}")
def login(username, password):
io.sendlineafter(b'Now input your option:', b'1')
io.sendlineafter(b'Username[HEX]:', username)
io.sendlineafter(b'Password[HEX]:', password)
log.info(f"Logged in as {username}")
def resetpassword(username, newpassword):
io.sendlineafter(b'do you need any services?', b'1')
io.sendlineafter(b'Password[HEX]:', newpassword)
log.info(f"Reseted password of {username} to {newpassword}")
def getkeys():
io.sendlineafter(b'do you need any services?', b'4')
io.recvuntil(b'Your private key is:')
priv = io.recvline().strip().decode()
priv = b2i(bytes.fromhex(priv))
io.recvuntil(b'Your public key is:')
pub = io.recvline().strip().decode()
pub = b2p(bytes.fromhex(pub))
return [pub, priv]
def exchange_keys(dest):
io.sendlineafter(b'do you need any services?', b'2')
io.sendlineafter(b'ToUsername[HEX]:', dest)
io.recvuntil(b'Exchanged Key is: ')
key = io.recvline().strip().decode()
key = bytes.fromhex(key)
return key
def get_enc_flag():
io.sendlineafter(b'do you need any services?', b'3')
io.recvuntil(b'Now its my encrypted flag:\n')
io.recvuntil(b'[AliceIsSomeBody] to [BobCanBeAnyBody]: ')
enc_flag = io.recvline().strip().decode()
return enc_flag
def logout():
io.sendlineafter(b'do you need any services?', b'5')
log.info(f"Logged out")
# Key is derived from A + B key exchange
def enc(msg,key):
aes = AES.new(key,AES.MODE_ECB)
return aes.encrypt(pad(msg))
class Curve:
def __init__(self):
# Nist p-256
self.p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff
self.a = 0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc
self.b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b
self.G = (0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296,
0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5)
self.n = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551
def add(self,P, Q):
if (P == (0, 0)):
return Q
elif (Q == (0, 0)):
return P
else:
x1, y1 = P
x2, y2 = Q
if ((x1 == x2) & (y1 == -y2)):
return ((0, 0))
else:
if (P != Q):
l = (y2 - y1) * pow(x2 - x1, -1, self.p)
else:
l = (3 * (x1**2) + self.a) * pow(2 * y1, -1, self.p)
x3 = ((l**2) - x1 - x2) % self.p
y3 = (l * (x1 - x3) - y1) % self.p
return x3, y3
def mul(self, n , P):
Q = P
R = (0, 0)
while (n > 0):
if (n % 2 == 1):
R = self.add(R, Q)
Q = self.add(Q, Q)
n = n // 2
return R
def i2b(i,l):
return int.to_bytes(i,length=l,byteorder='big')
def b2i(b):
return int.from_bytes(b,byteorder='big')
def p2b(P):
return i2b(P[0],32) + i2b(P[1],32)
def b2p(m):
return (b2i(m[:32]),b2i(m[32:]))
MOD = 0x10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000283
SEED = b2i(os.urandom(128))
class RandomNG:
def __init__(self, mod, seed):
self.coeffs = [randint(1,mod) for _ in range(8)]
self.mod = mod
self.state = seed
def next(self):
old_state = int(self.state)
self.state = sum(coeff * self.state**i for i,coeff in enumerate(self.coeffs)) % self.mod
return old_state
class ECDH:
def __init__(self, pub, priv):
self.curve = Curve()
self.private_key = priv
self.public_key = pub
def exchange_key(self,others_publickey):
return md5(str(self.curve.mul(self.private_key,others_publickey)).encode()).digest()
io = start()
register(BOB, b'16')
login(BOB, b'16')
ab = exchange_keys(ALICE)
enc_flag = get_enc_flag()
aes = AES.new(ab,AES.MODE_ECB)
flag = aes.decrypt(bytes.fromhex(enc_flag)).decode(errors='ignore').strip()
log.success(f"Flag found: {flag}")