sábado, 3 de agosto de 2019

Criptografía (CLVII): Solución Reto picoCTF 2018 "James Brahm Returns"

Solución a otro reto de la plataforma picoCTF 2018 sobre criptografía. Este desafío es, supuestamente, el más complicado de los catalogados en dicha categoría, y, en mi opinión, así es, por lo que le voy a asignar un nivel de dificultad muy alto ().

James Brahm Returns - Points: 700:

Su enunciado dice lo siguiente: '
Dr. Xernon has finally approved an update to James Brahm's spy terminal. (Someone finally told them that ECB isn't secure.) Fortunately, CBC mode is safe! Right? Connect with nc 2018shell.picoctf .com 14263Source'.
Solución: en el enunciado se deja claro que en este desafío también se ve involucrado el modo de operación CBC ('Cipher Block Chaining') de los algoritmos de cifrado por bloques y, efectivamente, en el código fuente que se proporciona (source.py) veo que se utiliza el algoritmo AES en dicho modo.

En este desafío, para identificar el ataque a realizar y, por tanto, poder resolverlo, es fundamental la pista que se da en la pestaña 'Hints' ('What killed SSL3?'). Busco en 'Google', literalmente, el texto de dicha pista y obtengo como primer resultado 'SSL 3 is dead, killed by the POODLE attack', por lo que ya tengo identificado el ataque, pero me toca investigar más sobre el mismo.

Tal y como nos cuenta wikipedia, el ataque POODLE (cuyo nombre no viene de caniche :), sino de'Padding Oracle On Downgraded Legacy Encryption') es un 'exploit' 'man-in-the-middle' que aprovecha Internet y la característica del software de clientes para forzar la bajada a SSL 3.0 con objeto explotar una vulnerabilidad en su diseño y poder así descifrar criptogramas. Además, existe también una variante de este ataque que explota las vulnerabilidades de implementación del modo de cifrado CBC en los protocolos TLS 1.0 al 1.2 sin necesidad de rebajar los clientes a SSL 3.0.

Antes de comenzar a intentar resolver el reto, me conecto al servicio; se me da la bienvenida como agente 006 y se me pide que: elija entre cifrar un mensaje, y enviar y verificarElijo la primera de ellas ('Encrypt message (E)'): se me pide que introduzca mi informe de situación, incluyo cualquier cosa, por ejemplo: 'My situation report', y se me muestra un mensaje cifrado:
Conforme al código fuente que se proporciona en el reto (source.py), el criptograma que se muestra se obtiene cifrando lo siguiente:
Es decir: el mensaje a cifrar se compone de un formato preestablecido, en el que se incluyen el informe de situación introducido y el código de agente, más lo introducido cuando se pregunta si se desea incluir alguna cosa más ('Anything else?'), más el resultado de calcular el hash sha1 de la concatenación de ambas cosas anteriores. Después se produce el relleno del mensaje y se cifra el resultado de todo ello mediante AES en modo CBC.

Ahora utilizo la segunda opción ('Send & Verify (S)'): se me pide que introduzca el mensaje cifrado (copio y pego el que se me ha mostrado cuando he utilizado la primera opción) y se me informa de que el descifrado se ha realizado con éxito.
Pruebo otra vez la segunda opción, pero ahora sólo incluyo una parte del mensaje cifrado anterior y se muestra un mensaje de error:
Acudo otra vez al código fuente que se proporciona en el reto (source.py) y veo que ese mensaje de error se muestra tanto si el relleno es inválido como si falla la verificación MAC ('Message Authentication Code'), es decir, si la validación del hash es incorrecta:
Por tanto, entiendo que en este caso un ataque 'padding oracle' como tal no funcionaría, ya que para que tenga éxito se necesita conocer con precisión cuando el relleno es incorrecto y cuando es válido, por lo que, tal y como se indica en la pista que se da, creo que hay que utilizar el ataque POODLE.

