GDB, shellcodes y nuestro primer exploit

Para compilar el programa name.c para este artículo es necesario desactivar DEP:

gcc -m32 -fno-stack-protector -z execstack name.c -o name

También es necesario desactivar ASLR ejecutando el siguiente comando con permisos de administrador

echo 0 > /proc/sys/kernel/randomize_va_space

GDB

Antes de proseguir donde lo dejamos en el artículo anterior es necesario repasar unas cuantas nociones de gdb.

Como se ha dicho anteriormente, gdb es el depurador de GNU, para invocarlo basta con ejecutar

gdb programa

o

gdb -q programa

Con la opción -q no nos mostrará la información inicial y ahorraremos espacio en pantalla.

Mediante la orden disas o disass podremos desensamblar el código, si no proporcionamos ningún parámetro nos desensamblará el código desde el registro EIP, si el programa no está en ejecución no nos desensamblará nada, por contra, le podemos pasar una dirección de memoria o nombre de método y nos desensamblará desde esa dirección de memoria, como segundo parámetro le podemos pasar la longitud en bytes que queremos desensamblar o la dirección final hasta la que queremos desensamblar.

disas func, +10 (desensamblaremos 10 bytes)
disas func, 0xffeeddcc (desensamblaremos hasta la dirección expecificada)

Con la orden break podremos poner una breakpoint, bien usando una dirección de memoria o un nombre de método.

Con la orden r o run seguido de argumentos (si es que nuestro programa tiene argumentos) podremos empezar a correr nuestro programa, si queremos movernos de línea en línea de ensamblador debemos usar las ordenes stepi/si o nexti/ni.

A continuación se muestra una sesión básica de gdb

0

Si queremos ver el estado actual de los registros podremos ejecutar la orden

info registers

1

A parte de disas, que sirve para desensamblar código, tenemos otra herramienta muy ponente que sirve para examinar la memoria. El comando para dicho fin es “x” seguido de la cantidad a examinar y del formato y por último la dirección de memoria. El formato que mas vamos a usar es el hexadecimal “x” y el de instrucción “i”, para más información se puede escribir help x en gdb.

Un ejemplo de este comando en ejecución puede ser el siguiente:

2

En el primer comando hemos ejecutado el comando de examinar memoria “x” con el parámetro /16x que significa que desensamble 16 direcciones en formato hexadecimal y como último parámetro le hemos pasado el registro $eip, es decir, nos va a mostrar a partir de la siguiente instrucción a ejecutar.

En el segundo ejemplo vemos que en vez de /16x le hemos pasado /10i que significa que nos muestre 10 instrucciones.

Aquí podemos ver una cosa muy interesante que nos servirá más adelante, que la memoria es un conjunto de bytes y que todo depende de como se interprete, si se interpreta como instrucción nos da el resultado del segundo “x” y si directamente no se interpreta obtenemos el conjunto de bytes del primer “x”.

Hay veces que necesitaremos saber donde están localizadas las librerías compartidas que usa nuestro programa, para ello hay 3 formas, pero una de ellas es la más completa. La primera de ellas y que personalmente no recomiendo es la herramienta de linux ldd

3

No la recomiendo porque no suele ser exacta.

Otra opción que podemos usar es ejecutar la orden info sharedlibrary en gdb

4

Para usar este comando deberemos haber ejecutado nuestro programa (run args) sino nos mostrará un error parecido al que me ha mostrado a mi.

Este método es preciso pero la dirección que nos da es la dirección en la que se encuentra el código, es decir la dirección 0xf7e07480 es la dirección en la que se encuentra la directiva .text de la librería libc.

A veces no nos interesa solo saber donde se encuentra la sección text, sino que nos interesa el rango de direcciones total donde se carga una librería, para ello podemos usar el comando.

Info proc mapping

5

Podemos ver que la librería compartida libc tiene 3 entradas, el espacio de direcciones en la que se encuentra esta librería será desde el primer start addr hasta el ultimo end addr, es decir, desde 0xf7df0000 hasta 0xf7fa9000 en mi caso particular.

