Introducción al Cracking

EDICIÓN 2015

Después de pensármelo bastante voy a publicar una serie de manuales y tutoriales que hice para la comunidad de crackers en la que estuve involucrado (CracksLatinos). Los textos que publicaré intentaran sufrir la menor cantidad de modificaciones posibles.

Estos textos eran para aydar a los más novatos del grupo a coger una base y que pudiesen compartir sus conocimientos conforme vayan haciendo investigaciones.

Estos textos están datados en 3 épocas distintas, la mayoría son del 2007, antes de empezar la universidad y justo después de acabar el instituto, así que pido perdón por los errores que puedan haber en la edición original, tanto en las nociones teorícas como en la programación de las herramientas, ya que aprendí a programar (es una forma de decirlo, solo sabía escribir código, programar aprendí en la Universidad, con POO, métrica, calidad del código, patrones etc) en 1º-2º de la ESO, pero al no disponer de internet no pude mejorar hasta bastante tiempo después .

Otros textos datan del año 2009 y los últimos textos (que los los menos explicativos) datan de alrededor del 2011-2012.

Es posible que haya incoherencias en los textos puesto que no voy a publicar todos, publicaré solamente aquellos de un nivel introductorio.

Antes de cada tutorial añadiré las herramientas necesarias para poder seguir el mismo, no serán versiones actualizadas de las herramientas sino que serán las herramientas que yo usé en su día.

EDICIÓN 2007

Esta primera parte va a estar dirigida a aclarar las cosas antes de empezar, trataremos definiciones, herramientas a utilizar y mi motivación personal.

¿Qué es Cracking?

El Cracking se basa en la ingeniería inversa, que consiste en estudiar un programa para saltarse sus mediadas de protección. Mucha gente, sin saberlo, tiene pensamiento de cracker (como decía Joe Cracker), seguramente muchos de vosotros, al igual que a mi, de pequeños desmontaban los aparatos electrónicos para saber que hay dentro o como funciona el susodicho aparato.

Herramientas a utilizar y niveles de dificultad

Dentro del cracking hay diversos niveles de dificultad, desde programas que se crackean en 1 minuto hasta programas que puedes tardar meses, las protecciones que vamos a estudiar son 2, los hardcoded y los nombres/Serial.

Hardcoded: Estas medidas de seguridad ya apenas se usan por ser las mas fáciles de saltar, consiste en un código que el programa lleva dentro de sí

Nombre/Serial: Es la protección que usan la mayoría de programas, por lo general suelen ser mas difíciles que los hardcoded, aunque siempre hay excepciones. Estos sistemas no se basan en un código fijo, sino que al introducir el nombre, el programa hace una serie de operaciones con él y luego lo compara con el código que has metido.

Yo dentro de los Nombre/Serial opino que hay 3 modalidades (por decirlo de alguna manera), algunos dicen que con el fin de conseguir lo que tú quieres da igual lo que hagas (el fin justifica los medios) pero yo lo que siempre intento hacer es crackearlos de la manera mas limpia y utilizando la inteligencia, que traducido al lenguaje cracking significa hacer un keygen o en su defecto sacar el serial. (aunque no siempre se puede debido a la complejidad de algunos algoritmos), a continuación paso a explicar brevemente esas tres “modalidades” que nombro arriba.

Romper la puerta: esto quiere decir parchear el programa, también se conoce como la “Método del 74/75” (mas adelante entendereis por qué), esta es la mas fácil, solo hay que encontrar los saltos condicionales que hacen aparecer el mensaje de error de que has introducido el serial equivocado y parchearlos (Esta técnica también se puede usar en los hardcoded). Ejemplo(El código esta simplificado para que lo entendáis mejor):

CMP EAX,EBX

JNE 00401000

