Optimus Prime

Desafío

En este desafío tenemos un servidor que nos da las siguientes opciones:

pantallazo opciones servidor

De la 1 a la 3 no nos entregan información útil, pero la 4 nos da lo siguiente:

información opción 4 servidor

Un par llave, texto encriptado. Si se está usando RSA “de libro” no podemos hacer mucho 😢. Al final de este archivo hay un código base que les puede servir para resolverlo

Solución

Si tuviéramos algo como phi(n) podríamos desencriptarlo.

Lo primero que debemos notar es que si volvemos a mandar 4, el servidor nos responde con otra cosa. Como sabemos que el mensaje a encriptar es el mismo, lo único que ha cambiado es la llave.

Después de mucho probar cosas, probamos corriendo miller_rabin en la llave. Con eso tenemos nuestro primer paso: podemos intentar encontrar sus factores para obtener phi(n).

Como ya sabemos, encontrar los factores de un número grande es muy caro computacionalmente. Así que probamos otra cosa: ¿qué pasa si las llaves que nos manda el servidor tienen un factor en común?. Probamos con la función GCD, y nos entrega lo que buscamos. Nota: esta parte es pura “suerte”, y al final hay que estar probando distintas cosas hasta que nos damos cuenta de eso

Con eso, encontramos un factor de la segunda llave. Luego, podemos ver que ese factor sí es primo usando miller_rabin de nuevo. Podemos definir entonces p = GCD(key1, key2) y q = key2 // p.

Después de eso, podemos calcular d usando pow(e, -1, phi), y sacar la contraseña usando la desencriptación de RSA. El único dato que no tenemos para esto es e, pero lo sacamos probando valores comunes.

Finalmente, mandamos la contraseña desencriptada al servidor (sin haber cerrado el socket con el que recibimos la segunda llave), y obtenemos la flag.

Anexo

Código base

#!/usr/bin/python3
import socketserver
from Crypto.Util.number import GCD
from Crypto.Util.number import long_to_bytes
from Crypto.PublicKey import RSA
import os
import secrets

import socket

# Acá colocamos los datos IP (o dominio) y puerto de la máquina con el challenge.
HOST, PORT = ("host", 1)

def receive_message(sock, message_length, endwith='\n'):
    """
    Recibe un mensaje de largo message_length del servidor, o hasta que el
    mensaje termina en endwith
    """
    received_message = ""
    while len(received_message) < message_length:
        if len(received_message) > 0 and received_message.endswith(endwith):
            break 
        received_part = sock.recv(message_length - len(received_message))
        received_message += received_part.decode("utf-8")
    return received_message


def receive_key_and_encrypted_pass(sock):
    # Implementar!
    pass


def miller_rabin(n, k):
    """
    Performs a Miller-Rabin primality test k times over n,
    Using the pseudo code from Wikipedia.
    We need this function to check if our big random numbers are primes or not.
    (A classic sieve is too slow for our magnitudes (~2048 bits)
    :param n: number to check
    :param k: number of times to make the check
    :return true if number is probably prime, false otherwise:
    """
    if n == 1:
        return False
    elif n % 2 == 0:
        return n == 2  # return true only if n is pair and 2, if it is pair and not 2 return false
    elif n == 3 or n == 5:
        return True
    d = n - 1
    r = 0
    while d % 2 == 0:
        r += 1
        d >>= 1
    # now n = 2^r * d
    for i in range(k):
        a = secrets.randbelow(n - 5) + 2  # a \in [2, n-2]
        x = pow(a, d, n)
        if x == 1 or x == n - 1:
            continue
        for j in range(r-1):
            x = pow(x, 2, n)
            if x == 1:
                return False
            if x == n - 1:
                break
        else:
            return False
    return True


if __name__ == '__main__':
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((HOST, PORT))
        key1, password1 = receive_key_and_encrypted_pass(s)
        s.close()
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((HOST, PORT))
        key2, password2 = receive_key_and_encrypted_pass(s)

Editar en GitHub Modificado por última vez el 26/04/2021 a las 20:45:22 hrs.