A one-time pad is unbreakable, but can you manage to recover the flag? (Wrap with picoCTF{}) nc mercury.picoctf.net 20266otp.py
Solution
Source code:
#!/usr/bin/python3 -uimport os.pathKEY_FILE="key"KEY_LEN=50000FLAG_FILE="flag"defstartup(key_location): flag =open(FLAG_FILE).read() kf =open(KEY_FILE,"rb").read() start = key_location stop = key_location +len(flag) key = kf[start:stop] key_location = stop result =list(map(lambdap,k:"{:02x}".format(ord(p)^ k), flag, key))print("This is the encrypted flag!\n{}\n".format("".join(result)))return key_locationdefencrypt(key_location): ui =input("What data would you like to encrypt? ").rstrip()iflen(ui)==0orlen(ui)>KEY_LEN:return-1 start = key_location stop = key_location +len(ui) kf =open(KEY_FILE,"rb").read()if stop >=KEY_LEN: stop = stop %KEY_LEN key = kf[start:]+ kf[:stop]else: key = kf[start:stop] key_location = stop result =list(map(lambdap,k:"{:02x}".format(ord(p)^ k), ui, key))print("Here ya go!\n{}\n".format("".join(result)))return key_locationprint("******************Welcome to our OTP implementation!******************")c =startup(0)while c >=0: c =encrypt(c)
A few things here:
startup() and encrypt() increment the key offset by the length of the data encrypted.
We know that the flag is 32 bytes, since the ciphertext is printed to us.
Once the key is reused, we can use the Crib Drag Attack to decode the ciphertext.
So, the goal is to make the key be used twice. This can easily be achieved by calculating the remaining bytes until stop % KEY_LEN eventually becomes 0. This means we have to encrypt a total of 50000 - 32 bytes of data.
Theory
If the key k is reused such that
c1β=m1ββkc2β=m2ββk
then we can XOR the two ciphertexts to get
c1ββc2β=m1ββm2β
In this case, we can control m2β. Since xβx=0 and xβ0=x, then
m1ββm2ββm2β=m1ββ0=m1β
Sidenote: if we don't know m2β, we can use the crib drag attack to guess parts of the message at a time.
Exploitation
The script will calculate the number of bytes so that the key is reused against our custom payload.
1) c1 = flag XOR key = 5b1e564b6e415c0e394e0401384b08553a4e5c597b6d4a5c5a684d50013d6e4b
from pwn import *
conn = remote('mercury.picoctf.net', 20266)
print(conn.recvuntil('What data would you like to encrypt?'))
remaining_bytes = 50000 - 32 # flag is 32 bytes
while remaining_bytes >= 1000:
print('[+] Sending 1000 bytes...')
conn.send('a' * 1000 + '\r\n')
remaining_bytes -= 1000
conn.recvuntil('What data would you like to encrypt?')
print(f'[+] Sending {remaining_bytes} bytes...')
conn.send('a' * remaining_bytes + '\r\n')
print(conn.recvuntil('What data would you like to encrypt?'))
print(f'[+] Key offset is at 0. Sending bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb...')
conn.send('b' * 32 + '\r\n')
print(conn.recvuntil('What data would you like to encrypt?'))
conn.close()
print("Done")