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[0]` is almost exactly the value of `rand`, except it's shifted to the right by one bit. This means that if we know `raw[0]`, 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[0] ^ m[0]` by simply computing `r0 ^ ct_blocks[0]`. There are only 256 possible values for `m[0]`, 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()[1], 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[0]

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