Por último, gdb dispone de un comando muy útil que nos permite buscar bytes en el espacio de memoria del proceso, su sintaxis es:

find [/size-char] [/max-count] start-address, +length, expr1 [, expr2 …]

donde /size-char puede ser /b para bytes /h para 2 bytes /w para 4 bytes y /g para 8 bytes, para más información se puede escribir help find en gdb.

6

En este ejemplo primero busco 2 bytes 0xf7fd en la direccion 0xf7def000 (que es donde se mapea libc) y busco en un rango de 3000 bytes. Se puede ver como me han aparecido 3 ocurrencias y compruebo que en la primera de ellas efectivamente se puede encontrar la secuencia de bytes. En el segundo ejemplo busco únicamente 1 byte desde una dirección de memoria hasta otra dirección de memoria, gdb me devuelve 2 ocurrencias y compruebo que en la primera ocurrencia que me devuelve gdb se puede encontrar el byte buscado.

Shellcodes

Después de esta pequeña introducción a gdb ya podemos continuar por donde se quedó en el artículo anterior, bueno, aún no podemos meternos a continuar el artículo anterior porque aún queda algo por explicar.

En el capítulo anterior conseguimos redirigir el flujo de ejecución modificando el registro EIP y para ello modificamos un valor de la pila. Lo que nos gustaría es redirigir EIP hacia el inicio de nuestro buffer y ejecutar lo que se encuentre allí. ¿Pero que es lo que se encuentra ahí? En el artículo anterior nuestro buffer era rellenado con 44 A's pero eso no nos sirve.

Lo que necesitamos introducir en nuestro buffer es código que el procesador pueda ejecutar, este código que se mete en el buffer y que la CPU ejecutará recibe el nombre de payload o shellcode.

En una gran cantidad de ataques el objetivo es obtener una shell para poder manejar el ordenador a nuestro antojo, resulta entonces obvio que a este código se le ponga el nombre de shellcode.

Vale ¿Pero que tipo de código tenemos que meter en nuestro buffer? ¿código C? No, la máquina no entiende el código C, deberemos meter directamente código máquina que es el único que la máquina sabe interpretar.

Llegados aquí podemos pensar en compilar un programa C y obtener su código ensamblador, esto es un error, primero porque el código que nos generará seguramente será bastante más largo que el que nosotros mismos podamos escribir, y el tamaño en los shellcodes importa, ya que tiene que caber en nuestro buffer de 44 bytes y por otra parte gcc va a generar código ensamblador para un programa estructurado, con su region de memoria, de código etc, nosotros necesitamos un programa que no necesite regiones, es decir, no debe estar bien estructurado.

Nuestro objetivo es escribir un código en ensamblador capaz de ejecutar una shell en tan solo 44 bytes. Además tenemos problemas añadidos, no vamos a tener regiones, entonces no tendremos region de datos ni variables que podamos referenciar de forma sencilla.

Antes de escribir ensamblador que nos ejecute una shell es necesario explicar que en linux muchas operaciones básicas las ejecuta realmente el sistema operativo, el programa de usuario que se ejecuta en el espacio de usuario hace una llamada al kernel y el kernel maneja esa llamada en espacio kernel y posteriormente devuelve el control al usuario. Esto es lo que se conoce como llamadas al sistema. Por ejemplo, cuando queremos abrir un fichero, hacemos la llamada al sistema open, el sistema operativo abre el fichero por nosotros y nos devuelve un descriptor de fichero.

Cada una de estas llamadas al sistema tiene un numero asociado, por ejemplo, open es el numero 4. Pues bien, hay una llamada al sistema que nos permite ejecutar procesos, esta es execve y tiene el número 0x0B (12). Lo que debemos hacer es invocar esta llamada al sistema y pasarle como proceso a ejecutar /bin/sh. ¿Pero como se ejecutan estas llamadas al sistema?

En c es bastante fácil:

int execve(const char *filename, char *const argv[], char *const envp[]);

