Se non hai nessuna chiave non puoi rompere nulla... Vero?
nc keyless.challs.olicyber.it 38300
Author: Matteo Rossi <@mr96>
La challenge espone un servizio con 3 opzioni possibili:
- firmare qualcosa per un utente a scelta, eccetto l'utente
admin
- verificare se stessi, fornendo il proprio nome utente e una firma di
Verifying myself: I am {user} on OliCyber.IT
, doveuser
è il nome utente scelto - inviare un messaggio a un utente scelto; questo restituirà la risposta dell'utente, che è sempre
ACK
, e la corrispondente firma (con la chiave dell'utente destinatario)
L'obiettivo è verificarsi come admin
.
Nota: questa challenge ha (almeno) 3 soluzioni possibili trovate durante lo sviluppo e il testing. Le riporteremo tutte e 3 nel writeup per completezza.
Analizziamo la funzione di verifica: data una firma
dove sha256
. Ignoriamo la funzione di firma per il momento, poiché non è necessaria per questa soluzione.
Dall'opzione 3 possiamo ottenere una firma di ACK
dall'admin. Chiamiamo questa firma
dove ACK
. A questo punto prendiamo
che, portando il termine in
Per questa seconda soluzione, ci serve sapere come funziona la funzione di firma. Dato un messaggio
Dalla prima query possiamo ottenere firme per qualsiasi utente admin
e qualsiasi messaggio
Sia ACK
. Sia
poiché
I numeri casuali random
di Python, che utilizza l'algoritmo Mersenne Twister, che non è crittograficamente sicuro. Pertanto, una volta recuperate abbastanza osservazioni di
Ora che conosciamo i valori di admin
e recuperare
A questo punto possiamo ottenere una firma per il messaggio
Infine verifichiamo la firma e otteniamo la flag.
Questa soluzione è simile alla prima nella struttura, ma utilizza il fatto che conosciamo il nome utente dell'admin.
Risolviamo
#!/usr/bin/env python3
import logging
import os
from pwn import remote
from hashlib import sha256
logging.disable()
def h(m):
return int.from_bytes(sha256(m).digest(), "big")
def extended_gcd(aa, bb):
lastremainder, remainder = abs(aa), abs(bb)
x, lastx, y, lasty = 0, 1, 1, 0
while remainder:
lastremainder, (quotient, remainder) = remainder, divmod(lastremainder, remainder)
x, lastx = lastx - quotient*x, x
y, lasty = lasty - quotient*y, y
return lastremainder, lastx * (-1 if aa < 0 else 1), lasty * (-1 if bb < 0 else 1)
HOST = os.environ.get("HOST", "localhost")
PORT = int(os.environ.get("PORT", 38300))
r = remote(HOST, PORT)
pk = r.recvline()
n = int(pk.split()[-2][1:-1])
e = 65537
r.recvuntil(b"> ")
r.sendline(b"3")
r.recvuntil(b": ")
r.sendline(b"test")
r.recvuntil(b": ")
r.sendline(b"admin")
r.recvlines(2)
sig = r.recvline()
s1 = int(sig.split()[-2][1:-1])
s2 = int(sig.split()[-1][:-1])
# a*e - b*h("I am...") = -h("ACK")
to_forge = h(b"Verifying myself: I am admin on OliCyber.IT")
base = h(b"ACK")
g, c1, c2 = extended_gcd(e, -to_forge)
c1 *= -base
c2 *= -base
s1f = pow(s1, c2, n)
s2f = (s2*pow(s1, c1, n)) % n
r.recvuntil(b"> ")
r.sendline(b"2")
r.recvuntil(b": ")
r.sendline(b"admin")
r.recvuntil(b": ")
r.sendline(str(s1f).encode())
r.recvuntil(b": ")
r.sendline(str(s2f).encode())
print(r.recvline())