Contenido principal

Esteganografia en imágenes PNG: Chunks

Junio 14, 2009

Primero que todo, pido disculpas a mis lectores por no actualizar la página casi en dos meses, la razón principal fue el estudio, aparté un rato todo lo que tenía que ver con la informática, pero ahora estoy devuelta con este artículo sobre como ocultar información en los archivos PNG sin dañar su imagen, esta vez se trata de los Chunks, muchas gracias a Hecky por este aporte. Les quedo debiendo el binario que se menciona en este tutorial.
Este artículo también se encuentra en versión PDF para descargar: Esteganografía en PNG - Chunks.pdf
Así mismo pueden ver las anteriores colaboraciones en esta página: Colaboraciones, y si quieres colaborar con nuestra página, puesde ver este enlace!

Tutorial Chunks

Bien, aquí vamos este tipo de esteganografía se realiza en imágenes formato PNG en algo llamado chunks que son como "pedazos" de espacio que hay en la cabecera del PNG.

Todos sabemos que si intentamos meter texto en la cabecera de un PNG este se va a dañar, porque estamos modificando bytes alborotadamente, pero hay un método con el que podemos lograr esto. Aprovechando los chunks y dejando la imagen intacta. Y Antes que nada agradezco a Mighty-D por orientarme en esta técnica. Asi que pasemos a la acción.

:arrow: Mi imagen PNG original es esta:

:arrow: Y la cabecera se ve así:

:arrow: Ahora para poder meter información en la cabecera necesito un programita que me deje meter lo que quiera en esos chunks. (Más info de chunks del formato png aqui http://www.w3.org/TR/PNG/#12Chunk-processing). Para lograr el código haremos uso de la librería main Libpng (http://www.manpagez.com/man/3/libpng/). Y el código queda algo así:

/*
 * Copyright 2002-2008 Guillaume Cottenceau.
 *
 * This software may be freely redistributed under the terms
 * of the X11 license.
 *
 */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#define PNG_DEBUG 3
#include <png.h>
void abort_(const char * s, ...)
{
 va_list args;
 va_start(args, s);
 vfprintf(stderr, s, args);
 fprintf(stderr, "\n");
 va_end(args);
 abort();
}
int x, y;
int width, height;
png_byte color_type;
png_byte bit_depth;
png_structp png_ptr;
png_infop info_ptr;
int number_of_passes;
png_bytep * row_pointers;
void read_png_file(char* file_name)
{
 char header[8]; // 8 is the maximum size that can be checked
 /* open file and test for it being a png */
 FILE *fp = fopen(file_name, "rb");
 if (!fp)
  abort_("[read_png_file] File %s could not be opened for reading", file_name);
 fread(header, 1, 8, fp);
 if (png_sig_cmp(header, 0, 8))
  abort_("[read_png_file] File %s is not recognized as a PNG file", file_name);
 /* initialize stuff */
 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
 
 if (!png_ptr)
  abort_("[read_png_file] png_create_read_struct failed");
 info_ptr = png_create_info_struct(png_ptr);
 if (!info_ptr)
  abort_("[read_png_file] png_create_info_struct failed");
 if (setjmp(png_jmpbuf(png_ptr)))
  abort_("[read_png_file] Error during init_io");
 png_init_io(png_ptr, fp);
 png_set_sig_bytes(png_ptr, 8);
 png_read_info(png_ptr, info_ptr);
 width = info_ptr->width;
 height = info_ptr->height;
 color_type = info_ptr->color_type;
 bit_depth = info_ptr->bit_depth;
 number_of_passes = png_set_interlace_handling(png_ptr);
 png_read_update_info(png_ptr, info_ptr);
 /* read file */
 if (setjmp(png_jmpbuf(png_ptr)))
  abort_("[read_png_file] Error during read_image");
 row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * height);
 for (y=0; y<height; y++)
  row_pointers[y] = (png_byte*) malloc(info_ptr->rowbytes);
 png_read_image(png_ptr, row_pointers);
        fclose(fp);
}
void write_png_file(char* file_name)
{
 /* create file */
 FILE *fp = fopen(file_name, "wb");
 if (!fp)
  abort_("[write_png_file] File %s could not be opened for writing", file_name);
 /* initialize stuff */
 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
 if (!png_ptr)
  abort_("[write_png_file] png_create_write_struct failed");
 info_ptr = png_create_info_struct(png_ptr);
 if (!info_ptr)
  abort_("[write_png_file] png_create_info_struct failed");
 if (setjmp(png_jmpbuf(png_ptr)))
  abort_("[write_png_file] Error during init_io");
 png_init_io(png_ptr, fp);
 /* write header */
 if (setjmp(png_jmpbuf(png_ptr)))
  abort_("[write_png_file] Error during writing header");
 png_set_IHDR(png_ptr, info_ptr, width, height,
       bit_depth, color_type, PNG_INTERLACE_NONE,
       PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
 png_write_info(png_ptr, info_ptr); /**************************************************************************************
          Unas pocas líneas para hacer la magia de esconder informacion en chunks!
***************************************************************************************/
        png_unknown_chunk stego[5]; /* Se permiten hasta cinco contenidos */
        stego[0].data = (unsigned char *)"Este es un mensaje esteganografiado en la CABECERA en chunks por hecky para sinfocol";
        stego[0].size = 84;
        stego[0].location = PNG_HAVE_IHDR;        
        png_set_unknown_chunks(png_ptr, info_ptr, stego, 1);
        png_write_chunk(png_ptr, "hecky",stego[0].data,stego[0].size);
 /* write bytes */
 if (setjmp(png_jmpbuf(png_ptr)))
  abort_("[write_png_file] Error during writing bytes");
 png_write_image(png_ptr, row_pointers);
 /* end write */
 if (setjmp(png_jmpbuf(png_ptr)))
  abort_("[write_png_file] Error during end of write");
 png_write_end(png_ptr, NULL);
        /* cleanup heap allocation */
 for (y=0; y<height; y++)
  free(row_pointers[y]);
 free(row_pointers);
        fclose(fp);
}
void process_file(void)
{
/*
  Este espacio puede usarse para procesar el archivo, por ejemplo para hacer deteccion de bordes!
 if (info_ptr->color_type != PNG_COLOR_TYPE_RGBA)
  abort_("[process_file] color_type of input file must be PNG_COLOR_TYPE_RGBA (is %d)", info_ptr->color_type);
 for (y=0; y<height; y++) {
  png_byte* row = row_pointers[y];
  for (x=0; x<width; x++) {
   png_byte* ptr = &(row[x*4]);
   printf("Pixel at position [ %d - %d ] has the following RGBA values: %d - %d - %d - %d\n",
          x, y, ptr[0], ptr[1], ptr[2], ptr[3]);
   /* set red value to 0 and green value to the blue one
   ptr[0] = 0;
   ptr[1] = ptr[2];
  }
 }
*/
}
int main(int argc, char **argv)
{
 if (argc != 3)
  abort_("Usage: program_name <file_in> <file_out>");
 read_png_file(argv[1]);
 process_file();
 write_png_file(argv[2]);
        return 0;
}

:arrow: Éste código esta en C# y debe ser compilado en un sistema *nix, ahora para eso yo usaré un entorno virtualizdo de BackTrack3 porque no tengo ninguna distro en mi ordenador real.

:arrow: En el codigo en C# ya se puso el texto que se quiere ocultar, en esta sección:

stego[0].data = (unsigned char *)"Este es un mensaje esteganografiado en la CABECERA en chunks por hecky para sinfocol";
        stego[0].size = 80;
        stego[0].location = PNG_HAVE_IHDR;

En stego data; se pone el texto a ocultar
En stego size; se define el tamaño
En stego location; se define donde aparecerá la ubicación del mensaje, en ese caso se puso después de la cabecera IHDR, pero también se puede poner antes o despues de IDAT

:arrow: Así que lo que haré será, a ese código ponerlo en un block de notas y lo guardo como "codigo.c" la extencion debe ser ".c"
http://www.sinfocol.org/archivos/2009/06/icono_codigo.png

:arrow: Ahora lo compilamos, para compilarlo usaremos estos comandos en la shell:

gcc -lpng codigo.c -o png

gcc: Es el compilador de C
-lpng: Dice que usará la librería libpng para compilar
codigo.c: Es nuestro código que hicimos
-o png: Dice que el programa resultado se llamará png

Y nos dará como resultado "png" como se puede observar:

:arrow: Ahora por fin, solo basta conseguir la imagen PNG (Una condición que exige el código es que esta imagen debe ser RGBA, esto es debe contener tanto los canales comúnes Rojo, Verde y Azul, como el canal de transparencia Alfa). Y en la shell pondremos el siguiente comando:

png imagen.png resultado.png

png: Es el programa que compilamos
imagen.png: Es la imagen a la que le meteremos el mensaje
resultado.png: Es el resultado con el mensaje

Y nos queda así:

:arrow: Ahora solo abramos resultado.png

Y con el editor hexadecimal vemos la cabecera:

Como vemos, la imagen se ve igual, no perdió su integridad, pero adentro de la cabecera esta el mensaje. Después de IHDR y antes de IDAT

Bueno espero que sea de su agrado... En este tuto para mi maestro HaDeS de sinfocol.org, en muestra de agradecimiento a todo lo que me a enseñado. Gracias!!!

Saludos
Hecky ;)