Pero en ensamblador se complica un poco, primero hay poner en eax el numero de la llamada al sistema que queramos hacer, después en ebx ponemos el primer parámetro, en ecx el segundo y en edx el tercer parámetro y por último llamar a la interrupción 0x80. Quedaría algo así

mov $0xb, %eax
mov 1º parámetro, %ebx
mov 2º parámetro, %ecx
mov 3º parámetro, %edx
int $0x80

Pero además tenemos un problema añadido con nuestra shellcode, no puede contener bytes nulos (0x00) y tampoco es recomendable que lleve espacios (0x20), saltos de linea (0x0A) y el byte (0x09), después explicaré un poco más detalladamente esto.

Para establecer valores númericos en los registros no es complicado, basta hacer un mov al registro y ya lo tenemos, pero lo complicado viene cuando queremos almacenar en un registro una cadena de texto, como puede ser “/bin/sh”, el registro no almacena la cadena como tal sino que almacena la dirección de memoria donde se encuentra la cadena y, como he dicho anteriormente, no tenemos sección de datos, no es un programa estructurado. Para resolver esto hay un truco bastante ingenioso que expongo a continuación

jmp truco
inicio:
pop %esi

truco:
call inicio
db “/bin/sh”

El sistema es bastante sencillo, nada más empezar hacemos un salto a truco, en truco hacemos un call a inicio ¿Y que es lo que hacía un call? Un call era como un jmp pero además almacenaba la dirección de la siguiente instrucción a ejecutar en la pila, es decir, este call va a almacenar la dirección de la cadena “/bin/sh” en la pila, y cuando saltemos a inicio la vamos a recuperar en ESI mediante pop %esi. Por tanto, tendremos en ESI la dirección del primer parámetro que le tenemos que pasar a execve. El código en ensamblador encargado de ejecutar una shell y posteriormente salir del programa me ha llevado apenas 34 bytes y es el siguiente:

para compilar y enlazar este código necesitamos los siguientes comandos:

as --32 shellcode.s -o shellcode.o
ld -m elf_i386 shellcode.o

Aún no he comentado que en lenguaje ensamblador hay 2 sintaxis principales, la sintaxis AT&T que es la mostrada arriba y la sintaxis INTEL que sería la siguiente

Esta sintaxis os será más cercana si programais en ensamblador bajo windows. Para compilar en linux mediante esta sintaxis se ejecutan los siguientes comandos:

nasm -f elf shellcode_intel.asm
ld -m elf_i386 shellcode_intel.o

En cualquier caso ambas sintaxis generan el mismo binario. Ahora necesitamos obtener el código máquina del binario que hemos compilado, para ello podemos ejecutar el siguiente comando:

objdump -D shellcode

7

Y obtendremos algo parecido a la imagen. Lo que nos interesa son los números centrales, los que están entre las direcciones de memoria (80480XY) y las instrucciones ensamblador. Estos números los tendremos que poner de la siguiente forma:

\xeb\x14\x5e\x31\xc0\x88\x46\x07\xb0\x0b\x89\xf3\x31\xc9\x31\xd2\xcd\x80\xb0\x01\xcd\x80\xe8\xe7\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68

Que si nos acordamos es la forma mediante la cual algunos lenguajes de programación nos permiten introducir directamente valores hexadecimales aunque su representación gráfica no sea visible (Como ya hicimos a la hora de poner las direcciones en el artículo anterior).

Llegados a este punto es bastante interesante hacerse un tester de shellcodes, esto es, un pequeño programita que nos permita ejecutar shellcodes, como puede ser el siguiente:

Si ejecutamos este programa veremos como hemos conseguido obtener una shell

8

Ya tenemos nuestro shellcode listo para ser ejecutado y además sabemos que podemos sobreescribir el registro EIP para redirigir la ejecución del programa, lo único que nos queda es saber en que posición de memoria se almacenará nuestra shellcode. Como ya se explico en el artículo anterior, al tener solo 1 buffer, nuestra shellcode se almacenará en la dirección de memoria que apunte ESP pero necesitamos averiguar dicha dirección, para ello vamos a gdb y ponemos el siguiente breakpoint:

break *func+44

y acto seguido ejecutamos nuestro programa mediante, ponemos 44 porque es la cantidad exacta que ocupa nuestro buffer.

run `python -c “print 'A' *44”`

Cuando nos pare el breakpoint ejecutamos

x /16x $esp

9

Vemos que la entrada que hemos metido se almacena en 0xFFFFD600 y nos encontramos con que tenemos un problema, hay un byte nulo 0x00 en la dirección de ESP, esto implica que vamos a tener que meter ese byte nulo en la posición 45 de nuestra cadena de entrada (recordad que en little endian el byte menos significativo se escribe primero).

Un byte nulo implica el final de una cadena y muchas funciones de cadenas de C acaban de leer cuando se introduce un byte nulo o un espacio, o un salto de linea (de ahí que los bytes 0x00, 0x0A, 0x09 y 0x20 no puedan aparecer)

¿Que podemos hacer?
Poner como primer byte el byte 0x90 que es la instrucción NOP y no hace exactamente nada y en vez de llamar a la dirección de memoria 0xFFFFFD600, llamar a la dirección 0xFFFFFD601. Por tanto, tenemos 34 bytes del shellcode + 1 byte NOP que en total hacen 35 bytes, pero nuestra entrada debe ser de 44 bytes para empezar a sobreescribir el registro EIP (y 48 bytes para sobreescribirlo totalmente) por tanto necesitamos 9 bytes más a parte de nuestro shellcode, estos bytes serán NOPs (0x90) y al final le concatenamos \x01\xd6\xff\xff (dirección 0xFFFFD601 en formato little-endian). Vamos a probar

10

¡Obtuvimos una shell!. Hemos conseguido obtener una shell a partir de un programa vulnerable, vamos a probar a explotar esta vulnerabilidad fuera de gdb.

El shell lo obtenemos con el siguiente shellcode

\x90\xeb\x14\x5e\x31\xc0\x88\x46\x07\xb0\x0b\x89\xf3\x31\xc9\x31\xd2\xcd\x80\xb0\x01\xcd\x80\xe8\xe7\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x90\x90\x90\x90\x90\x90\x90\x90\x90\x01\xd6\xff\xff'

11

¿Como es posible? Nos da un fallo de segmentación, pero si hemos sido capaces de explotar la vulnerabilidad….
La respuesta en el siguiente artículo 😉

Fundamentos de StackOverflows

Notas Previas

Hace ya bastante tiempo, fácilmente unos 9 años, a la par que los artículos de ingeniería inversa con Ollydbg (quizá un poco después) escribí unos artículos sobre las vulnerabilidades del tipo stackoverflow para un ezine de aquella época. He decidido reeditarlos añadiendo muchas mas explicaciones y muchas más imagenes, para que se entienda mejor, los artículos que escribí inicialmente no tenían apenas imágenes y en algunos puntos quizá eran un poco complicados de entender. Así que sin más preámbulos empezamos.

Antes de cada artículo voy a comentar la forma de comentar el programa vulnerable. Para este caso es necesario compilar el programa name.c mostrado más abajo de la siguiente forma:

gcc -m32 -fno-stack-protector -z execstack name.c -o name

También es necesario desactivar ASLR ejecutando el siguiente comando con permisos de administrador

echo 0 > /proc/sys/kernel/randomize_va_space

Fundamentos de StackOverflow

En este artículo nos vamos a centrar en las vulnerabilidades de tipo Stack Overflow.

Para seguir este artículo vamos a necesitar una serie de herramientas que se enumeran a continuación.

- GCC
- objdump
- gdb

En la mayoría de distribuciones Linux estas herramientas vienen por defecto instaladas, por último para compilar nuestros programas vamos a necesitar el siguiente comando de gcc

gcc -fno-stack-protector -z execstack fichero.c -o programa

Donde fichero.c es el fichero fuente que queremos compilar y programa el nombre del ejecutable.

También vamos a nacesitar desactivar ASLR ejecutando la siguiente instrucción como root

echo 0 > /proc/sys/kernel/randomize_va_space

