Find Marher's Secret
5 minutos de lectura
Se nos proporciona un código en Python y un servidor al que conectarnos:
$ nc 161.35.172.25 32275
Connected to the cyborg's debugging interface
Options:
1. Encrypt your text.
2. Claim the key.
>
Análisis de código estático
Esta es la parte importante del código:
def challenge(req):
key = bytes.fromhex(KEY)
assert(len(key) == 27)
req.sendall(b'Connected to the cyborg\'s debugging interface\n')
while True:
req.sendall(
b'\nOptions:\n1. Encrypt your text.\n2. Claim the key.\n> ')
try:
response = json.loads(req.recv(4096).decode())
if response['option'] == 'encrypt':
iv = bytes.fromhex(response['iv'])
pt = bytes.fromhex(response['pt'])
ct = encrypt(key, iv, pt)
payload = {'response': 'success',
'pt': response['pt'], 'ct': ct}
payload = json.dumps(payload)
req.sendall(payload.encode())
elif response['option'] == 'claim':
answer = bytes.fromhex(response['key'])
if hashlib.sha256(answer).hexdigest() == hashlib.sha256(key).hexdigest():
payload = {'response': 'success', 'flag': FLAG}
payload = json.dumps(payload)
req.sendall(payload.encode())
else:
payload = {'response': 'fail',
'message': 'Better luck next time.'}
payload = json.dumps(payload)
req.sendall(payload.encode())
else:
payload = {'response': 'error', 'message': 'Invalid option!'}
payload = json.dumps(payload)
req.sendall(payload.encode())
except Exception as e:
payload = json.dumps(
{'response': 'error', 'message': 'An error occured!'})
req.sendall(payload.encode())
return
En primer lugar, vemos que necesitamos interactuar con el servidor usando mensajes con formato JSON.
Además, podemos enviar mensajes de texto arbitrarios al servidor y se cifrarán mediante RC4 de la siguiente manera:
def encrypt(key, iv, pt):
return ARC4.new(iv + key).encrypt(pt).hex()
Nótese que también debemos proporcionar el IV, porque el cifrador RC4 se crea a partir de iv + key
.
Para obtener la flag, debemos extraer la clave secreta, que sabemos que tiene una longitud de 27 bytes.
Ataque FMS
Hay un ataque muy conocido a los criptosistemas que usan RC4, como el protocolo WEP para Wi-Fi (que está completamente roto). Este ataque es el ataque Fluhrer, Mantin y Shamir (ataque FMS). Nótese que el acrónimo del nombre del reto es una pista que apunta a este ataque.
RC4 es un cifrado en flujo que funciona de la siguiente manera:
Básicamente, un cifrado de flujo tiene una clave de entrada y genera un flujo de bits aleatorio, de modo que el cifrado es la operación XOR entre el flujo de bits y los bits del mensaje. El uso de la misma clave dará como resultado el mismo flujo de bits.
La siguiente explicación se basa principalmente en estos recursos:
Y para el script final, adaptaré el código fuente de FMS-Attack.
Breve explicación
RC4 usa varias S-Box para generar el flujo de bits. Siguiendo el documento anterior, en la Sección 7.1, tenemos la misma situación en este reto. Es decir, controlamos el IV y se antepone a la clave secreta antes de crear el cifrador RC4.
Vamos a definir $K$ como la clave de entrada para el cifrador RC4 y $\mathrm{SK}$ como la clave secreta real. También, sea $L$ la longitud de la clave. Por conveniencia, el IV solo tendrá 3 valores. Por lo tanto:
$$ K[0 .. L - 1] = [ \mathrm{IV}[0], \mathrm{IV}[1], \mathrm{IV}[2], \mathrm{SK}[0], \mathrm{SK}[1], \dots, \mathrm{SK}[L - 4]] $$
El problema está en que al usar $\mathrm{IV} = [A + 3, N - 1, V]$ podremos recuperar el byte $K[A + 3]$ con una alta probabilidad. Esto ocurre porque en cada iteración del Key Schedule Algorithm (KSA) algunos bytes se intercambian por índice, y existe una condición resuelta en la que $\mathrm{KSB}[0] = S[S[1] + S[j]]$, donde $\mathrm{KSB}$ son los bytes del flujo generado (la salida del cifrador RC4).
Los dos primeros elementos del IV se eligen de tal forma que la primera iteración intercambia los índices $0$ y $A + 3$ (porque el índice i
empieza en $0$ y el j
también empieza en $0$, pero se actualiza a j = (j + s[i]) % 256
, que es $(A + 3 + (N - 1)) $):
La segunda iteración intercambia los índices $1$ y $A + 3$, porque i
se incrementa en uno y j
se actualiza como j = (j + s[i]) % 256
, que queda invariante:
Como dice el paper: en la siguiente iteración, j
se incrementa en $V + 2$, que implica que con distintos IV tendremos diferentes valores de j
y por tanto, a partir de este punto cada IV actúa de forma diferente. Como el atacante sabe el valor de $V$ y $K[3], \dots, K[A + 2]$, puede calcular la clave hasta la iteración anterior a la número $A + 3$. Así, se sabe el valor de j
en la iteración $A + 2$ y el valor exacto de la permutación. Si el valor de los índices $0$ y $1$ son modificados, el atacante descarta el IV. En caso contrario, j
se incrementa como $S_{A + 2}[i_{A + 3}] + K[A + 3]$, y luego se hace el intercambio, resultando en la siguiente estructura:
Script final
Hay dos scripts importantes en FMS-Attack. Uno de ellos es WEPOutput.py, que genera el cifrador y guarda los valores de salida en un archivo CSV; y keyRecover.py, que coge el archivo CSV y extrae la clave usando el ataque FMS.
Para resolver el reto, combiné ambos scripts, sin usar un archivo CSV para guardar los valores intermedios. En su lugar, tendremos que usar el servidor para generar los valores necesarios y realizar el ataque FMS.
Este ataque funciona en WEP porque el texto claro siempre empiezarpor aa
(en hexadecimal), que es parte de la cabecera SNAP. Se puede usar otro byte, pero lo dejé como estaba.
Flag
Si ejecutamos el script, encontraremos la flag (puede tardar unos pocos minutos):
$ python3 solve.py 161.35.172.25:32275
[+] Opening connection to 161.35.172.25 on port 32275: Done
[+] A: 27
[+] Key: A1DD163ADAC252ADE631A89A203C2BE92B981EAAE6DAC3044EACE1
[+] Flag: HTB{7h3_FMS_@tt3ck_br0k3_@_l0t_0f_th17gs_i7_w1f1!!}
[*] Closed connection to 161.35.172.25 port 32275
El script completo se puede encontrar aquí: solve.py
.