Esto es la estructura mas fácil que os podéis encontrar, en la primera línea lo que hace es comparar EAX y EBX (EAX y EBX son como variables, realmente se llaman registros, donde se almacenan datos del programa, hay mas “variables” como ECX, EBP, etc. eso ya se verá) y si no son iguales el programa salta a la dirección de memoria 00401000 que es el lugar donde se encuentra el mensaje de error, y justo debajo de ese salto supondremos que esta el mensaje de felicitación por haber introducido el serial correcto, así que lo que se tendría que hacer es cambiar el JNE(Saltar si no son iguales) por JE(Saltar si son iguales) de esta manera introduciendo cualquier nombre y cualquier serial registraremos el programa (a no se que acertemos el serial, ya que si acertamos el serial EAX y ECX serán iguales por lo tanto en el programa parcheado que hemos cambiado JNE por JE no nos dejaría registrarnos), y diréis ¿Qué tiene que ver esto con el 74/75? Esto se explica con la equivalencia Hexadecimal del lenguaje ASM. JE=74 y JNZ=75.

1

2

Nota: Esta estructura admite 2 soluciones más mediante el parcheo.

  1. (Os recuerdo que el pc lee el programa secuencialmente de arriba hacia abajo, dicho esto explico esta forma de parcheo) Podemos cambiar el JNZ por NOP (no hacer nada) y como la zona de felicitación esta justo debajo del JNE (por que nosotros lo suponemos en este ejemplo) entonces hará la comparación, pasará por los nops sin hacer nada y finalmente llegará a la zona de de felicitación.

3

Aclaracion: Si os habéis dado cuenta en las imágenes de los saltos condicionales JE y JNZ a la izquierda aparecían 4 números agrupados en grupos de dos, en el de JNZ a aparecía 75 16 esto son 2 Bytes, cada par de números en hexadecimal es 1 Byte (ojo solo en el sistema hexadecimal) pues para no alterar el programa ha cambiado esos 2 bytes en 1 línea por 2 byte en 2 líneas, de ahí que el programa haya colocado 2 Nop’s en vez de 1.

Nota: encima de los saltos condicionales y de los nop’s esta la comparación.

  1. Muchos dicen que poner NOP’s en un programa es ensuciarlo así que lo que prefieren hacer es poner instrucciones que dejen el programa como estaba, como por ejemplo:

4

Aclaración: INC = Incrementar en 1, DEC = Decrementar en 1, ya supongo que sabréis en que consiste esto, primero incremento EAX en 1 y luego lo Decremento en 1, por lo que se queda tal cual estaba al principio.

Después de esta “extensa” explicación prosigo con las demás “modalidades”

Copia de llaves: Esta táctica consiste en, mediante un “debugeo en vivo”, encontrar el serial para nuestro nombre.

Llave Maestra: Finalmente llegamos a la “modalidad” de mayor dificultad, esta quizás es la que mas se ajuste a la definición de Cracking, ya que hacer un keygen significa que comprendes el algoritmo de generación de las claves y por lo tanto has cumplido las bases de la ingeniería inversa.

Pasamos a las herramientas a utilizar

  • La herramienta esencial que utilizaremos será OLLYDBG que es un Depurador, es decir, un programa usado por los programadores para testear sus propios programas para buscar agujeros de seguridad, pero nosotros lo usaremos para hacer ingeniería inversa.

  • La segunda herramienta será un editor Hexadecimal, el que queráis como Hiew o UltraEdit.

  • Usaremos otras herramientas que ya se irán mencionando y se proporcionará el link cuando sea necesario.

Configuración del OLLYDBG

Vamos a Options>Debugging Options y a la pestaña CPU, allí seleccionamos las siguientes casillas: Show direction of jumps, Show jump path y Show grayed path if jump is not taken luego sobre donde se nos muestra el código en ASM hacemos clic con el 2º botón y elegimos Apparence > Highlighting > jumps’n’jumps.

Interface del OLLYDBG

5