Para volver a activar ASLR:

echo 2 > /proc/sys/kernel/randomize_va_space

Lo que estamos haciendo con esto es desactivar todas las protecciones que implementa el compilador y el sistema operativo.

Introducción

En estos artículos vamos a hablar sobre aspectos bastante técnicos de la arquitectura de un programa y del Sistema Operativo, así que es necesario explicar antes una serie de conceptos importantes.

Para empezar tenemos que tener en cuenta que cada proceso se ejecuta en su propio espacio de direcciones y a grandes rasgos la estructura de memoria de cada proceso es algo parecido a lo siguiente:

process_va

En la primera dirección del proceso se coloca el valor 0x00000000 a continuación va el nombre del programa, después las variables de entorno que han sido heredadas del proceso padre y los argumentos.

A continuación nos encontramos con 2 secciones bastante interesantes, por una parte la pila o stack donde se almacenan los argumentos de los métodos y las variables de los métodos, entre otras cosas. El stack va creciendo hacia direcciones inferiores de la memoria. Por otra parte tenemos el heap o monton, donde se almacenan las variables que se hayan definido reservando memoria con la familia de funciones malloc. El heap está estructurado en zonas contiguas de la memoria formadas por una cabecera y los datos en si y no puede haber dos regiones del heap vacias, si esto es así, estas 2 regiones se fusionan en una sola. El heap al contrario que el stack va creciendo hacia direcciones superiores de la memoria y también es susceptible de ataques, los conocidos heap overflow.

La estructura que nos interesa a nosotros es la pila, que es una estructura de tipo LIFO (Last-In First-Out). ¿Qué significa esto? Significa que el último dato en entrar será recogido el primero cuando se ejecute una instrucción de recogida de la pila.

Podemos hacer una analogía con un conjunto de libros apilados, si tenemos un libro encima de otro, cuando dejemos otro libro lo dejaremos encima del todo y cuando queramos coger un libro tendremos que ir retirando libros desde el final hasta llegar al deseado.

Las dos instrucciones básicas que trabajan con la pila en ensamblador son POP y PUSH. PUSH sirve para introducir un dato en la pila y POP para retirarlo.

Es necesario recordar que el procesador internamente trabaja con registros generales (EAX, EBX, ECX, EDX, EIP, ESI, EDI, EBP, ESP y un registro de flags), registro de segmentos (CS, DS, SS, ES, FS, GS) y unos cuantos registros más internos que no vienen al caso. La E delante de los registros significa extendido, ya que estos registros son heredadeos de las arquitecturas de 16 bits donde EAX era AX.

Los registros que más nos interesan son EBP (Extended Base Pointer), ESP (Extended Stack Pointer) y EIP (Extended Instruction Pointer). EBP y ESP son registros de pila, EBP apunta a la base de la pila y ESP apunta a la cima de la pila, EIP almacena la siguiente instrucción a ejecutar.

A continuación vamos a presentar un programa en C.

Podemos pensar que a simple vista es un programa sin nada en especial, de hecho si lo ejecutamos obtenemos una salida normal:0

Pero vamos a introducir más caracteres de los que permite (que son 32). Para ello me voy a valer de python para generar cadenas, concretamente voy a meter 35 caracteres a ver que ocurre, para ello voy a usar el siguiente comando:

./name `python -c "print 'A'*35"`

1

Vemos que en contra de lo que hemos indicado permite una cadena mayor de 32 bytes, vamos a introducir ahora una cadena bastante más larga a ver que ocurre.

2

Como podemos ver ahora si que nos muestra un error, concretamente el conocido fallo de segmentación. Vamos a proceder a ejecutar este programa con el depurador de linux gdb a ver que ocurre

3

Aquí podemos obtener mucha más información, vemos que gdb nos indica lo siguiente:

0x41414141 in ?? ()

¿Qué significa esto? Significa nada más y nada menos que el programa ha intentado ejecutar una instrucción en la dirección 0x41414141 pero que no ha podido ejecutarla porque dicha dirección no está mapeada y de ahí que de un fallo de segmentación. 0x41 es la representación del carácter A.

