👾
Elijah's CTF Blog
  • 👋Home
  • 🇲🇾Wargames.MY CTF 2024
    • Credentials (crypto)
    • Stones (rev)
    • Rick'S Algorithm (crypto)
    • Rick'S Algorithm 2 (crypto)
    • Hohoho 3 continue (crypto)
  • 🎄Advent of CTF 2024
    • Jingle Bell ROP (pwn)
    • help (pwn)
  • Backdoor CTF 24
    • [rev] Ratatouille
  • 🇭🇰HKCERT CTF 24
    • Shellcode Runner 3 + Revenge (pwn)
    • ISH (1) (pwn)
    • Cyp.ress (rev)
    • Void (rev)
  • 🇮🇹ECSC 2024
    • ➕OffTopic (crypto)
  • 🎩Greyhats WelcomeCTF 24
    • EE2026 (misc)
  • 🚆UIUCTF 24
    • Syscalls (pwn)
    • Summarize (rev)
    • X Marked the Spot (crypto)
    • Without a Trace (crypto)
    • Determined (crypto)
    • Naptime (crypto)
    • Snore Signatures (crypto)
  • 🪼Jelly CTF 24
    • Cherry (crypto)
    • the_brewing_secrets (crypto)
  • 👨‍🦯vsCTF 24
    • Dream (crypto)
    • Cosmic Ray V3 (pwn)
  • 😎AKASEC CTF 24
    • Warmup (pwn)
    • Good_trip (pwn)
    • Sperm Rev (rev)
    • Paranoia (rev)
    • Grip (rev)
    • Risks (rev)
    • Lost (crypto)
  • 😁L3AK CTF 24
    • oorrww (pwn)
    • angry (rev)
    • Related (crypto)
    • BatBot (web-misc)
    • Matrix Magic (crypto)
  • 🥹CDDC Qualifiers 2024
    • WASM (rev)
    • crashMe (pwn)
Powered by GitBook
On this page
  1. vsCTF 24

Dream (crypto)

I hear python MT can be broken with 624 outputs, but I only really need 8 random numbers. Surely you can't break it... right?

Last updated 11 months ago

We are given the following code:

#!/usr/local/bin/python
if __name__ != "__main__":
    raise Exception("not a lib?")

from os import urandom
# check if seed.txt exists
try:
    seed = open("seed.txt", "rb").read()
except:
    seed = urandom(8)
    # seed is 8 bytes
    open("seed.txt", "wb").write(seed)

seed = int.from_bytes(seed, "big")
import random
random.seed(seed)
from ast import literal_eval
idxs = literal_eval(input(">>> "))
if len(idxs) > 8:
    print("Ha thats funny")
    exit()
for idx in range(624):
    rand_out = random.getrandbits(32)
    if idx in idxs:
        print(rand_out)


key = random.getrandbits(256)
nonce = random.getrandbits(256)
flag = open("flag.txt").read()
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from hashlib import sha256
aes_key = sha256(str(key).encode()).digest()[:16]
aes_nonce = sha256(str(nonce).encode()).digest()[:16]
cipher = AES.new(aes_key, AES.MODE_GCM, nonce=aes_nonce)
ct = cipher.encrypt(pad(flag.encode(), 16))
print(ct.hex())

The seed is hardcoded in seed.txt. It is read and used. Then a list is accepted as input. The given list must be at most of length 8. Then, 624 random 32-bit numbers are generated. If their index is present in the given list, they will be printed out.

Then, the key and nonce are generated (again using random.getrandbits), and the flag is encrypted.

In order to retrieve the seed, we need to retrieve all 624 numbers. Since the seed is hardcoded, we can repeatedly connect to the server to retrieve all of them. Each time we would retrieve a different idx from the server. Then, using randcrack, we can work out the Mersenne Twister seed, generate the key and nonce, and decrypt the flag.

from pwn import *
from randcrack import RandCrack
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from hashlib import sha256

context.log_level = 'debug'
rc = RandCrack()
curr_idx = 0

for i in range(78):
    conn = remote("vsc.tf", 5001)
    payload = b"["
    for j in range(8):
        payload += str(curr_idx).encode()

        payload += b","
        curr_idx += 1
    payload += b"]"
    
    conn.sendlineafter(b">>> ", payload)
    for j in range(8):
        x = conn.recvline()
        info(x)
        x = int(x[:-1].decode())
        rc.submit(x)
    conn.close()

conn = remote("vsc.tf", 5001)
conn.sendlineafter(b">>> ", b"[0]")
conn.recvline()
ct = bytes.fromhex(conn.recvline()[:-1].decode()) # receive ciphertext in hex, convert to number in bytes
conn.close()
key = rc.predict_getrandbits(256)
nonce = rc.predict_getrandbits(256)
aes_key = sha256(str(key).encode()).digest()[:16]
aes_nonce = sha256(str(nonce).encode()).digest()[:16]
cipher = AES.new(aes_key, AES.MODE_GCM, nonce=aes_nonce)
pt = cipher.decrypt(ct)
print(unpad(pt, 16))
# b'vsctf{dream_luck???_5e3ec2f2d338fc9f}'
👨‍🦯
982B
chall.py
225B
Dockerfile
97B
docker-compose.yml