Código: Aquí es donde se muestra el código ASM del fichero que hayamos cargado, a su vez esta formado por varias parte, a la izquierda del todo se encuentran las posiciones de memoria, lo siguiente por la derecha son los Bytes (grupos de 2 en el sistema hexadecimal) lo siguiente por la derecha es la tradución a ASM de esos bytes y lo último son comentarios.

DUMP: El Dump es como si abriesemos el archivo con un editor hexadecimal, aquí se nos muestra las posiciones de memoria, los bytes y la traducción en ASCII de esos bytes.

Pila o Stack: Aquí es donde se irán guardando datos que el programa luego necesitará, la Pila o el Stack es como un mazo de cartas, cuando dejas una carta, la dejas arriba del todo y cuando sacas una la sacas de arriba del todo, las ordenes para quitar y poner valores son: PUSH, PUSHAD, POP, POPAD, PUSHA, POPA, etc… La Pila o Stack se divide en 3: de izquierda a derecha son: Posiciones de memoria, Valor colocado y Valor ASCII.

Registros y Banderas: Los registros son lugares en donde se guardan valores, estos registros son: EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI, EIP (este ultimo indica la posición de memoria que se ejecutara a continuación), Ahora veamos como se dividen los registros, imaginemos que EAX = 00112233, los últimos 4 valores se llaman AX (solo en EAX, en EBX será BX, en ECX será CX, etc…), de esta manera AX=2233 y por ultimo AX se divide en AH=22 y AL=44. Las banderas son como apoyos del programa, el programa hace una comparación y dependiendo del resultado se activa una u otra bandera, veamos las banderas:

  • C: Es el Carry flag se activa cuando se sobrepasa el maximo valor permitido, es decir, mayor a FFFFFFFF.

  • P: Bandera de Paridad se activa cuando el numero que se somete a operaciones convertido a binario tiene un número PAR de unos.

  • Z: Bandera Zero, se activa cuando ejecutamos una instrucción y el resultado es 0.

  • S: Es la Bandera de Signo y se activa cuando el numero es negativo.

  • O: Es la bandera de desbordamiento de datos (overflow).

Hay más flags, pero de momento con estos está bien.

Instrucciones básicas en ASM

INC: incrementa en 1, por ejemplo, INC EAX, esto es igual a EAX + 1.

DEC: Decrementa en 1, por ejemplo, DEC EAX, esto es igual a EAX - 1.

ADD: ADD EAX,1 es igual a INC EAX, de esta manera ADD EAX,6 es igual a EAX+6.

SUB: SUB EAX,1 es igual a DEC EAX, de esta manera SUB EAX,4 es igual a EAX-4.

NOP: No hace nada.

JE: Salto condicional que salta si los 2 registros cormprobados son iguales.

JNE: Salto condicional que salta si los 2 registros comparados no son iguales.

CMP: Compara 2 registros, ejemplo CMP EAX;EBX.

TEST: Es otro tipo de comprobación, pero se utiliza para saber si lo comprobado es 0.

PUSH: Pone el valor que hay a continuación del Push en la pila/snack, ejemplo Push 30, de esta manera colocará el valor 30 en la Pila/Stack.

POP: Quita el ultimo valor de la Pila/Sntck y lo coloca en el registro que haya a continuación, ejemplo POP EAX, esto hará que se retire el ultimo valor de la Pila/Stack y se coloque en EAX.

MOV: Mueve un valor de un lugar a otro, ejemplo MOV EAX,EBX Mueve el valor de EBX a EAX (y no al reves).

MUL: MUL es una multiplicación normal y corriente que no considera el signo, solo utiliza un operando, por que el otro operando siempre va a ser EAX, y el resultado se va a guardar EDX:EAX, esto quiere decir que EAX guarda las cifras que pueda y las que no quepan se guardan en EDX. Ejemplo: Mul EBX, multiplicará EAX * EBX y se guardará en EDX:EAX.

IMUL: IMUL es igual que la anterior con la excepción de que ahora considera el signo del número.

