Homomurphy's Law
13 minutos de lectura
Se nos da el código fuente de un proyecto de ransomware.
Análisis de código fuente
Este es ransomware.py
:
from Crypto import Random
from encryption import *
import os
MBEGIN = "---BEGIN MORPHEUS KEY---"
MEND = "---END MORPHEUS KEY---"
GBEGIN = "----BEGIN GPUBLIC KEY---"
GEND = "---END GPUBLIC KEY---"
with open("note.txt", "r") as f:
note = f.read()
aes = AESP()
gm = GM()
obf = OBF()
def encrypt(key):
obf_key = obf.obfuscate(key)
enc_key = gm.encrypt(obf_key)
return enc_key
def pwn(directory):
for file in os.listdir(directory):
file = directory + "/" + file
with open(file, "rb") as f:
data = f.read()
enc_data = aes.encrypt(data)
with open(file, "wb") as f:
f.write(enc_data)
os.rename(file, file + r'.IBKFZ')
# Here will be the flag and other files
pwn("sensitive_data")
enc_key = encrypt(aes.key)
enc_key = "\n".join([str(x) for x in enc_key])
with open("sensitive_data/custom_note.txt", "w") as f:
data = note + "\n"
data += MBEGIN + "\n"
data += enc_key + "\n"
data += MEND + "\n"
data += "\n"
data += GBEGIN + "\n"
data += hex(gm.n)[2:] + "\n"
data += hex(gm.x)[2:] + "\n"
data += GEND
f.write(data)
Este script define algunas instancias AESP
, GM
y OBF
. La función llamada pwn
toma cada archivo de sensitive_data
y lo cifra con AES. Luego, la clave de AES se ofusca con obf.obfuscate
y se cifra con gm.encrypt
. El resultado se guarda como “MORPHEUS KEY” y “GPUBLIC KEY” en sensitive_data/custom_note.txt
:
---= MORPHEUS V5.0.4 =---
***********************UNDER NO CIRCUMSTANCES DO NOT DELETE THIS FILE, UNTIL ALL YOUR DATA IS RECOVERED***********************
*****FAILING TO DO SO, WILL RESULT IN YOUR SYSTEM CORRUPTION, IF THERE ARE DECRYPTION ERRORS*****
Attention!
All your files, documents, photos, databases and other important files are encrypted and have the extension: .IBKFZ
The only method of recovering files is to purchase an unique private key. Only we can give you this key and only we can recover your files.
The server with your key is in a closed network TOR. You can get there by the following ways:
----------------------------------------------------------------------------------------
| 0. Download Tor browser - https://www.torproject.org/
| 1. Install Tor browser
| 2. Open Tor Browser
| 3. Open link in TOR browser:
| 4. Follow the instructions on this page
----------------------------------------------------------------------------------------
On our page you will see instructions on payment and get the opportunity to decrypt 1 file for free.
ATTENTION!
IN ORDER TO PREVENT DATA DAMAGE:
* DO NOT MODIFY ENCRYPTED FILES
* DO NOT CHANGE DATA BELOW
---BEGIN MORPHEUS KEY---
2f2a8daa5d3f2d3901604c7075c8471c2e3f142c1a80bb1a23aa4c6919d9dcd4af7b490002c9ad2ebbd6426484c98a304dbfe67478beb289e6cb3a01d81500d0558eea3a2807ce507b403cf5837b9870a6b84455d3fbf89f566fd2c255252b912cab5a739db2b96b4c5d738bdb77da5824c6290fecda1dde09f969c3348d035730774206be7f264bacf8ab673fe2f4ed435d3c3fe235d3257c177c593d106d2b3412f9e0395f285c0564d5714070f902d6726e9d885a030e8b79ce585bb99195c41a4477b72082bd10d97205379901f8ce113ccc9d0d118d07c05694d89317be43da78f86ad53fb8cbce56fc766dc3167accb3750d4e62cee8bae22527e32512
1cc27c4153df5fe1c875b5f594db39220a646bf225ca72e2237e41ddc6b8200c4d8a4335f4f8b3914f3d31eefca7cbf28769cd178b6e68eb4b2eac5b985bd629e208fb8d4478098eadef0a4a12b0c4b6c35735ce484a5a05540d8f635ee0bbdee7df513dfc3542c0cfbaf76fe26f01cd86d1a88e58d1712dc5228e1791da0847e839919a8fc4b827b8c25d921746d2b8e7c6508f262831a14eeef410fc9e2d3662c18a883d28f860a23fdccb9b409ada5b7a02a284b0288133ba3ae81eec7470eca71e1e96e68fe85c4e1db01e679a6814a7ae76bc9883a1650aee919b18ef46512536a5063adc6f10796eedfc9ba527b93d2afefc39c9587fc85c5017d4b21
4227fb66ff1396089d6953d7297d70484cb02ef7e8c6a428dd1bb8447ceb24339c9e74679010e68dfe74ac17b4acf36c6818d0ca24f99c35754001cb424cbb922a7b6cb8f3c5d752bc7ea75871ffe37713dd3ad5e58678f3e6117a9385016d1a00645a94637b5c029c81240e9e3b571b76375dfe9c031fb45cba73bfd59183dcd36b7ea580537f00f1517828018fba3c48ddf724b3000dd937423f4cf77f752c17ba5548f237f86141bab089a4299be42e7357bd0c8179318845d6b54ecf51240db89033e2e3cef9d01b1009b8c2678d5cb7f9814517699ed21369e6adbc51dedbd5fef5c53c08c99cde0b25e7f24f463d029764d22a67d0167af26ca4f5c8fa
...
6f317a1ae90ef57ee81accf22211fa1fbba8bc72de1fc18d0add4afc52d85de9e5d7f514d7c169b054f51a3d36e89c0a84124d23a44228405507298a750f566c4acfb0a2ed79f5a0875fb00875e92fedf06ebd5fa38f32a2c1392f00520f41d1040cf5f9c2d89555bb90ea88f0360193110bbbbf650006b3633a2626549cc3d13e37340f2d9e0ee6ea2d1e1157aeade4df6eec626abc360a257aebcdd6047027f2d6671df02eac767f58cb7b6e9c9bcbcd58eb3039cf3d4b693edc49c556e21717ec8d60fe44f62a345dddb8a1181ab7c0583d62f58f7838351e0e860defeb8c0b099b936957203c9dfe6bd7cdf006bcb76a8ad97e40c0d0dd1bebc0a768a207
272fa06fbc0b58ce8838789d62faef1fdd0f511dffe8c2286a683be4a5b93f8b943abe084cd5abb848789d10520da5f6265f435de1c9d8aab6945a3921cc3fdc9c9b33595845d24fd8793a97d4629fe03e4ead32f4160278f495a5764449532574eea5355fc9e649c0f4d58e2b1d3074d3f8aabf93c3c98877fe022f4c528205900b5582c5f3822720fba2ca8fd364727cad4c4c7d02835497ddb8336b64a847acb335c96cb0de631e48ac14ecdaa1c25c3270d72e24e93c70579555e1705e6fe87ef231c2c992a50a8c0f720c5c6c349303bc3a9d60ff21b855b3d58da09dce21cb63e8144626bb117275b0a42b03d0b7d05e42680b67208f7d5e1ebcbbcf32
---END MORPHEUS KEY---
----BEGIN GPUBLIC KEY---
9074fd8be583014d8048a1d79bfc727e13786a64b0374f7c24ba68572b8a1f64ed2360be46dc52b3c1c183843bca63b5427e39a092b2077426ab36880e456525dcc603ed18335e44f57d5eda11161982d3dca2f999d72aae0fe2b1f50f6dbff7db0395406ccf27179d3c345fe04b14e24846ee5b156457d7b4da66f922d8a72969138c09675854ddee14b8999113b223792dea8468d3afdd40a6925adc65f24758c3a1130098e50ca879f4dc421e3dca4b1e6dc3bacb5692ae2a70d8fdce2f009df6e6ab8334e541c8e50c7a9e9b949d21ce7fcad6426b596f06d8a03646150dd511d4f8b442518b6b2e74df73e50fc5dbd55765247d2f703eedf407dc7ba5fb
6badcfa5896d8c1a309e98ad2ec56d274db2dabaf8ee9253bb5f65d9949c125a153ba3e12b0a136a0a3dff2247f1b9b2d890e91613ab880bcd9dcf68f2e5f15303e8b27ea5565403674eae32c9c4a2e24bb69a5dd406b0b8386d2ec91952c3a4daff355aba68d91f7ed5b7ea687e9b964dd73fe673c348b4dba554abb0a6804c08e6b89be1d5feca76b1552bcbbb8ce6c67814bdf31a5107c91c22d055114a03a007467c5bfd664cac5b7f1ba4a97e43e3b602d18cf12c36820a2462d5868992f1cf2739198d4a1984b19adf764480a306bec8c840db8314354c74017b0fdb8697078a801b9ea8d75655e7f0b73c7343379c8f6a8c0946f2b854494b7e776662
---END GPUBLIC KEY---
Entonces, tenemos otro script llamado server.py
:
def main(s):
gm = GM()
obf = OBF()
while True:
aesp, aesc = AESP(), AESC()
try:
sendMessage(s, "Awaiting for encryption key\n\n")
enc_key = [recieveMessage(s, "> ") for _ in range(128)]
enc_key = [int(i) for i in enc_key]
obf_key = gm.decrypt(enc_key)
key = obf.deobfuscate(obf_key)
choice = recieveMessage(
s, "\nChoose what library you want to benchmark:\n\n"
"[1] - PyCrypto AES Encrypt\n"
"[2] - cryptography AES Encrypt\n\n> ")
if choice == "1":
time, enc_file = AESEncrypt(aesp, key)
elif choice == "2":
time, enc_file = AESEncrypt(aesc, key)
else:
sendMessage(s, "Invalid choice!\n")
exit()
sendMessage(s, enc_file.hex() + "\n")
sendMessage(s, str(time) + "\n")
except Exception as e:
sendMessage(s, "Unexpected error.\n")
print(e)
Básicamente, nos permite cifrar un textoclaro conocido (note.txt
) con una clave controlada. Sin embargo, se supone que esta clave debe ser ofuscada y cifrada con GM
. Es por eso que el servidor toma nuestra entrada, la descifra con GM
y la desofusca para obtener la clave de AES. El archivo note.txt
no se proporciona, pero podemos deducir su contenido de ransomware.py
. Básicamente es la primera parte de sensitive_data/custom_note.txt
eliminando las claves:
---= MORPHEUS V5.0.4 =---
***********************UNDER NO CIRCUMSTANCES DO NOT DELETE THIS FILE, UNTIL ALL YOUR DATA IS RECOVERED***********************
*****FAILING TO DO SO, WILL RESULT IN YOUR SYSTEM CORRUPTION, IF THERE ARE DECRYPTION ERRORS*****
Attention!
All your files, documents, photos, databases and other important files are encrypted and have the extension: .IBKFZ
The only method of recovering files is to purchase an unique private key. Only we can give you this key and only we can recover your files.
The server with your key is in a closed network TOR. You can get there by the following ways:
----------------------------------------------------------------------------------------
| 0. Download Tor browser - https://www.torproject.org/
| 1. Install Tor browser
| 2. Open Tor Browser
| 3. Open link in TOR browser:
| 4. Follow the instructions on this page
----------------------------------------------------------------------------------------
On our page you will see instructions on payment and get the opportunity to decrypt 1 file for free.
ATTENTION!
IN ORDER TO PREVENT DATA DAMAGE:
* DO NOT MODIFY ENCRYPTED FILES
* DO NOT CHANGE DATA BELOW
Cifrado
Una cosa curiosa es que hay dos implementaciones de AES disponibles:
class AESP():
def __init__(self):
self.key = aes_key
self.iv = Random.new().read(16)
def encrypt(self, msg):
cipher = PYAES.new(self.key, PYAES.MODE_CBC, self.iv)
return self.iv + cipher.encrypt(pad(msg, 16))
def decrypt(self, ct):
cipher = PYAES.new(self.key, PYAES.MODE_CBC, ct[:16])
return cipher.decrypt(ct[16:])
def setKey(self, key):
self.key = key
class AESC():
def __init__(self):
self.key = aes_key
self.iv = Random.new().read(16)
def encrypt(self, msg):
cipher = Cipher(algorithms.AES(self.key), modes.CBC(self.iv))
encryptor = cipher.encryptor()
ct = encryptor.update(pad(msg, 16)) + encryptor.finalize()
return self.iv + ct
def decrypt(self, ct):
cipher = PYAES.new(self.key, PYAES.MODE_CBC, ct[:16])
return cipher.decrypt(ct[16:])
def setKey(self, key):
self.key = key
De hecho, podemos elegir cuál de ellas usar y hacer un “benchmark”:
def AESEncrypt(aes, key):
aes.setKey(key)
data = readFile("note.txt")
ts = time.time()
for _ in range(1000):
enc_file = aes.encrypt(data)
te = time.time()
return te - ts, enc_file
Cuando vi esto, pensé que el ataque involucraba un enfoque basado en tiempo… pero no es así, que yo sepa.
La implementación de la ofuscación está aquí:
class OBF():
def __init__(self):
self.seed = rseed % 2**7
seed(self.seed)
self.pin = self.genPin()
def genPin(self):
pin = []
initial = [[randint(1, 256) for _ in range(128)] for _ in range(8)]
initial = self.transpose(initial)
for i in range(128):
tmp = initial[i]
shuffle(tmp)
pin.append(tmp[0])
return ([i % 2 for i in pin])
def transpose(self, bits):
return [row for row in map(list, zip(*bits))]
def xor(self, bits, key):
return [a ^ b for a, b in zip(bits, key)]
def bytesToBits(self, bytes):
bits = bin(int(bytes.hex(), 16))[2:].zfill(len(bytes) * 8)
bits = [int(i) for i in bits]
return bits
def bitsToBytes(self, bits):
bits = "".join([str(i) for i in bits])
bits = [int(bits[i:i + 8], 2) for i in range(0, len(bits), 8)]
return bytes(bits)
def obfuscate(self, bytes):
bits = self.bytesToBits(bytes)
bits = self.xor(bits, self.pin)
return bits
def deobfuscate(self, bytes):
return self.bitsToBytes(self.obfuscate(bytes))
Básicamente, elige una semilla al azar rseed
y utiliza rseed % 2**7
como una nueva semilla para el PRNG. Luego emplea genPin
para generar el pin. Aunque no conocemos el pin, podemos aplicar la fuerza bruta porque rseed % 2**7
es un número entero entre 0
y 127
. Este pin se transforma en bits y se usa para cifrar otros bits con XOR (obfuscate
). Obsérvese que deobfuscate
llama a obfuscate
porque XOR es igual a su inversa.
Por último, pero no menos importante, tenemos GM
, que viene de Goldwasser–Micali. Uno puede encontrar esto leyendo el nombre del reto: “Homomurphy’s Law”. Hay un tipo de cifrado que se llama homomórfico, que permite que los cálculos se realicen en datos cifrados sin tener que descifrarlos primero. De todos modos, aquí está la implementación:
class GM():
def __init__(self):
self.p = p
self.q = q
self.n = self.p * self.q
self.x = self.jacobi_symbol()
def jacobi_symbol(self):
tmp = getRandomRange(0, self.n)
while legendre(tmp, self.p) != -1 or legendre(tmp, self.q) != -1:
tmp = getRandomRange(0, self.n)
return tmp
def encrypt(self, bits):
ct = []
for bit in bits:
y = getRandomRange(0, self.n)
tmp = pow(y, 2) * pow(self.x, int(bit)) % self.n
ct.append(format(tmp, 'x'))
return ct
def decrypt(self, ct):
m = 0
for c in ct:
m <<= 1
if legendre(c % self.p, self.p) != 1 or legendre(
c % self.q, self.q) != 1:
m += 1
h = '%x' % m
return bytes.fromhex(h.zfill(32))
Goldwasser–Micali funciona de la siguiente manera:
- El módulo $n$ es la clave pública de tal manera que $n = p q$, con $p$ y $q$ dos números primos mantenidos como clave privada
- El residuo cuadrático $x$ es también una clave pública (como símbolo Jacobi: $\left(\frac{x}{n}\right) = 1$)
- Sea $\mathcal{E}: \{0, 1\} \to \mathbb{Z}/n\mathbb{Z}$ la función de cifrado
- Un bit de texto claro $b$ se cifra como sigue: $\mathcal{E}(b) = x^b y^2 \mod{n}$ para un valor aleatorio $y \in \{0, 1, \dots, n - 1\}$
La función de cifrado puede parecer débil porque solo hay dos casos: $\mathcal{E}(0) = y^2 \mod{n}$ o $\mathcal{E}(1) = x y^2 \mod{n}$. Sin embargo, se eligió $x$ para ser un residuo cuadrático, por lo que no hay forma de averiguar qué resultado proviene de $b = 0$ o $b = 1$.
La propiedad homomórfica es la siguiente:
$$ \begin{align} \mathcal{E}(b_1) \cdot \mathcal{E}(b_2) & = (x^{b_1} y_1^2) \cdot (x^{b_2} y_2^2) \mod{n} \\ & = x^{b_1 + b_2} (y_1 y_2)^2 \mod{n} \\ & = \mathcal{E}(b_1 \oplus b_2) \end{align} $$
El método de descifrado utiliza $p$ y $q$ para determinar si el residuo es un símbolo de Legendre en $p$ y $q$ (nótese que $y$ no puede ser un residuos cuadrático módulo $p$ y $q$ porque son primos y además $y < n$).
Desarrollo del ataque
Recordemos que podemos ingresar cualquier clave ofuscada y cifrada para cifrar note.txt
. Esta habilidad nos permitirá encontrar la clave de AES utilizada para cifrar la flag.
En primer lugar, descubriremos el pin de OBF
(fuerza bruta desde 0
hasta 127
). Para hacer esto, podemos usar GM
para cifrar a un candidato a pin y luego enviarlo al servidor, que lo descifrará nuevamente al candidato a pin. Entonces, el servidor usará OBF
para desofuscarlo para obtener la clave de AES. Una vez que tengamos el pin correcto, la clave de AES será todo bytes nulos, por lo que podemos comparar salidas para verificarlo.
Obtención del pin
Quizás la implementación sea más clara:
def h2d(h: str) -> int:
return int(h, 16)
def aes_decrypt(key: bytes, enc_data: bytes) -> bytes:
iv, ct = enc_data[:16], enc_data[16:]
cipher = AES.new(key, AES.MODE_CBC, iv)
return cipher.decrypt(ct)
def main():
host, port = sys.argv[1].split(':')
io = remote(host, int(port))
with open('sensitive_data/custom_note.txt') as f:
custom_note = f.read()
note = custom_note.split(f'\n{MBEGIN}')[0]
e_k_xor_pin = list(map(h2d, re.findall(
f'{MBEGIN}\n(.*?)\n{MEND}', custom_note, re.DOTALL)[0].split()))
n, x = map(h2d, re.findall(
f'{GBEGIN}\n(.*?)\n{GEND}', custom_note, re.DOTALL)[0].split())
gm = GM(n, x)
prog = log.progress('PIN seed')
index_prog = log.progress('Sending key index')
for s in range(128):
prog.status(str(s))
obf = OBF(s)
io.recvuntil(b'Awaiting for encryption key')
for i, p in enumerate(gm.encrypt(obf.pin)):
index_prog.status(str(i))
io.sendlineafter(b'> ', str(h2d(p)).encode())
io.sendlineafter(b'AES Encrypt\n\n> ', b'1')
enc_data = bytes.fromhex(io.recvline().decode().strip())
if note.encode() in aes_decrypt(b'\0' * 16, enc_data):
break
prog.success(str(s))
Con lo anterior, podemos encontrar el pin (puede tomar alrededor de 10 minutos):
$ python3 solve.py 104.248.174.109:32511
[+] Opening connection to 104.248.174.109 on port 32511: Done
[+] PIN seed: 114
[▆] Sending key index: 127
[*] Closed connection to 104.248.174.109 port 32511
Encontrando la clave de AES
Ahora tenemos estos valores:
zero_key = bytes.fromhex(hex(int(''.join(map(str, obf.pin)), 2))[2:])
log.success(f'OBF pin: {obf.pin}')
log.info(f'Zero key: {zero_key}')
$ python3 solve.py 104.248.174.109:32511
[+] Opening connection to 104.248.174.109 on port 32511: Done
[+] PIN seed: 114
[\] Sending key index: 127
[+] OBF pin: [1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1]
[*] Zero key: b'\xc9\xbc\xeeq\x12\x98\xdf\x7f9K\xb4\x046\xc1R\x1b'
[*] Closed connection to 104.248.174.109 port 32511
La “Zero key” es la clave de AES que resulta cuando enviamos todo 0
cifrados al servidor. Obsérvese que GM
descifrará a todos los 0
y OBF
usará XOR con el pin que ya tenemos, por lo que tenemos la clave de AES resultante. Usando este hecho, podemos encontrar la clave de AES utilizada para cifrar la flag byte a byte.
Para el primer byte, podemos hacer lo siguiente:
key = []
io.recvuntil(b'Awaiting for encryption key\n\n')
for i, k in enumerate(e_k_xor_pin):
index_prog.status(str(i))
io.sendlineafter(b'> ', str(k if 0 == i // 8 else k * k).encode())
io.sendlineafter(b'AES Encrypt\n\n> ', b'1')
enc_data = bytes.fromhex(io.recvline().decode().strip())
for k in range(256):
test_key = bytes([k]) + zero_key[1:]
if note.encode() in aes_decrypt(test_key, enc_data):
key.append(k)
print(bytes(key).hex())
break
El punto clave está aquí:
for i, k in enumerate(e_k_xor_pin):
index_prog.status(str(i))
io.sendlineafter(b'> ', str(k if 0 == i // 8 else k * k).encode())
Estamos tomando los primeros 8
valores (0
a 7
) de e_k_xor_pin
(que es la clave de AES cifrada y ofuscada) y enviando k
. Para el resto de índices (8
a 127
), enviamos k * k
. Aquí entra en juego la propiedad homomórfica. Se tiene
$$ k \cdot k = \mathcal{E}(b) \cdot \mathcal{E}(b) = \mathcal{E}(b \oplus b) = \mathcal{E}(0) $$
Entonces, la función de descifrado $\mathcal{D}: \mathbb{Z}/n\mathbb{Z} \to \{0, 1\}$ se comporta como
$$ \mathcal{D}(k \cdot k) = \mathcal{D}(\mathcal{E}(0)) = 0 $$
Como resultado, solo 8 bits contendrán la clave de AES relevante, mientras que el resto de los bits serán parte de la “Zero key” ($0 \oplus \mathtt{obf.pin}$). Entonces, podemos hacer un poco de fuerza bruta (de 0
a 255
) para encontrar el byte de la clave de AES.
Si ejecutamos el código anterior, obtendremos el primer byte de la clave de AES (\x13
):
$ python3 solve.py 104.248.174.109:32511
[+] Opening connection to 104.248.174.109 on port 32511: Done
[+] PIN seed: 114
[.....\..] Sending key index: 127
[+] OBF pin: [1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1]
[*] Zero key: b'\xc9\xbc\xeeq\x12\x98\xdf\x7f9K\xb4\x046\xc1R\x1b'
13
[*] Closed connection to 104.248.174.109 port 32511
Vamos a automatizarlo:
key = []
prog = log.progress('AES key')
for j in range(16):
io.recvuntil(b'Awaiting for encryption key\n\n')
for i, k in enumerate(e_k_xor_pin):
index_prog.status(str(i))
io.sendlineafter(b'> ', str(k if j == i // 8 else k * k).encode())
io.sendlineafter(b'AES Encrypt\n\n> ', b'1')
enc_data = bytes.fromhex(io.recvline().decode().strip())
for k in range(256):
test_key = zero_key[:j] + bytes([k]) + zero_key[j + 1:]
if note.encode() in aes_decrypt(test_key, enc_data):
key.append(k)
prog.status(bytes(key).hex())
break
index_prog.success()
prog.success(bytes(key).hex())
Finalmente, si todo funciona bien, la clave debe ser válida para descifrar la flag:
with open('sensitive_data/flag.txt.IBKFZ', 'rb') as f:
flag = aes_decrypt(bytes(key), f.read())
log.success('Flag: ' + unpad(flag, 16).decode())
Flag
Vamos a ejecutarlo:
$ python3 solve.py 104.248.174.109:32511
[+] Opening connection to 104.248.174.109 on port 32511: Done
[+] PIN seed: 114
[+] Sending key index: Done
[+] OBF pin: [1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1]
[*] Zero key: b'\xc9\xbc\xeeq\x12\x98\xdf\x7f9K\xb4\x046\xc1R\x1b'
[+] AES key: 13ac6164903991ced058cd2d91c36809
[+] Flag: HTB{d4n9320u5_p20p327135_c4n_134d_70_d3c2yp71n9_3v32y7h1n9}
[*] Closed connection to 104.248.174.109 port 32511
El script completo se puede encontrar aquí: solve.py
.