HackIt! 2014 _ Level 3

poor_mans_stego_ nyan0verflow, «No siempre todo es lo que parece. Y lo que aparece no es siempre el todo». Con la foto de un gato (no es nyan cat 😉 y el texto anterior empieza el tercer reto del HackIt! Atendiendo al nombre de la imagen, a simple vista parece que han usado algún algoritmo de esteganografía (¿simple?). Probando con outguess y steghide, no parece que saquemos nada en claro.

Aquí Joserra sacó un truco de su chistera. Si abres el jpg en un editor (vim, por ejemplo) y eliminas al azar algunas líneas (entre 3 y 5), grabas y abres el jpg, verás que el gráfico esconde unas líneas más de las que se muestran sin hacer ningún cambio (poor man’s stegano 😉 Si sigues eliminando, verás el mensaje oculto:

poor_mans_stego

Dos cosas: primera, como veis, la resolución fue de chiripa. Segunda: realmente lo que está ocurriendo es que el jpg que nos pasan tiene codificada en las cabeceras una altura distinta a la que realmente ocupa la imagen. Modificando con un editor hexadecimal la altura de la imagen (0xc9 = 201) y añadiéndole algunos pixels más (por ejemplo, cambiando el 0xc9 por un 0xf0), obtendremos una forma más ortodoxa de pasarse el nivel 🙂

7 comentarios en «HackIt! 2014 _ Level 3»

  1. Esta nos salió bastante rápido porque habíamos visto algunas pruebas similares en CTFs.

    Como nos indicó a posteriori @marcan42, si además de aumentar el número de filas aumentas el de columnas se vé por qué el nombre de la prueba ;-).

    El caso es que como de costumbre en las pruebas de marcan, hay una sutileza en esta prueba. Según nos contó, aunque estaría bien que se pasara por aquí y lo explicara bien, no es simplemente que haya más datos y los veamos al aumentar el número de filas, como ocurría en las pruebas que habíamos visto en otros CTFs, sino que explota la forma de renderizar los jpg y el hecho de que se codifiquen en bloques de 8×8. Lo dicho, a ver si se pasa y nos lo explica bien…

  2. En realidad la explicación es simple: JPEG se codifica en bloques de 8×8 (el tamaño de la transformada DCT que usa). Cuando las dimensiones no son múltiplo de 8 (o de 16 si el color está submuestreado, pero entonces se complica la cosa un poco) entonces se añade padding antes de codificar la imagen. Dicho padding es, normalmente, una copia de la última fila o columna de píxeles (por aquello de evitar transiciones bruscas que se comprimen peor, como ocurriría si fuera simplemente negro). En este caso, la imagen necesita 7 píxeles de padding en cada dimensión, pero el padding no es tan simple 🙂

    Por lo tanto, la imagen es, de hecho, un JPEG normal y corriente, con las dimensiones correctas. La única sutileza es que los datos de padding son distintos a los que normalmente se usan (y la forma correcta de verlo es sumarle 7 a la anchura y la altura de la cabecera – más no, ya que entonces sí intentarías descodificar bloques que no existen). También es por esto por lo que veis una línea negra inferior en la imagen corrupta: esa fila de píxeles corresponde a la siguiente fila de bloques, que no existen, y por lo tanto el decodificador los deja en negro. Por cierto, el nivel lo hice simplemente recortando la imagen del tamaño original con jpegtran (que no modifica los bloques, y por lo tanto mantiene el padding con los datos originales).

    Eso sí, he de decir que me ha molado lo de resolverlo cargándose el JPEG a lo bestia con vim. La semana pasada he andado trasteando con JPEGs en el curro y también tuve unos bonitos fails… (el proyecto en cuestión, que por cierto es open source)

    Y ahora una divagación sobre las entrañas del formato JPEG. ¿Sabéis por qué, al corromper un JPEG de esta manera, cambia de forma extraña el color de los bloques que siguen al error, pero los detalles se mantienen perfectamente? Como se puede ver, el formato JPEG es muy tonto, comparado con por ejemplo los formatos modernos de compresión de vídeo. Tan tonto que si lo corrompes, la mayoría de los datos sobreviven. Tan tonto que no realiza predicción de los datos basados en datos anteriores – cada bloque se codifica de forma independiente. Bueno, casi. Con una excepción: el coeficiente 0,0 de cada bloque, es decir, el de DC, frecuencia cero, o lo que es lo mismo, la media del color del bloque, se codifica relativo al del bloque anterior: la diferencia. Si corrompemos un bloque, entonces todos los sucesivos tienen un offset incorrecto en ese componente, y por lo tanto una desviación en su color (o brillo, o ambos). Dicha desviación se mantiene constante hasta el final de la imagen (o el siguiente error).

    1. «En realidad la explicación es simple»… cada vez que oigo/leo este comienzo de frase en marcan me echo a temblar 🙂 No, ahora en serio, gracias por la explicación! aunque para entenderla y asimilarla bien, en mi caso necesitaré hacer unas pruebas y leer más sobre cómo se codifica un JPEG. Me vendrá bien para próximos retos.

  3. A mí me recordó a aquella prueba que hizo topo[lb] hace unos años, en los que usaba un jpg para encriptar unos textos, y reconstruías parte del jpg. De hecho lo primero que pensé fue en revisar la integridad del jpg.. vuelta a repasar la estructura. Y la pista en la imagen me hacía suponer que tenía de algún modo datos ocultos que no se estaban viendo (un análisis/histograma a la paleta de colores no revelaba nada extraño).

    En mi caso inicialmente cambié tanto el ancho como el alto (0xFF p0wah!!), y ya se veía el mensaje, aunque debido al ancho que le había indicado se veía la imagen y el mensaje desalineados.

    crAck & prAy!

    1. @thePopE ! Esa prueba de topo[lb] no la recuerdo… igual es que no llegamos a ella O:-) Oye, ya que te tengo por aquí, aprovecho para pedirte un favor: ¿podrías publicar un write-up de tus pruebas en el SolveIt? Creo que se me quitaron las ganas de comer lacasitos durante unos años 😛

Responder a amok Cancelar la respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.