DIV: Como MUL pero para la división.

IDIV: Como IMUL pero para la división.

NEG: Se encarga de cambiar el signo del número.

CALL y RET: Estas instrucciones son MUY IMPORATANTES cuando hay un call lo que hace el programa es llevarte a una subrutina, es decir, el call te lleva a una zona del programa en donde se ejecutara un determinado código y acabará la ejecución cuando encuentre una instrucción RET. Cuando llega al RET el programa seguirá ejecutando la linea que hay por debajo del CALL, Puede ser que dentro de un CALL haya otro CALL a esto se le llama CALLs ANIDADOS.

Operaciones lógicas

Las operaciones lógicas se suelen hacer siempre a nivel de bits, es decir, en el sistema binario, así que veamos unos ejemplos:

OR: Se activa cuando al menos, uno de los bits es 1.

1 or 1 = 1

1 or 0 = 1

0 or 1 = 1

0 or 0 = 0

Veamos ahora una operación más compleja con OR:

10010011110010011101111000111101

00001101001110101001111001100001

10011111111110111101111001111101

AND: El resultado es 1 SOLO cuando los 2 bits son 1

1 and 1 = 1

1 and 0 = 0

0 and 1 = 0

0 and 0 = 0

Veamos ahora la operación compleja con AND:

10010011110010011101111000111101

00001101001110101001111001100001

00000001000010001001111000100001

XOR: El resultado es 1 SOLO cuando uno de los dos bits es 1 (si los 2 bits son 1 el resultado no será 1)

1 xor 1 = 0

1 xor 0 = 1

0 xor 1 = 1

0 xor 0 = 0

Operación compleja con XOR:

10010011110010011101111000111101

00001101001110101001111001100001

10011110111100110100000001011100

NOT: Esta función lo que hace es si un bit es 1 transformarlo en 0 y si es 0 ponerlo a 1:

not 1 = 0

not 0 = 1

operación compleja con NOT:

1010100110 = 0101011001

Saltos Condicionales

Antes de los saltos condicionales tiene que haber una comparación para saber si el salto se tomará o no.

CMP: Esta instrucción ya la vimos, lo que haces es comparar 2 valores, esta instrucción en realidad es una instrucción SUB que no se guarda en los registros, si los 2 valores son iguales se activará el Flag Zero (ya que el resultado es 0) y dependiendo de si es mayor o menor se activara el Flag de signo.

TEST: Esta instrucción ya la vimos, pero muy por encima, esta función en realidad es un AND que tampoco se guarda en los registros, se suele usar para comparar el mismo registro, ejemplo: TEST EBX,EBX ¿Por qué? Supongamos que EBX=1001101, la comparación entonces será

1001101

1001101

1001101

Vemos que el resultado es el mismo, ¿pero y si EAX=0000000?

0000000

0000000

0000000

Seguimos viendo que el resultado es el mismo, pero al contrario que en el ejemplo anterior (que toma un valor, pero ese valor nos da igual) aquí nos dice que vale 0, de esta manera hemos comprobado que EAX=0. Resumiendo, comparar un registro consigo mismo sirve para saber si ese registro es 0 (Esto se utiliza bastante en los algoritmos de generación de las claves)

Ahora si, vamos con los saltos condicionales

  • JE,JZ: Salta si es igual.

  • JNE,JNZ: Salta si no es igual.

  • JS: Salta si el signo es negativo.

  • JNS: Salta si el signo es positivo.

  • JMP: Salta siempre.

  • JP,JPE: Salta si hay un numero par de 1s.

  • JNP,JOP: Salta si hay un numero impar de 1s.

  • JO: Salta si se ha excedido la capacidad.

  • JNO: Salta si no se ha excedido la capacidad.

  • JB,JNAE: Salta si esta por abajo.

  • JNB,JAE: Salta si esta por encima.

  • JBE,JNA: Salta si esta por abajo o igual.

  • JNBE,JA: Salta si esta por encima o igual.

  • JL,JNGE: Salta si es menor que.

  • JNL,JGE: Salta si es mayor que.

  • JLE,JNG: Salta si es menor o igual que.

  • JNLE,JG: Salta si es mayor o igual que.

