I had the correct idea, but unfortunately could not come up with the correct implementation to this problem. We can notice that shuffle
is a function that uses the value of each byte in mixed_and
as an index to select which byte in mixed_xor
will be outputted. Hence, with the same value for every byte in mixed_and
, the shuffle
output will only be a repeat of some byte len(FLAG)
times.
We cannot take advantage of mixed_xor
as the flag and data is xor
-ed with random data, so the output will inherently be random. My initial approach is, for every guess of the new character, we will try to craft some payload such that the and
value of the guess character and one of the known characters will point to the same index in mixed_xor
. For example, suppose we take c
as the known character, y
is some character we are guessing. My approach will try to craft a payload such that after mixed_and
, the character in the position of c
and y
will point to the same index in mixed_xor
, let's say 3.
However, the above approach does not work - sometimes you cannot find the correct byte for the known and unknown character to point at the same position in mixed_xor
. This is due to the nature of the and
operation, it is sometimes impossible to craft some byte, unlike xor
. Also this idea is necessary as the output is piped through sha256
, and obviously sha256
is very hard (or even impossible) to do pre-image attack, hence we need to limit the space of possible messages. That way, we will have the ability to verify our guess.
Peeking at the correct solution, props to soon_haari (and perhaps a bit cheating as I knew this writeup page exists), instead of leaking byte by byte, we will try to leak the flag bit by bit. This is an idea I overlooked, but relies on the same principle above: if the flag-bit is zero, very_mixed
(the output of shuffle
) are all equal bytes.
Hence, we can use this as some sort of decision (binary) system to guess each bit of the flag. We can precompute a table of 256 possible equal-bytes string hashed using sha256
. If the result is in the table, then we have a zero bit (as all bits should be the same), otherwise a one bit.
Since the mixed_xor
output is random, there is some (slim) chance that the one bit will create a byte string with all equal bytes, hence we have to repeat the guessing process a few times to be sure. I picked 5
, but anything more than 1
should work.
Python Implementation:
xfrom hashlib import sha256
from pwn import *
import json
from Crypto.Util.number import long_to_bytes
import os
io = remote('socket.cryptohack.org', 13402)
io.recvline()
FLAG = b"crypto{???????????????????????????????}"
# Check whether the hash exists in the table of 256 hashes of string
# with equal bytes
def check_guess(hash):
for i in range(256):
msg = bytes([i]) * len(FLAG)
if sha256(msg).hexdigest() == hash:
return 0
return 1
flag = 0
for i in range(8 * len(FLAG)):
msg = long_to_bytes(1 << i)
msg = b'\x00' * (len(FLAG) - len(msg)) + msg
bit = 0
# Send 5 times to avoid false negative
for j in range(5):
to_send = {'option': 'mix', 'data': msg.hex()}
io.sendline(json.dumps(to_send).encode())
target = json.loads(io.recvline().decode())['mixed']
if check_guess(target) == 1:
bit = 1
flag += bit << i
print(long_to_bytes(flag))