Rating:

This challenge has a custom encrypt function based on RNG. Each ciphertext block is the xor of a random 512-bit integer, one byte of plaintext, and a raw value which is derived from the seed. In the info PDF, we are given the first random value.

Looking at the get_seed code, raw is almost exactly the value of rand, except it's shifted to the right by one bit. This means that if we know raw, we only have 2 possible values for rand, and knowing rand allows us to compute both raw and seed.

Since we know r0, we can obtain raw ^ m by simply computing r0 ^ ct_blocks. There are only 256 possible values for m, so we can just try them all. All in all, this leaves 512 candidates for the rand in get_seed, which we can brute-force. Once we know the correct raw and seed, we can pretty much just run the encryption again to get the original message.

py
import random
ct_blocks = []
with open("encrypted.txt") as f:
for line in f:
ct_blocks.append(int(line.split(), 16))

# from info.pdf
r0 = 1251602129774106047963344349716052246200810608622833524786816688818258541877890956410282953590226589114551287285264273581561051261152783001366229253687592

# from src.py
def get_seed(l, rand):
seed = 0
raw = []
while rand > 0:
rand = rand >> 1
seed += rand
raw.append(rand)
return raw, seed

tmp = r0 ^ ct_blocks
for i in range(512):
rand = (tmp << 1) ^ i
raw, seed = get_seed(64, rand)
random.seed(seed)
if random.randint(1, 2**512) == r0:
break

out = []
random.seed(seed)
for i in range(64):
r = random.randint(1, 2**512)
pt = r ^ raw[i] ^ ct_blocks[i]
out.append(pt)
print(bytes(out))
# b'darkCON{user_W4rm4ch1ne68_pass_W4RM4CH1N3R0X_t0ny_h4cked_4g41n!}'


Original writeup (https://github.com/keyboard-monkeys/ctf-writeups/blob/main/2021-darkctf/crypto_tony_and_james.md).