Todos los saltos condicionales se componen por la instrucción del salto seguido de la posición de memoria a la que saltará

BreakPoint

Un breakpoint es la ruputra en la ejecución de un programa, por lo general el programa se queda parado en el punto donde hayamos colocado el breakpoint hasta que demos la orden de continuar. Hay varios tipos de BreakPoint.

BreakPoint común, se suele escribir BreakPoint o BPX para abreviar

BP memory on acces, se utiliza cuando queremos que el OLLY pare al leer algo de la memoria

BP memory on write se utiliza cuando queremos que el OLLY pare al escribir algo en memoria

Hardware BreakPoint, se suelen escribir BPH y solo se pueden colocar 4 BreakPoint a la vez

Condicional BreakPoint se usan para que el OLLY pare cuando se den unas determinadas condiciones

Aún quedan algunos tipos más de BreakPoint pero ya se verán

Un BreakPoint común se pone seleccionado la línea a la que queremos ponérselo y luego a F2 o bien haciendo doble clic sobre se nos muestra las instrucciones en Hexadecimal, a la derecha de la posición de memoria y los BreakPoint se quitan haciendo clic sobre el botón B:

10

Eligiendo el breakpoint que quieras quitar y luego a Remove

WIN32 API

Las API’s son funciones ya creadas que lo programadores pueden utilizar en sus programas, dicho de otra manera, son funciones que vienen con el Sistema operativo y facilitan la tarea al programador, las API’s generalmente vienen en los archivos DLL del sistema, las mas importantes son Kernel32.dll y User32.dll (el 32 es por que estas API’s son para los sistemas de 32 bits y de ahí que se llamen Win32 API’s). Por ejemplo si queremos mostrar un mensaje, tenemos 2 posibilidades, o bien hacer nosotros mismo el mensaje o bien llamar a la API MessageBoxA.

Las API’s que mas se usan para coger texto son: GetDlgItemTextA y GetWindowTextA en los tutoriales ya iremos viendo mas.

Mi motivación personal

Puede sonar raro pero mi motivación personal para meterme en este mundo fue el videojuego Pokemon (concretamente los primeros pokemons, edición roja, azúl y amarilla). En estas ediciones había bastantes fallos y bastantes rumores como por ejemplo las islas fallo (error real), el valle de toguei (rumor) o el camión del SS ANNE (rumor) y es que a veces, el juego ayudaba a crear ese ambiente de rumores.

76

89

En la primera foto vemos lo que supuestamente es el valle de toguepi. En la segunda vemos esas extrañas hierbas que siempre me intrigaron, pensando que si estaban ahí es porque se podía pasar a ese lado. La tercera es como el profesor OAK pasa por esa hierba. La cuarta es missigno, al no tener ningún pokemon al que elegir y haber entrado en una pelea mi pokemon es missigno. Este último quizás es el mayor rumor, missigno, que más tarde cuando ya era un poco mayor desemsamble el código ensamblador de Pokemon Azúl.

En el juego pokemon cada pokemon tenia asignado un numero en Hexadecimal, estos números hexadecimales no podían ir desde 0 a F por que eso solo son 16 pokemon, así que lo que hicieron es ir desde 00 a FF (es decir desde 0 hasta 255), pero como todos los que hemos jugado a pokemon sabemos,en las primeras ediciones solo había 151 pokemon (Aunque hay rumores que dicen que iban a haber más pokemons) ¿Entonces que? Sobraban 105 huecos en donde debía ir algún pokemon ¿Sabéis que pokemon ocupa esos espacios? Es Missigno.

Agradecimientos