Como en el caso de SSL 3.0, el servicio al descifrar sólo mira el último Byte cuando comprueba el relleno, y elimina tantos Bytes como éste indique. Por ejemplo: si el último Byte es \x10 (16 en decimal) elimina 16 Bytes, es decir un bloque completo. 
¿Cómo puedo utilizar esto para implementar el ataque y obtener la solución del reto?. Explico brevemente lo que he entendido (por favor, como siempre, agradecería que si no lo he comprendido bien y/o no lo explico correctamente, se realicen las correcciones y/o ampliaciones de información oportunas en forma de comentarios a esta entrada).

1.- Obtengo un criptograma con un bloque completo de relleno. ¿Cómo hago esto?. Pues voy probando con un informe de situación con diferentes longitudes para el mismo hasta que con una determinada longitud obtenga un criptograma de un tamaño de 16 Bytes más que con la longitud anterior. Por ejemplo: probando con un informe de situación cumplimentado únicamente con caracteres "a"; en primer lugar introduzco "a", después "aa", posteriormente "aaa", y así sucesivamente hasta obtener con un cierto número concreto de caracteres "a" un criptograma de tamaño 16 Bytes mayor que el precedente.

En nuestro caso, tal y como se observa en la figura siguiente, veo que esto ocurre cuando introduzco 14 caracteres "a"; la longitud del criptograma que obtengo es de 208 Bytes frente a 192 Bytes en el caso de incluir 13 caracteres "a":

Como en el reto se proporciona la estructura del criptograma, e incluso parte del texto en claro, y, además, se puede incluir texto antes y después de la parte del mismo que me interesa descifrar, puedo obtener un criptograma que me permita ir directamente a descifrar dicha parte.

2.- En base a lo anterior, 14 caracteres "a" como informe de situación, y uilizando "\" para indicar el salto de línea, divido el texto en claro en bloques:

P1 = Agent,\Greetings
P2 = . My situation r
P3 = eport is as foll
P4 = ows:\aaaaaaaaaaa
P5 = aaa\My agent ide
P6 = ntifying code is
P7 = : xxxxxxxxxxxxxx
P8 = xxxxxxxxxxxxxxx.
P9 = \Down with the S
P10 = oviets,\006\????
P11 = ????????????????
P12 = ????????????????

Los caracteres "x" se corresponderían con la flag y, por tanto, la solución de este reto (el código de identificación del agente 006 que tengo que robar) y que, lógicamente, entiendo que tiene el formato estándar de la plataforma (picoCTF{...}), mientras que los últimos caracteres "?" se corresponderían con el hash sha1 (20 Bytes) más el relleno (16 Bytes, último bloque completo).

3.- Incluyo caracteres "a" antes de los caracteres "x" hasta conseguir que el primero de estos últimos se encuentre al final de un bloque de texto plano (como más adelante se verá el número de caracteres "a" que finalmente se hallen en esta posición debe ser mayor que el número de caracteres "x"), y para mantener el bloque de relleno completo introduzco tanto caracteres "a" como sean necesarios para ello después de los caracteres "x":

P1 = Agent,\Greetings
P2 = . My situation r
P3 = eport is as foll
P4 = ows:\aaaaaaaaaaa
P5 = aaaaaaaaaaaaaaaa
P6 = aaaaaaaaaaaaaaaa
P7 = \My agent identi
P8 = fying code is: x
P9 = xxxxxxxxxxxxxxxx
P10 = xxxxxxxxxxxx.\Do
P11 = wn with the Sovi
P12 = ets,\006\aaa????
P13 = ????????????????
P14 = ????????????????

4.- Obtengo el criptograma correspondiente a un reporte de situación (43 caracteres "a") y a la información adicional (3 caracteres "a" cuando el servicio pregunte: 'Anything else?'):
Es decir, en este caso:

C0 = 97ca0fcf7499260f6a9094db0046fb93; [Vector de inicialización] 
C1 = c608d1f7b3f44879f0860a227c5affc3P1 = Agent,\Greetings
C2 = f77515621dc87ef663e837e608406fd6P2 = . My situation r
C3 = df58770a01e2fc9058184d0f13ed80adP3 = eport is as foll
C4 = c34b0706165e4a25f1e2ab409b7d0ac3P4 = ows:\aaaaaaaaaaa
C5 = f9de61fab17ef653c4d2a3bbe0c3ad8dP5 = aaaaaaaaaaaaaaaa
C6 = 7b7a29d99fa75c0e7e4c7e08f5392c8cP6 = aaaaaaaaaaaaaaaa
C7 = 96ad4bfdb76749f8e3d5571081596861P7 = \My agent identi
C8 = 00fdf2e02996dbf2cb654fc6f24074ffP8 = fying code is: x
C9 = 1b5cc8abc9903352095e45722c5a2d38P9 = xxxxxxxxxxxxxxxx
C10 = 31e30758758e1dfadcb83fcdb3e41022P10 = xxxxxxxxxxxx.\Do
C11 = 29b1e68993c22693b94847e51d908ba9P11 = wn with the Sovi
C12 = d313c256bf00855040ebc6e2ddac6aafP12 = ets,\006\aaa????
C13 = 9b2ece32a716ece56454641ded3f9cb8P13 = ????????????????
C14 = 24e6eeb6301a237cccfc6f1c165e9caeP14 = ????????????????

5.- Y, finalmente, cambio el bloque final de relleno (C14por el bloque de texto cifrado por el que comenzar el ataque (C8):

C0 = 97ca0fcf7499260f6a9094db0046fb93; [Vector de inicialización] 
C1 = c608d1f7b3f44879f0860a227c5affc3P1 = Agent,\Greetings
C2 = f77515621dc87ef663e837e608406fd6P2 = . My situation r
C3 = df58770a01e2fc9058184d0f13ed80adP3 = eport is as foll
C4 = c34b0706165e4a25f1e2ab409b7d0ac3P4 = ows:\aaaaaaaaaaa
C5 = f9de61fab17ef653c4d2a3bbe0c3ad8dP5 = aaaaaaaaaaaaaaaa
C6 = 7b7a29d99fa75c0e7e4c7e08f5392c8cP6 = aaaaaaaaaaaaaaaa
C7 = 96ad4bfdb76749f8e3d5571081596861P7 = \My agent identi
C8 = 00fdf2e02996dbf2cb654fc6f24074ffP8 = fying code is: x
C9 = 1b5cc8abc9903352095e45722c5a2d38P9 = xxxxxxxxxxxxxxxx
C10 = 31e30758758e1dfadcb83fcdb3e41022P10 = xxxxxxxxxxxx.\Do
C11 = 29b1e68993c22693b94847e51d908ba9P11 = wn with the Sovi
C12 = d313c256bf00855040ebc6e2ddac6aafP12 = ets,\006\aaa????
C13 = 9b2ece32a716ece56454641ded3f9cb8P13 = ????????????????
C14 = 00fdf2e02996dbf2cb654fc6f24074ffP14 = fying code is: x

6.- Y ya estoy preparado para llevar a cabo el ataque. En primer lugar voy a intentar descifrar el primer carácter "x" de la flag, que como ya he dicho antes muy probablemente sea una "p" debido al formato estándar de flag que utiliza la plataforma.

Para ello, envío al servicio el criptograma anterior: si el descifrado no se realiza correctamente (se devuelve el mensaje ''Ooops! Did not decrypt successfully. Please send again.) vuelvo al punto 4 para obtener otro criptograma, mientras que si se realiza correctamente (se devuelve el mensaje 'Successful decryption'), la única manera de que esto se produzca es si P14[16] es \x10 (16 en decimal), ya que de esta forma se eliminará un bloque completo de relleno y se pasará también con éxito la validación del hash, puedo obtener el último Byte del texto plano (P8[16]), que es el que intento descifrar, de la siguiente manera:

Dk(C8[16])  = C13[16] XOR \x10
P8[16] = C7[16] XOR Dk(C8[16])

7.- Para conseguir el siguiente Byte de la flag basta con volver al punto 4 para obtener el criptograma correspondiente a un reporte de situación compuesto por 42 caracteres "a" (uno menos que para el Byte anterior) y a la información adicional formada por 4 caracteres "a" cuando el servicio pregunte: 'Anything else?' (uno más que para el Byte anterior), y así sucesivamente hasta conseguir descifrar todos los Bytes de la solución.

Creo un script en python para automatizar este proceso. Lo ejecuto y voy obteniendo el texto plano correspondiente a la flag. Cuando finaliza puedo ver la solución a este reto:

No hay comentarios:

Publicar un comentario