El código fuente en C para aplicar la esteganografía sobre los chunks en la imagen PNG es: estegano_chunks.png

Archivado en: Esteganografía |

15 comentarios

  1. hecky Junio 15, 2009 @ 3:36 pm

    Gracias por colgar el tutorial maestro.

    p.d.Maestro haber si cuando tenga un tiempecito me puede ayudar con mi duda en la solucion al reto1 Gracias

    Saludos ;)

  2. servant Junio 26, 2009 @ 3:46 am

    Muchisimas gracias, yo estaba esperando este articulo desde hacia varios dias (y)

  3. Sluger Agosto 21, 2009 @ 10:21 pm

    Hola, bueno mira te pido, mas bien te suplico, y te agradeceria mucho si me dieras unos cursitos de la libreria libpng.... esque el tutorial de la pagina oficial es una mierda ademas de que esta en ingles xD, bueno ahora no estoy muy interesado en esto de la esteganografia pero mi interes es porque quiero programar un soft ;)

  4. 3elow3it Septiembre 25, 2009 @ 11:06 pm

    de veraz que esta buenisimo esto bro mil gracias por el tuto.......

    SALU2

  5. Ingphillip Diciembre 21, 2009 @ 11:50 am

    Hola, al parecer sabes mucho de png, mira lo que sucede es que estoy creando una pab web en ASP.NET de practica. En esta pagina muestro varias imagenes en png en un rompecabezas no se si me entiendas, entonces como estas imagenes están juntas los fondos de transparencia se confunden y aveces si posiciono el mouse sobre una imagen el tooltip que aparece es el de otra imagen que esta al lado, entonces, quisiera saber si hay alguna forma de capturar el canal RGB y omitir el canal alfa en una imagen png. gracias porfa escribeme al correo.

  6. Sysroot Diciembre 22, 2009 @ 1:50 am

    No hay que saber mucho de imágenes para "arreglar" el problema que tenes, mas bien hay que saber de hojas de estilo en cascada (CSS), específicamente la propiedad z-index que se utiliza para ordenar los elementos que se muestran en la página. Si queres hacer que una imagen aparezca delante de otra debes especificar un z-index mayor para esa imagen.

    Podes probarlo con un código similar a este:

    <img src="imagen1.png" alt="" style="z-index: 5;" />
    <img src="imagen2.png" alt="" style="z-index: 2;" />
    <img src="imagen3.png" alt="" style="z-index: 10;" />

    El mayor z-index indica el primer elemento que se va a mostrar, el menor es el último.

    No estoy muy seguro (Con respecto a la pregunta en sí), pero creo que no se puede, porque aunque la imagen sea un poco transparente ella va a estar ahí y el foco (Cuando des clic o dejes el cursor sobre la imagen) lo va a tomar es la imagen que esté en el orden superior, quizás con los avances en javascript se pueda hacer algo, pero desconozco algún método para omitir el canal alfa, intenta con la propiedad z-index, creo que va a funcionar de maravilla.

    Un ejemplo para que pruebes online: http://www.w3schools.com/Css/tryit.asp?filename=trycss_zindex

  7. logslie Enero 20, 2010 @ 7:29 am

    Hola, gracias por explicarlo todo tan bien, me ha sido de gran ayuda.

    Tengo un problema, al compilar el código exactamente como el tuyo, me da el siguiente error:
    cc -lpng -o dist/Debug/GNU-Linux-x86/png build/Debug/GNU-Linux-x86/main.o
    build/Debug/GNU-Linux-x86/main.o: In function `read_png_file':
    /home/logslie/NetBeansProjects/png/main.c:64: undefined reference to `png_set_longjmp_fn'
    /home/logslie/NetBeansProjects/png/main.c:77: undefined reference to `png_set_longjmp_fn'
    collect2: ld returned 1 exit status

    Sabes a qué se puede deber?

    Gracias, un saludo.

  8. Sysroot Enero 20, 2010 @ 11:22 pm

    Lo único que puedo decir es: tienes la librería para el manejo de archivos png?
    Porque viendo las líneas y los errores, no encuentro algunas cadenas que pusiste en el código que mostramos.

  9. logslie Enero 21, 2010 @ 6:15 am

    Sí, instalé el lipbng1.4.
    Éste es mi código:
    /*
    * File: main.c
    * Author: logslie
    *
    * Created on 12 de enero de 2010, 12:50
    */

    #include
    #include
    #include
    #include
    #include
    #define PNG_DEBUG 3
    #include
    #include

    /*
    *
    *
    */
    void abort_(const char * s,...)
    {
    va_list args;
    va_start(args, s);
    vfprintf(stderr, s, args);
    fprintf(stderr, "\n");
    va_end(args);
    abort();
    }

    int x, y ;
    int width, height;
    png_byte color_type;
    png_byte bit_depth;
    png_structp png_ptr;
    png_infop info_ptr;
    int number_of_passes;
    png_bytep * row_pointers;
    void read_png_file(char* file_name){

    char header[8];
    FILE *fp = fopen(file_name, "rb");
    if (!fp)
    abort_("[write_png_file] png_create_write_struct failed");

    fread(header, 1, 8, fp);

    if(png_sig_cmp(header, 0, 8))
    abort_("[read_png_file] File %s is not recognized as a PNG file", file_name);
    /* initialize stuff */

    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);

    if(!png_ptr)
    abort_("[read_png_file] png_create_read_struct failed");

    info_ptr = png_create_info_struct(png_ptr);

    if(!info_ptr)
    abort_("[read_png_file] png_create_info_struct failed");
    if (setjmp(png_jmpbuf(png_ptr)))
    abort_("[read_png_file] Error during init_io");

    png_init_io(png_ptr, fp);
    png_set_sig_bytes(png_ptr, 8);
    png_read_info(png_ptr, info_ptr);
    width = info_ptr->width;
    height = info_ptr->height;
    color_type = info_ptr->color_type;
    bit_depth = info_ptr->bit_depth;
    number_of_passes = png_set_interlace_handling(png_ptr);
    png_read_update_info(png_ptr, info_ptr);

    if(setjmp(png_jmpbuf(png_ptr)))
    abort_("[read_png_file] Error during read image");

    row_pointers = (png_bytep*) malloc(info_ptr->rowbytes);
    for(y=0; yrowbytes);
    png_read_image(png_ptr, row_pointers);
    fclose(fp);

    /* read file */

    }

    int main(int argc, char** argv) {
    if(argc != 2)
    abort_("Usage: program_name ");
    read_png_file(argv[1]);
    return 0;
    }

  10. logslie Enero 21, 2010 @ 6:19 am

    En el código aunque no salgan las librerías, en el mío real sí, es que no copia el texto de los import.

  11. logslie Enero 21, 2010 @ 6:21 am

    digo,de los include, que tengo una mezcla de lenguajes de programación que ya no sé ni lo que digo jajajaja

  12. logslie Enero 22, 2010 @ 5:08 am

    Solucioné el problema, era por un conflicto en compatibilidad de versiones del libpng.

    Gracias, un saludo

  13. Sysroot Enero 23, 2010 @ 1:53 pm

    Ok, gracias por la información!

  14. logslie Enero 25, 2010 @ 12:35 pm

    Hola, tengo una duda. Si yo quisiera ver el contenido de un pixel, cuál es su color, cómo puedo obtener esa información a través del row_pointer?

    Mi idea es ir recorriendo la imagen pixel a pixel y en función de su color hacer una cosa u otra.

    Un saludo, gracias

  15. Sysroot Enero 25, 2010 @ 1:42 pm

    Hola, encontré un ejemplo que te puede servir de mucho: http://blog.hammerian.net/2009/reading-png-images-from-memory/

    Allí encontrarás una función llamada ParseRGBA, así que es seguro lo que estas buscando.

Deja un comentario