Bueno aprovecho para agradecer aquí a las personas para no tener que agradecer en cada tutorial

  • Ricardo Narvaja: Por sus magníficos conocimientos sobre cracking, su afán por enseñar y también quiero nombrar a toda la escuela de Cracker que siguen a Ricardo 😉

  • RedH@wk: por sus Crackme’s

  • Joe Cracker: Por sus crackme’s que son muy originales y aún me quedan por resolver unos cuantos.

  • Makkako: Por sus tutoriales

  • Caos Reptante: Por su magnifico manual de ASM

15 de Agosto de 2007

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.

El regalo que le hice a Elena

El regalo que le hice a Elena

En efecto, este es el regalo que le hice a Elena, imprimí la foto (realmente era otra versión de mayor resolución) en papel fotográfico y se la regalé con un marco.

¿Qué es lo que tiene de especial esta imagen? Pues que no es una imagen normal y corriente aunque pueda parecer que si, es una imagen con un texto oculto.

Hace 4 años si no recuerdo mal desarrollé un pequeño programa para codificar textos dentro de imagenes (bmp's), se me ocurrió como se me solián ocurrir los proyectos en aquella época, por la noche y en época de exámenes, esa misma noche dejé todo lo que estaba estudiando y me pasé la mayor parte de la noche desarrollando el sistema.

¿Qué es una imagen?

Una imagen es una matriz de píxeles de dimensiones alto*ancho, un pixel es un conjunto de bytes que especifican las canales rojo, verde y azul de cada pixel, a partir de ahora denotado por la sintaxis [rojo, verde, azul], cada uno de los canales puede ir desde 0 hasta 255, de tal manera que un pixel de rojo puro es [255,0,0] y el blanco podría representarse por [255,255,255], es decir, el blanco es la presencia de los 3 colores primarios, esto es lo que se conoce como el modelo RGB bastante usando en representación de colores en un modelo de luz (como pueden ser las pantallas de los ordenadores) en contra posición con el modelo CMYK que se suele usar en impresión (para representación de colores en luz hay más modelos a parte del RGB, una variante bastante usada por ejemplo es el RGBA o ARGB que a parte de los colores primarios también incorpora un componente alpha que otorga transparencia al pixel).

De todas maneras centrémonos en el modelo de esta imagen de cabecera, es una imagen RGB con 8 bits para cada canal, es decir, 1 byte para cada canal (de 0 a 255), como he dicho antes, el color rojo sería representado por [255,0,0] en notación decimal.

¿Como podemos entonces ocultar texto en una imagen?

Pues la solución obviamente pasa por esconder el texto en los pixeles de la imagen, pero para hacer esto nos tenemos hacer otra pregunta.

¿Qué es un texto?

Un texto, al igual que una imagen o cualquier otro elemento digital, no es más que una secuencia de 1's y 0's. Un texto está formado por letras y cada letra está formada 8 bits, es decir, 1 byte, al menos en el sistema ASCII. Por ejemplo, para escribir mi nombre, Ángel Luis, lo que entiende el ordenador es:

181 110 103 101 108 32 76 117 105 115

Eso pasado a sistema binario es lo que realmente entiende el ordenador, por ejemplo, la Á (181) sería 10110101. Mi nombre completo en binario sería

10110101 01101110 01100111 01100101 01101100 00100000 01001100 01110101 01101001 01110011

¿Como podemos entonces ocultar texto en una imagen? (De nuevo)

Visto lo anterior lo que tenemos que hacer es intentar ocultar las letras de un texto en cada uno de los píxeles, pero tenemos una limitación, un pixel en este caso son 8 bytes, al igual que una letra, si introducimos una letra por cada pixel la imagen se distorsionaria porque eliminamos por completo un canal del pixel, por tanto, lo más sencillo una vez llegado aquí es que en vez de codificar una letra por cada pixel se va a codificar 3 bits de una letra por cada pixel, esto es, para codificar una letra necesitaremos 3 píxeles, y se va a modificar el bit menos significativo para que afecte lo mínimo posible a la imagen.

Por ejemplo, si queremos codificar la letra Á (181, 10110101) dentro de una imagen totalmente roja de tamaño 3x3.

Aquí tenemos la imagen original en rojo puro

\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}