¿Que es lo que ha ocurrido?

Lo que ha ocurrido es que hemos sobreescrito la dirección de retorno del método func con el parámetro que le hemos proporcionado y ahora está intentando retornar a una dirección no mapeada. Para entender mejor todo lo que ha pasado es necesario explicar un poco más a fondo el funcionamiento de la pila y de la llamada a métodos.

Cuando se va a llamar a una función o método el programa introduce los parámetros (esto depende de la convención de llamada usada, cdecl, stdcall o fastcall) en la pila mediante la instrucción push, acto seguido se ejecuta la instrucción ensamblador call que básicamente lo que hace es realizar un salto a la dirección de memoria de la función a llamar y además introduce el registro EIP en la pila, a continuación, se introduce en la pila el registro EBP y por último se introducen las variables locales. Por tanto tenemos la siguiente representación gŕafica:

stack

Podemos ver que si proporcionamos más bytes de los que admite nuestro buffer sobreescribiremos valores superiores, esto es EBP, EIP…

Vamos a desensamblar el código del programa para ver realmente que es lo que se hace en la función func

4

Vemos como, nada más entrar, en la función se ejecuta la instrucción push %ebp seguida de un mov %esp, %ebp. Lo que está haciendo aquí es crear un nuevo marco de pila. Pone EBP en la pila (como se ha mostrado en la imagen superior) e iguala ESP a EBP, posteriormente resta 0x28 (50 en decimal) a ESP, es decir, reserva 50 bytes en la pila, esto corresponde a nuestro buffer local que hemos declarado. Nosotros hemos definido que tenga capacidad para 32 bytes pero realmente nos ha reservado 50 bytes, esto realmente depende del compilador usado y de la versión.

Las dos primeras instrucciones ensamblador, junto a las 2 ultimas (leave y ret) forman el prólogo y el epílogo de la función que es común a todas las funciones. De hecho, si ponemos un breakpoint en la función func con break func veremos que no pone el breakpoint en la dirección 0x0804842b sino en la dirección 0x08048431 que corresponde básicamente con la instrucción ensamblador siguiente a la reserva de memoria para las variables locales.

5

Esto es porque gdb interpreta que es el prólogo común de todas las funciones y no lo tiene en cuenta (si queremos que lo tenga en cuenta deberemos poner el breakpoint mediante break *func).

Vale, sabemos que hemos sobreescrito la dirección de retorno del método pero esto nos sirve de poco, necesitamos saber la longitud exacta que tenemos que pasarle al argumento para sobreescribir la dirección de retorno del método. De forma artesanal podemos optar por una técnica bastante sencilla (y que usan algunos de los frameworks de exploiting existentes en el mercado).

La técnica consiste en invocar al programa de la siguiente forma:

./name AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNN

De esta forma cuando sobreescribamos el EIP podremos identificar en que parte de la cadena se ha sobreescrito, si ejecutamos esto en gdb obtenemos lo siguiente:

6

Vemos que EIP =0x4c4c4c4c, 0x4c es L, contando desde el principio hasta la primera L de la cadena pasada por argumentos obtenemos que la primera L está en la posición 45, por tanto para sobreescribir EIP necesitamos exactamente 48 caracteres en la cadena.
Vamos a probar a redirigir el flujo del programa, si sabemos donde está EIP, podemos pasarle una dirección válida y ver si ejecuta el código de esa dirección de memoria. Hay que recordar que el valor de EIP se recupera cuando se sale de la función, mediante RET, por tanto, la dirección de memoria que pongamos ahí se ejecutará cuando se finalice la función func. Vamos a hacer algo sencillo, queremos que cuando acabe la función func se vuelva a llamar a si misma. Para esto necesitamos conocer la dirección en la que se encuentra la función func, podemos hacerlo mediante gdb, pero ahora vamos a usar objdump, otra herramienta de volcado de binarios que nos será realmente útil

7

Podemos observar que la función func está en la dirección de memoria 0x0804842B

