¿Cuál es el mensaje oculto? (Solución)

Y bien; ¿cuál es el mensaje oculto? :-)

En principio, la imagen no parece más que ruido blanco. ¿Podría tratarse entonces de un reto de esteganografía, como el publicado hace poco? Esperamos que hayáis probado unas cuantas técnicas, pero no es el caso :-)

Un detalle curioso, si revisamos el código fuente de la página, es que la imagen la «devuelve» un script en PHP; no es un simple enlace a un fichero JPG o PNG estático. Tratándose éste de un blog sobre seguridad, y dado que lo que ahora está «de moda» es la seguridad web, sobretodo las inyecciones SQL y similares, esperamos también que hayáis intentado buscarle las cosquillas a ese foo.php. Tampoco va por ahí…

Pero el hecho de que la imagen se genere dinámicamente sí es importante. De hecho, si visitamos la URL de la imagen y le damos varias veces al botón de «refrescar» algo pasa, ¿no? ;-) De vez en cuando sale «algo» como subliminal, y los entrenados en técnicas de lectura rápida (o los aficionados a las pestañas ;-) puede que ya tengan el mensaje oculto en su poder. Pero ¿qué es exactamente lo que produce ese efecto óptico, y cómo podemos hacer que el mensaje se «quede quieto»?

Está claro que el efecto se produce durante la transición entre una imagen y otra. Además, no se aprecia siempre que le damos a «refrescar». Por lo tanto, ¿cuántas imágenes distintas hay? Una forma de averigüarlo es pedir unas cuantas imágenes, sacar sus respectivos resúmenes MD5 y ver cuántos distintos hay. Traducido a un script para el shell:

for i in `seq 1 60`; do
  wget -q https://www.securityartwork.es/wp-content/themes/default/foo.php \
    -O - | openssl md5
  sleep 1 # poco a poco!
done | sort | uniq -c

Resulta que hay dos imágenes, cuyos MD5 son:

32 08980fd7bf39a6ff0bdbf5b0344d5bee
28 a0f65ee7ec226a45b0e514f5a42ba368

Una de las imágenes, que ha aparecido 32 veces, es:

y la otra, que ha aparecido 28 veces:

Luego hay dos imágenes, y cada una aparece más o menos la mitad de las veces.

Ahora ya es cuestión de tratar de reproducir el efecto óptico observado al refrescar el PHP. Si abrimos las imágenes con un programa de retoque fotográfico, como el Gimp, podemos observar que ambas imágenes están compuestas de puntos negros y puntos transparentes. Luego de algún modo el efecto puede que sea parcialmente producido con esas transparencias. Eso, unido a que se observa cuando una de las imágenes transiciona a la otra, nos lleva a abrir una de las imágenes y abrir la otra como una capa (o lo que es lo mismo, superponerlas), obteniendo el mensaje oculto:

¿Cómo funciona? Criptografía visual

Aparte de para construir el puzzle anterior, con el que esperamos que hayais disfrutado, dos imágenes como las anteriores podrían tener utilidad en una situación similar a la siguiente.

Supongamos que el mandatario de un país es el único que conoce el código de activación de un silo de misiles (a la Juegos de Guerra). Por si se olvidara del código, decide compartir el secreto con dos amigos de confianza, pero no quiere que ninguno de ellos por separado sepa el código. A tal efecto, fabrica dos transparencias (de las de antes del Power Point; las de proyector), de forma que si se superponen muestran el código de activación (explicaremos más adelante el proceso de fabricación). Le entrega la primera transparencia a uno de los hombres, y la segunda al otro. De este modo, obviando robos, fugas de información, compromiso de la seguridad física o lógica del silo, etc. (¡es sólo un ejemplo), si en algún momento se le olvidara el código, bastaría con que visitase por separado a sus dos amigos y recuperase las transparencias.

En criptografía, un esquema como el anterior se conoce como k out of n secret sharing, que ¿nos atrevemos? a traducir como compartición de secretos entre k sobre n. De forma simplificada, en este esquema un mensaje secreto se transforma en n mensajes secretos «parciales» distintos, de forma que como mínimo hay que «reunir» la información presente en k de ellos para recuperar el mensaje secreto completo. En nuestro ejemplo, la imagen inicial con el mensaje secreto se transforma en dos imágenes, y hay que juntar las dos para recuperarlo (luego k = n = 2).

Lo interesante de la variante del esquema utilizada en nuestro puzzle es que no hace falta un ordenador para recuperar el texto en claro; basta con la capacidad «procesadora» del sistema visual humano. Por ello, sus inventores, Moni Naor y Adi Shamir (este último es la S del famoso sistema criptográfico RSA), la llamaron criptografía visual.

Veamos cómo generar las dos «transparencias» a partir de la imagen original. Partimos de una imagen PBM, en blanco y negro, con el mensaje en claro pintado; en nuestro caso «Security A(r)tWork». Esa imagen se puede ver como una matriz de MxN pixels donde cada uno de ellos es ó 0 (blanco) ó 1 (negro). La transformación consiste en sustituir cada uno de esos pixels por 4 pixels en cada una de las transparencias (de forma que las dimensiones de estas últimas son 4Mx4N 2Mx2N). Trabajamos con estas 6 posibles transformaciones:

   
   
   
   
   
   
   
   
   
   
   
   

