Bei der heutigen Challenge „Easy Peasy“ von picoCTF geht es darum, eine mit einem One-Time-Pad verschlüsselte Flag zu entschlüsseln.
Wenn sie richtig implementiert ist, ist eine One-Time-Pad-Verschlüsselung ohne den richtigen Schlüssel nicht zu knacken.
Aus der Beschreibung der Challenge erhalten wir eine Serveradresse, mit der wir uns über nc
verbinden können, und eine Datei mit dem Python-Code, der auf dem Server ausgeführt wird. Die einzige Möglichkeit, die Flagge zu erhalten, ist also, dass die Implementierung der One-Time-Pad-Verschlüsselung einen Fehler aufweist.
Wie funktioniert eine One-Time-Pad Verschlüsselung?
Für die One-Time-Pad-Verschlüsselung benötigst du einen Klartext zum Verschlüsseln und einen Schlüssel, der mindestens so lang ist wie der Klartext.
Stell dir vor, du hast den Klartext „This is a secret message“ mit einer Länge von 24 Zeichen. Um diese Nachricht zu verschlüsseln, benötigst du einen Schlüssel mit mindestens 24 Zeichen. Wählen wir „My super secret otp key!“ als Schlüssel.
Um den Text zu verschlüsseln, musst du jedes Zeichen des Klartextes mit dem Zeichen des Schlüssels an der gleichen Stelle XOR-verknüpfen. Da nur Zahlen und keine Zeichen XOR-verknüpft werden können, kannst du die ASCII-Darstellung jedes Zeichens XOR-verknüpfen.
Um die Nachricht zu entschlüsseln, wird einfach jede Zahl wieder mit dem entsprechenden ASCII-Wert des Schlüssels XOR-verknüpft.
![OTP-schema](https://www.bugninja.de/wp-content/uploads/2022/10/OTP.png)
Hier ein kurzes Python Script, das einen Text verschlüsselt und direkt wieder entschlüsselt:
cleartext = "This is a secret message"
key = "My super secret otp key!"
if ( len(key) <= len(cleartext) ) :
cipher = []
# Encryption
for i in range(len(cleartext)) :
cipher.append(ord(cleartext[i]) ^ ord(key[i]))
print("Chipher as array of integers:", cipher)
# Decryption
output = []
for i in range(len(cipher)) :
output.append(chr(cipher[i] ^ ord(key[i])))
print("Back to the original message:", "".join(output))
Das Wesentliche am One-Time-Pad ist, dass der Schlüssel geheim ist und – wie der Name schon sagt – nur einmal verwendet wird. Wenn ein Angreifer in der Lage ist, den Schlüssel ein zweites Mal mit einem bekannten Klartext zu verwenden, kann die resultierende Chiffre mit dem bekannten Klartext XOR-verknüpft werden, um den Schlüssel zu erhalten.
Untersuchen des Quellcodes
Werfen wir einen Blick auf den angegebenen Quellcode:
#!/usr/bin/python3 -u
import os.path
KEY_FILE = "key"
KEY_LEN = 50000
FLAG_FILE = "flag"
def startup(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(lambda p, k: "{:02x}".format(ord(p) ^ k), flag, key))
print("This is the encrypted flag!\n{}\n".format("".join(result)))
return key_location
def encrypt(key_location):
ui = input("What data would you like to encrypt? ").rstrip()
if len(ui) == 0 or len(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(lambda p, k: "{:02x}".format(ord(p) ^ k), ui, key))
print("Here ya go!\n{}\n".format("".join(result)))
return key_location
print("******************Welcome to our OTP implementation!******************")
c = startup(0)
while c >= 0:
c = encrypt(c)
Das Programm liest beim Start die Flag und den Schlüssel aus den Dateien des Servers. Der Schlüssel hat eine Größe von 50000 Bytes.
Nach dem Lesen der Flags und des Schlüssels wird die Flag mit den ersten Bytes des Schlüssels verschlüsselt und das verschlüsselte Flag wird an uns zurückgesendet. Jetzt können wir unsere eigenen Nachrichten verschlüsseln.
Aber was passiert, wenn wir versuchen, mehr Text zu verschlüsseln, als die Länge des Schlüssels beträgt?
Exploiting picoCTF easy peasy
Bei Herausforderungen wie dieser verwende ich gerne Pwntools, weil es die Kommunikation mit dem Server wesentlich erleichtert.
Pwntools hat auch viele Vorteile, wenn man mit Binaries zu tun hat, aber im Moment brauchen wir diese Funktionalität nicht.
Wenn Sie Pwntools noch nicht installiert haben, finden Sie hier eine ausführliche Anleitung dazu.
Ich habe jeden wichtigen Schritt des Python-Skripts kommentiert, so dass Du in der Lage bist, die Schritte nachzuvollziehen, die notwendig sind, um den Server zu exploiten und die Flagge zu erhalten.
from pwn import *
import re
key_len = 50000
conn = remote('mercury.picoctf.net', 20266)
# Receive the encrypted flag
conn.recvuntil(b'This is the encrypted flag!\n')
flag_xor = conn.recvline().decode('utf-8').strip()
flag_len = len(flag_xor)//2
print(f"Flag encrypted: {flag_xor}")
print(f"Length of flag: {flag_len}")
# Send key_len - flag_len bytes to return to the start of the key file
conn.recvuntil(b'What data would you like to encrypt?')
conn.send(b'a'*(key_len - flag_len) + b'\n')
# Send flag_len nullbytes to get the key
conn.recvuntil(b'What data would you like to encrypt?')
conn.send(b'\x00'*(flag_len)+b'\n')
conn.recvuntil(b'Here ya go!\n')
key_string = conn.recvline().decode('utf-8').strip()
print(f"Key: {key_string}")
# Split the key into a list where each item has two characters
key_parts = re.findall('..', key_string)
# Split the xored flag also into a list like the key before
flag_parts_xor = re.findall('..', flag_xor)
# xor flag with key
flag = ""
for i in range(flag_len):
flag += chr(int(flag_parts_xor[i], 16) ^ int(key_parts[i], 16))
# wrap the flag
flag = "picoctf{" + flag + "}"
print(f"Flag decrypted: {flag}")
Jetzt wissen Sie, warum es keine gute Idee ist, ein One-Time-Pad zweimal zu verwenden.
Lass einen Kommentar da, wie dir mein write-up der picoCTF Easy Peasy Challenge gefallen hat.