Para pasar una dirección hexadecimal de memoria como parte del argumento del programa necesitamos hacerlo de la siguiente forma:

./name `python -c "print 'A'*44+'\x2b\x84\x04\x08'"`

Lo que hacemos aquí es imprimir 44 veces A y después la dirección de memoria con la que queremos sobreescribir el registro EIP, hay que recordar que en una arquitectura little-endian como en la que estoy, el byte menos significativo se debe colocar en la primera posición. La dirección de memoria no la podemos pasar como un string normal porque no se va a interpretar correctamente, por eso python y otros muchos lenguajes de programación tienen mecanismos para escribir valores hexadecimales directamente, con la sintaxis \xXX donde XX es el valor hexadecimal. Si ejecutamos el programa con estos parametros veremos lo siguiente:

8

Vemos que se ha vuelto a ejecutar la función porque ha vuelto a imprimir por pantalla “Bienvenido” lógicamente después del estropicio que hemos hecho da un fallo de segmentación. No es el objetivo de este ejemplo intentar restaurar los valores de la pila para salir del programa de forma limpia, solamente demostrar como desbordar un buffer en el stack.

Bueno, hemos conseguido sobreescribir la dirección del registro EIP y poder saltar a una dirección de memoria arbitraría. Esto es interesante pero podría ser mucho más interesante, ¿Y si se puediese saltar a una dirección donde nosotros hayamos inyectado código para poder hacer lo que queramos? La buena noticia es que se puede hacer y lo veremos en el siguiente artículo.

A gift that I did to Elena

A gift that I did to Elena

That photo is the present that I did to Elena. I printed the photo (well, the original photo had a higher resolution), put it on frame and give it to Elena.

So, Why this photo is special? What relation has this photo with the thematic of this blog?. Let me show the magic inside this photo.

That photo is a special photo, ¡inside it the photo hide a message!

If my memory doesn't fail, 4 years ago I develop a little program that can hide texts inside images (bmp's). As usual in me, I had the idea at night and in exams period. That night I forgot all that I had to do and started to develop the program.

What is an image?

An image is a pixel matrix of height*width dimensions, a pixel is a set of bytes that determine the amount of red, green and blue that contain each pixel (each "amount" can be called channel), from now it denote as [red, green, blue], each channel can be anu number between 0 and 255 in decimal, so the red color is [255,0,0] and the white color is [255,255,255], that is, the white color is formed by the presence of all primary colors, this model is called RGB and is frequently used for color representation in light model (as could be monitor of computers), in the other hand, for color representation in paper, the most common model is CMYK. A variation of RGB is ARGB or RGBA, in this model we have a additional channel for alpha.

We center in RGB model that is used in the header image, this image has 8 bit per each channel, this is, 1 byte per each channel (from 0 to 255 in decimal). As I mentioned before a red color in this model is [255, 0, 0]

How can we hide text in an image?

This is simple solution, obviously the solution is hide text inside pixel of the image, but to do this we must ask other question.

What is a text?

A text, same as image or whatever digital thing is a sequence of 1's and 0's. A text is formed by letters and each letter is formed by 8 bits, this is, 1 byte, this is valid for the system that we use in Spain (extended ascii). For example, to write my name, Ángel Luis, the computer understand:

181 110 103 101 108 32 76 117 105 115

If we pass this to binary, that is what computers understand, we obtain:

10110101 01101110 01100111 01100101 01101100 00100000 01001100 01110101 01101001 01110011

 

How can we hide text in a image? (Again)

What we have to do is try to hide letters of text inside pixels, but we have a limitation, our pixels has 8 bits, the same as a letter, if we store a letter per pixel the image will be distorted because we remove completely a channel of our pixel, so the mos simple way to do our task is store 3 bits per pixel (1 bit per channel), so in order to encode one letter we need 3 pixels. Other thing that we have in mind is that we have to modify the less significant bit in our channel, the purpose of this is to affect as little as possible to our image.For example, if we want encode the letter Á (181, 10110101) in a image completely red that have 3x3 size.Here is the original image in red