Para cada pixel en la imagen original, en primer lugar seleccionamos al azar una de las seis anteriores transformaciones (llamémosla T). Para la primera transparencia, sustituimos el pixel original por esa transformación T. Para la segunda transparencia, si el pixel original era blanco (0), sustituimos por la misma transformación que en la primera transparencia (esto es, por T otra vez); en cambio, si el pixel era negro (1), sustituimos por el «complementario» de T (el resultado de cambiar los pixels negros por blancos, y viceversa; lo denotamos por ~T).
De esta forma, los pixels que eran blancos quedan iguales en las dos transparencias, produciendo un efecto de blanco/gris al superponerlas. En cambio, los pixels que eran negros quedan complementados, produciendo 4 pixels negros.

Éste es el código para Octave que hemos escrito para generar las imágenes del puzzle. Hay que tener en cuenta que Octave entiende un 0 como un pixel negro, y un 1 como un pixel blanco (por ello las complementaciones con el operador «~»). Una versión del código más eficiente utilizando operaciones matriciales es posible, pero pensamos que esta implementación es más intuitiva.

shares = {
  [0 0; 1 1],
  [1 1; 0 0],
  [0 1; 0 1],
  [1 0; 1 0],
  [0 1; 1 0],
  [1 0; 0 1]
};

function ret = random_share(shares)
  nshares = size(shares)(1);
  [foo, random_indexes] = sort(rand(1, nshares));
  ret = shares{random_indexes(1)};
endfunction

enclaro = imread('enclaro.pbm');
enclaro = ~enclaro;

cipher1 = [];
cipher2 = [];
for row = 1:rows(enclaro)
  cipher1_row = [];
  cipher2_row = [];
  for column = 1:columns(enclaro)
    cipher1_share = random_share(shares);
    pixel = enclaro(row, column);
    if pixel == 0
      cipher2_share = cipher1_share;
    else
      cipher2_share = ~cipher1_share;
    endif
    cipher1_row = [cipher1_row, cipher1_share];
    cipher2_row = [cipher2_row, cipher2_share];
  endfor
  cipher1 = [cipher1; cipher1_row];
  cipher2 = [cipher2; cipher2_row];
endfor

imwrite(logical(~cipher1), 'cipher1.png');
imwrite(logical(~cipher2), 'cipher2.png');

Tras generar las transparencias cipher1.png y cipher2.png, sólo resta transformar los pixels en blanco en pixels transparentes, para lo cuál hemos utilizado el Gimp. El código del script foo.php, que devuelve una u otra imagen, es el siguiente. Como ya observamos al obtener el número de ocurrencias de cada una de las imágenes, es como lanzar una moneda y devolver la primera imagen si sale cara, y la segunda si sale cruz (seguramente, jugando con estas probabilidades se podría haber aumentado la dificultad del puzzle, ya que el efecto se hubiera reproducido menos
veces).

<?php

if (rand(1, 2) == 1) {
        $img = file_get_contents('cipher1.png'); 
} else {
        $img = file_get_contents('cipher2.png');
}
header('Content-Type: image/png');
echo $img;
?>

Intuitivamente, dada una transparencia, la única forma de recuperar el mensaje en claro es mediante la otra, por lo que una puede verse como la «clave» de la otra. Además, la longitud de dicha «clave» es idéntica a la del texto cifrado, por lo que, salvo errores en la implementación, se trata de un esquema de cifrado similar al one-time pad, con seguridad perfecta (por eso haciendo un XOR de las dos imágenes se obtiene la original). Para los lectores interesados en las demostraciones formales de lo recién dicho y con ganas de profundizar en el tema de la criptografía visual, incluimos al final del post referencias al artículo original de Naor y Shamir, y a dos artículos posteriores.

Por último, quisiéramos agradecer los comentarios publicados por algunos de los participantes. ¡Esperamos que hayais disfrutado con estos dos posts!

Para saber más

(Los artículos están en formato PostScript. Pueden leerse, por ejemplo, con Ghostview.)

Comments

  1. Muy bueno Pablo ;).

  2. Lo curioso es que me acaba de llegar una carta de la ITV con una banda muy parecida. aplicando tecnicas ninja he descubierto el mensaje «p4g4,c4br0n» dentro. está claro que están utilizando un metodo de encriptado que desconozco, pero tengo la granja de GPUs trabajando en ello. cuando lo tenga lo posteo. :)
    por otro lado, muy buen post, del estilo del de Toni de los monos, en la categoría de «desconfía de lo obvio» (y yo quemandome los ojos con el Gimp..) :)

    Saludos.

  3. Muy bueno el reto Pablo ;)

    En mi caso, para resolverlo, lo hice analizando el tráfico con Wireshark y vi que a veces el contenido de la imagen era otro. Así que capturé las dos imágenes y utilizando «composite» pude obtener las diferencias entre las dos imágenes.

    La instrucción era: composite imagen1.png imagen2.png -compose difference diferencias.png

    Y así en «diferencias.png» obteníamos el mensaje oculto :)

    Saludos
    Rafa Páez

  4. Que bueno, siseñó!

    Yo me di cuenta al fundir el php a F5’s pero obviamente no pude entender hasta ahora el porqué.

  5. Me alegro de que os haya gustado, chicos.

    Disfruto leyendo vuestras ideas y soluciones tanto o más que construyendo el puzzle ;-)