Si lo pasamos a binario que se verá mejor, tenemos

\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}

Nota: Por simplicidad cuando es todo 0 se omite 00000000 y se pone solo 00

Nota: En negrita están los bits que van a ser sustituidos por cada uno de los bits de la letra Á (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}

En negrita ahora se aprecian aquellos bits a los que ha afectado la codificación.

En decimal esto se traduce a

\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}

Como se puede observar, al modificar el bit menos significativo obtenemos un error de \pm 1 en cada uno de los canales con respecto a la imagen original.

Esta es nuestra imagen con el texto códificado. Ahora solo queda automatizar el proceso mediante un programa informático, teniendo en cuenta una cosa, el texto tiene que estar delimitado entre una marca de inicio y marca de fin para saber donde empieza el texto y donde acaba.

Proceso de codificación del texto

Proceso de codificación del texto

En la imagen anterior se lista el directorio actual donde existen 4 ficheros, 2 programas, 1 texto y 1 imagen. A continuación se imprime el texto de text_hide por pantalla (Esto es una prueba de texto oculto en una imagen). Luego se ejecuta el programa codificador, se elige la imagen donde se va a codificar el texto (image1.bmp), el texto a ocultar (text_hide) y la nueva imagen que se generará (image1.bmp). Por último, mediante el comando diff nos dice que las 2 imágenes son distintas a nivel binario, es decir, algunos bytes son distintos en ambas imagenes, aunque a efectos prácticos para el ojo humano las 2 imágenes son idénticas.

Proceso de decodificación del texto

Proceso de decodificación del texto

En la imagen anterior se muestra el proceso de decodificación, primero pruebo a decodificar image.bmp y me dice que no tiene ningún texto oculto, a continuación pruebo a decodificar el fichero imagen1.bmp y en efecto me dice que hay un texto oculto en ella.

Este método tiene una desventaja. Si tenemos un texto de 100 letras en total necesitariamos una imagen de

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

No son especialmente muchos píxeles si los comparamos con una foto en alta definicion (1920 x 1080 píxeles), pero si que puede llegar a ser un factor a tener en cuenta en imágenes más pequeñas.

Conclusión

Así es como le regalé una foto especial a Elena que actualmente se encuentra en su mesita.photo

Esta técnica se llama Esteganografía, y consiste en ocultar mensajes dentro de otros objetos, en este caso una imagen. Esta técnica se lleva usando desde hace muchos siglos.

Recuerdo que el programa lo hice hace unos 4 años y en cuanto a prestaciones deja mucho que desear debido a la falta de tiempo que tenía. Este programa solo acepta imágenes bmp de 24 bytes por pixel (8 por cada canal). Si tuviese que reformar hoy en día este programa haría las siguientes mejoras

  • Aceptar más formátos de imágenes (jpg, png) esto se conseguiría facilmente con alguna librería de tratamiento de imágenes o framework, por ejemplo imagemagick, la clase Pixmap del framework Qt o la función PixBuf del framework GTK+. Una vez decodificada la imagen, el tratamiento sería el mismo que el mostrado aquí, las librerías anteriores solamente decodifican la imagen y la convierten en un mapa de bits (como un bmp nativo).
  • Ocultar el texto en una posición aleatoria de la imagen, actualmente se empieza a ocultar a partir de la posicion 54.
  • Comprimir el texto con algún algoritmo conocido, por ejemplo Base64.
  • Permitir encriptar el texto con algún algoritmo de encriptación, tanto de clave simetrica como de clave asimétrica.
  • Introducir un hash del texto en una parte aleatoria de la imagen que no coincida con la posición del texto, con esto podriamos comprobar que el texto no ha sido modificado.