\begin{pmatrix} [255, 0, 0] &[255, 0, 0] & [255, 0, 0] \\ [255, 0, 0] &[255, 0, 0] & [255, 0, 0] \\ [255, 0, 0] &[255, 0, 0] & [255, 0, 0] \end{pmatrix}

If we pass it to binary, we have

\begin{pmatrix} [1111111\textbf{1}, 0\textbf{0}, 0\textbf{0}] &[1111111\textbf{1}, 0\textbf{0}, 0\textbf{0}] & [1111111\textbf{1}, 0\textbf{0}, 0\textbf{0}] \\ [1111111\textbf{1}, 0\textbf{0}, 0\textbf{0}] &[1111111\textbf{1}, 0\textbf{0}, 0\textbf{0}] & [1111111\textbf{1}, 0\textbf{0}, 0\textbf{0}] \\ [1111111\textbf{1}, 0\textbf{0}, 0\textbf{0}] &[1111111\textbf{1}, 0\textbf{0}, 0\textbf{0}] & [1111111\textbf{1}, 0\textbf{0}, 0\textbf{0}] \end{pmatrix}

Note: For simplicity when a channel is completely 0 we use 00 instead 0000000.

Note: In bold the bits that will be replaced for each of our letter's bits (in that case the letter will be Á, 10110101)

\begin{pmatrix} [1111111\textbf{1}, 0\textbf{0}, 0\textbf{1}] &[1111111\textbf{1}, 0\textbf{0}, 0\textbf{1}] & [1111111\textbf{0}, 0\textbf{1}, 00] \\ [11111111, 00, 00] &[11111111, 00, 00] & [11111111, 00, 00] \\ [11111111, 00, 00] &[11111111, 00, 00] & [11111111, 00, 00] \end{pmatrix}

Now, in bold the bits that has been affected by encode.

If we pass it to decimal

\begin{pmatrix} [255, 0, 1] &[255, 0, 1] & [254, 1, 0] \\ [255, 0, 0] &[255, 0, 0] & [255, 0, 0] \\ [255, 0, 0] &[255, 0, 0] & [255, 0, 0] \end{pmatrix}

As you can see, when we modify the less significant bit we get an error of 1 pixel in each channel respect the original image.

This is our image with the encoded text. Now, we only have to automatize the process via program, have in mind that text must be delimited between start and end flag in order to know where start and end the text.

Proceso de codificación del texto

Process to encode text

In the above image we list the current directory where there are 4 files, 2 programs, 1 text and 1 image. Next we show the text inside text_hide (Esto es una prueba de texto oculto en una imagen). Now we run the program codificador, we choose the image where the text will be encode (image1.bmp), the text to hide (text_hide) and the new image that will be generated (image1.bmp). Finally I show that 2 images are different, for that I use a diff command. Despite this, for the human's eye both images are  identical.

Proceso de decodificación del texto

Process to decode text

In above image I show decode process. First I try to decode image.bmp, that is the original image, and the program says that it hasn't hidden text, next I try to decode the file image1.bmp and the program text that it has a hidden text.This method has a disadvantage. If we have a text of 100 letters of length, we need a image of

\dfrac{100*8}{3} = 266,666 \approx 267 pixels

Not many pixels if we compare it with a high definition photo (1920 x 1090 pixels). But it could be a limitation with little images.

Conclusion

photo

So, this was a special gift to Elena.This technique is called Steganography, and it is based in hide messages inside other containers, in this case, a image.I want to remember you that this program has 4 years old. This program only accepts bmp images of 24 bytes per pixel (8 bytes per channel). If I have to improve this program I considerer the following points

  • Accept more images format (like jpg or png), This will be get using some processing image library or framework, for example, imagemagick, Pixmap class from Qt or PixBuf function from GTK+.
  • Hide text in a random position in the image, now the text starts encode at position 54.
  • Compress the text with well-known, for example Base64.
  • Encrypt the text with well-known algorithm (symmetric or asymmetric) like RSA or AES.
  • Also hide a text's hash in a random position of the image, with this we can check that message hasn't been modified.