Easy Peasy
Description
A one-time pad is unbreakable, but can you manage to recover the flag? (Wrap with picoCTF{}) nc mercury.picoctf.net 64260 otp.py
ncで接続されたサービスに接続すると、最初に暗号化フラグを取得し、必要なだけのデータを暗号化できる。
$ nc mercury.picoctf.net 20266
******************Welcome to our OTP implementation!******************
This is the encrypted flag!
5b1e564b6e415c0e394e0401384b08553a4e5c597b6d4a5c5a684d50013d6e4b
What data would you like to encrypt? test
Here ya go!
15532f0c
What data would you like to encrypt?
サービスの実装から、長さ 50000 の XOR パッドを使用して入力を暗号化していることがわかる。ワンタイムパッドとして使用する場合、これは壊れないはずですが、この場合、サービスはラップアラウンドを実行し、50000 文字ごとに同じパッドを再利用する。
したがって、フラグの暗号化に使用された XOR 値を取得するには、ラップアラウンドを発生させて、既知の入力を同じXOR値で再暗号化する必要があります。入力がわかっているので、それを暗号化された結果と XOR してキーを取得できます。次に、暗号化されたフラグを使用してキーを XOR するだけです。
実際、同じ暗号化フラグを、最初に暗号化するために使用された XOR ストリームで再暗号化することで、これをもう少し効率的にすることもできる。これにより、平文フラグが生成されます。
from pwn import *
from tqdm.auto import tqdm
import binascii
import string
KEY_LEN = 50000
encrypted = "51466d4e5f575538195551416e4f5300413f1b5008684d5504384157046e4959"
def get_message(slide, encrypted):
io = remote("mercury.picoctf.net", 64260)
for i in range(4):
io.recvline()
io.sendline("1"*(50000-slide))
io.recvline()
io.recvline()
io.sendline(deconvert(encrypted))
io.recvline()
io.recvline()
message = io.recvline().strip()
return message
def convert(message):
key = (chr(0)*len(message)).encode()
result = list(map(lambda p, k: "{:02x}".format(ord(p) ^ k), message, key))
return "".join(result)
def deconvert(message):
dec = b""
for i in range(0,len(message),2):
dec+=chr(int(message[i:i+2],16)).encode()
return dec
print(deconvert(encrypted))
message = get_message(32, encrypted)
message_dec = deconvert(message)
print(message_dec)
from pwn import *
KEY_LEN = 50000
MAX_CHUNK = 1000
r = remote("mercury.picoctf.net", 64260)
r.recvuntil("This is the encrypted flag!\n")
flag = r.recvlineS(keepends = False)
log.info(f"Flag: {flag}")
bin_flag = unhex(flag)
counter = KEY_LEN - len(bin_flag)
with log.progress('Causing wrap-around') as p:
while counter > 0:
p.status(f"{counter} bytes left")
chunk_size = min(MAX_CHUNK, counter)
r.sendlineafter("What data would you like to encrypt? ", "a" * chunk_size)
counter -= chunk_size
r.sendlineafter("What data would you like to encrypt? ", bin_flag)
r.recvlineS()
log.success("The flag: {}".format(unhex(r.recvlineS())))
picoCTF{3a16944dad432717ccc3945d3d96421a}