Sin Imaginación

De todo un poco

Introducción al Cracking

April 3rd, 2012

Este artículo fue escrito para una colaboración con un amigo en un esfuerzo de tener una distribución local parecida a las famosas revistas HackXCrack.

Huelga decir que ese proyecto no avanzó (no recuerdo las razones) pero ya tenía escrito parte del artículo sobre el tema que quería abordar. A continuación la versión inédita.

Introducción al Cracking

INDICE:
0x00 Introducción
0x01 Conceptos básicos de Assembler
0x02 Ollydbg
0x03 Ejemplos
0x04 Final

0x01 Introducción

Buenas a todos, en este mini paper se explicará una breve introducción al mundo del cracking y la ingeniería inversa de modo que el lector que comienza su travesía por estas calles pueda orientarse de manera  práctica y sencilla. Tratare de escribir el contenido lo mas llano y dinámico posible para que sea fácil de entender. Hay que dejar claro que el paper esta enfocado en Windows y las nociones que se explicaran de assembler serán sobre el ensamblador  FASM de 32 bits y que trabaja con sintaxis Intel. Si aun no entienden bien esto no se preocupen, mas adelante tratare el tema pero pienso que es oportuno aclarar esto.

Pues nada, comencemos refrescando los conceptos básicos:

Los programas informáticos están escritos en lenguajes de programación  (C/C++, Visual Basic, Perl, Python, Delphy, etc.) y almacenados en lo que se conoce como código fuente, este es compilado o interpretado (depende del lenguaje) para procrear el ejecutable.

Como estoy seguro que ya todos sabrán, nuestro ordenador solo entiende secuencias de ceros y unos, por lo tanto, el código de cualquier lenguaje de programación le es indiferente, o sea no lo puede entender así tal cual. Aquí es donde entra el  lenguaje máquina que consta de secuencias de estos 0 y 1 que el microprocesador entiende directamente.

El procesador trabaja con código binario (ceros y unos) ya que se opera con transistores que sólo pueden estar de dos formas: abierto o cerrado, 1 o 0 Un bit es una unidad de información que sólo puede representar dos valores 1 ó 0. Dado que es muy difícil e incómodo representar cualquier cosa en binario, el sistema más usado para la representación de datos y valores es el Sistema Hexadecimal.

Un byte (8bits) es la unidad básica en la que se mide la información. Cada una de las celdillas lógicas en que se divide la memoria del ordenador, tanto RAM como ROM, tienen capacidad para un byte. Aparte del bit y byte se suelen utilizar a menudo word y dword. En resumen:

1 bit  =  0 ó 1;  1 byte   =   8 bits;   1 word  =  2 bytes;   1 dword  =  4 bytes.

Ahora, aclaremos conceptos que son muy utilizados en el ámbito del cracking:

  • Ingeniería Inversa: es el arte de examinar algún programa (del cual no se posee su código fuente) con el único objetivo de conocer su estructura y funcionamiento hasta el punto de poder imitarlo a la perfección. El termino no solo se aplica al área de software, también se puede emplear  a cualquier otra cosa si se posee el mismo fin.
  • Cracking: consiste en la modificación de algún programa  con el fin de cambiar su comportamiento y obtener un beneficio ilícito. En la mayoría de los casos esto varía desde conseguir el número serial de un software hasta saltarse un tipo de protección que implemente un programa.
  • Depurador o Debugger: es el programa que se utiliza para probar de manera detallada (paso x paso) el funcionamiento de un programa. Más simple, nos permite saber que esta sucediendo dentro de otro programa mientras se esta ejecutando. Usualmente es utilizado por los programadores para facilitar la corrección de errores en la creación de algún software y en el cracking es la herramienta esencial para alcanzar nuestro objetivo. Entre los depuradores más comunes esta: el GDB, Ollydbg, Cheat Engine, Ida Pro y el SoftICE.
  • Desensambladores: es el programa encargado de traducir las instrucciones de un ejecutable de código maquina (binario [ceros y unos]) a ensamblador (ASM). En ocasiones el código obtenido puede no estar de la manera esperada ya que puede estar empaquetado o comprimido. Ejemplo de desensambladores: Ollydbg, Cheat Engine, Ida.
  • Empacadores o Packers: son programas que protegen, ofuscan, comprimen o reducen una aplicación.  Ejemplos: UPX, Armadillo, Themida.
  • Editor Hexadecimal: es un programa que nos permite ver y modificar un ejecutable en el sistema hexadecimal. Es muy utilizado para tareas sencillas como modificar un número específico de bytes. Modificando los bytes también se modifican las instrucciones que estén asociadas a ellos, influyendo en su comportamiento. Ejemplo: 010 Editor, Hex Editor.
  • Compilador: programa encargado de traducir el código fuente de un lenguaje de programación al código maquina.
  • Dumper: es un programa que puede volcar o extraer datos de la memoria de un proceso. Tomar rangos o posiciones aleatorias y vertirlos en otro lado para ver y analizar su contenido.
  • Dirección de memoria: es un identificador para una localización de memoria. Las direcciones son asignadas por el sistema operativo a cada programa en ejecución. El rango de valores naturales que pueden ser almacenados en un sistema de 32 bits es de 0 hasta 4.294.967.295 direcciones.
  • Dirección HardCodeada: es usa dirección encontrada a mano, debido a que pude variar dependiendo del sistema operativo o la versión de algún software.
  • Offset: es una dirección dentro de un binario, que indica un número de bytes.
  • Interfaz de programación de aplicaciones o API: es el conjunto de funciones y procedimientos  que ofrece una librería para ser utilizado por otro software.

Teniendo esos conceptos claros solo nos falta una breve introducción de la memoria y los procesos. Empecemos por lo más básico:

Un programa, aplicación, fichero, o ejecutable son los términos empleados para referirse a  un archivo con un conjunto de instrucciones compactadas en binario, esto ya lo dejamos claro arriba. En cambio un proceso es un programa en ejecución con su espacio de memoria en donde posteriormente se cargaran las instrucciones contenidas en el archivo. Para cada proceso se carga un espacio de memoria diferente.

Ahora ¿qué sucede? Que cuando se coloca un programa en memoria para ejecutarlo, no se coloca de la misma manera en la cual esta en el archivo. Cuando esta cargado en la memoria el contenido del archivo se separa y se alinea en secciones. Esto no sucede en el archivo puesto que todo está más pegado. Uno encima de otro.

Por lo tanto una dirección de memoria y una dirección del archivo son totalmente diferentes. Cuando se habla de memoria física o RAW se esta refiriendo al archivo aun no cargado en memoria, en cambio cuando se habla de memoria virtual es el programa ya cargado en memoria para ejecutarlo.

  • RVA = VA – ImageBase;
  • VA    =RVA + ImageBase;
  • RAW = RVA – (VirtualAddress PointerToRawData)
  • VirtualAddress          =  dirección en donde comienza la sección en la memoria.
  • PointerToRawData   =  dirección en donde comienza la sección en el archivo.

Después de esta larga y tediosa información que es vital para entender lo que viene, pasemos a explicar los principios básicos de ensamblador. Creo que con lo que acabo de explicar ya es suficiente para crear una base sobre el tema.

0x01 Conceptos básicos de Assembler

El lenguaje ensamblador, assembler  (o assembly) en ingles o simplemente ASM en siglas, es un lenguaje de bajo nivel que tiene la capacidad de interactuar de manera directa con la arquitectura de el procesador. Al momento de programar en ensamblador se debe tener en cuenta la arquitectura bajo la cual se este trabajando debido a que si el programa se crea para una determinada plataforma (x86)  no servirá en otra (x64).

¿Por qué es importante ensamblador? Pues por su increíble potencia, rapidez y versatilidad al momento de realizar tareas que requieran una interacción mas cercana con el procesador. Además como vimos mas arriba es imprescindible en el cracking y la ingeniería inversa.

Pues bien, en ensamblador existen lo que se llaman los registros. Los registros son pequeñas zonas en donde se almacenan datos de manera temporal. Son prácticamente las variables del procesador.

Existen diferentes tipos de registros que tienen diferentes propósitos, están:

Registros de propósito general:
Registro Puntero de Instrucciones (EIP)
Registro de Estado o de Señalizadores (EFLAGS)
Registros de Segmento

Registros de propósito general: Son 8 registros capaces de trabajar con información de 32 bits en su mayor tamaño. Pueden usarse tanto para almacenar datos como direcciones. Su nombre empieza por “E” que significa extendido. Son los siguientes:

  • EAX: Acumulador.
  • EBX: Base.
  • ECX: Contador.
  • EDX: Datos.
  • ESP: Puntero de pila.
  • EBP: Puntero de base.
  • ESI: Índice fuente.
  • EDI: Índice destino.

Nota: en la programación de 64 bits se agregan otra forma para representar los registros: rax, rbx, rcx, rdx, rdi, rsi, rip, rbp, rsp.

Estos registros, son capaces de trabajar con información de 32 bits, pueden manejar datos de 16 bits y cuatro de ellos pueden manejar información de 8 bits. Cuando se accede únicamente a los 16 bits de menos peso, se designan por AX, BX, CX, DX, SP, BP, SI, DI, respectivamente. A los registros AX, BX, CX y DX se puede acceder a sus registros AL, BL, CL y DL cuando se accede al byte de menos peso y AH, BH, CH y DH cuando se accede al byte de más peso.

Ejemplo:

  • RAX: registro de 64 bits =  8 bytes. Ej. 0000000011223344
  • EAX: registro de 32 bits =  4 bytes => Ej. 11223344
  • AX:    registro de 16 bits =  2 bytes de menor peso de EAX => Ej. 3344
  • AH:    registro de 8 bits  =  1 byte de mayor peso de AX => Ej. 33
  • AL:    registro de 8 bits   =  1 byte de menor peso de AX => Ej. 44
  • EAX: (EAX, AX, AH, AL).
  • EBX: (EBX, BX, BH, BL).
  • ECX: (ECX, CX, CH, CL).
  • EDX: (EDX, DX, DH, DL).
  • ESP (ESP, SP) y EBP (EBP, BP)

Estos registros son todos de propósito general porque son usados para desarrollar las tareas más comunes como almacenar datos o realizar las operaciones aritméticas. El de más importancia es el EAX porque la mayoría de las API tienen su valor de retorno en este registro.

Para entender los últimos dos registros debemos conocer que es la pila. La pila es una estructura de tipo LIFO (ultimo en entrar, primero en salir) en donde se almacenan todo tipo de datos de manera temporal para luego utilizarse en alguna operación. Nos podemos imaginar la pila como un montón de libros uno encima del otro. En donde solo puedo tomar el que esta en la cima para leerlo, los que están mas abajo no puedo tomarlos puesto que debo quitar el que esta mas próximo primero.

A esto hace referencia estos dos registros, al dato que esta en la cima (ESP)  y el que esta mas abajo (EBP).

Registro Puntero de Instrucciones (EIP): (EIP IP) Es el registro que apunta a la siguiente instrucción a se ejecutada.

Registro de Estado o de Señalizadores (EFLAGS): Consta de 32 bits, de los cuales la mayoría son señalizadores de estado. Esto se vera bien mas adelante cuando llegemos a la parte del Ollydbg.

Registros de Segmento: son registros que apuntan a una determinada zona de memoria ya sea la pila, el código o los datos. Para controlar los segmentos, se dispone de varios registros de 16 bits. Estos son:

  • -CS: Contiene la dirección al segmento de código, es decir, a las instrucciones.
  • -DS: Contiene la dirección al segmento de datos del programa.
  • -SS: Contiene la dirección del segmento de la pila.
  • -FS y GS: son registros de propósito auxiliar.
  • -ES: es un segmento extra.

En ensamblador existen las instrucciones con las cuales el lenguaje opera y funciona. La mayoría tiene la siguiente estructura.
Instruccion destino, fuente.

Las más comunes son:

MOV, mueve datos de un sitio a otro.

Ejs:

mov eax,0x7.                Se mueve el valor 7 al registro eax.
mov ebx, [eax].             Se mueve el contenido del valor del registro eax a ebx.
mov [0x1007000],eax   Se mueve el valor de eax a la direccion de memoria 0x1007000.

Nota: no se puede mover un dato entre dos direcciones de manera directa, para eso se debe pasar primero al algún registro.

ADD, suma un número a un registro.

Ej: add eax, 3.

SUB, resta un número a un registro.

Ej: sub eax, 3.

MUL, multiplica con eax el multiplicando.

Ej: mov eax, 0x3

mul eax         ;eax valdrá 0x9

DIV, divide con edx.eax el divisor.

Ej:  mov edx, 0x9

mov eax, 0x9

div   eax        ;eax valdrá 0x1

INC, incrementa el operando en 1.

Ej: inc eax

DEC, decrementa el operando en 1.

Ej: dec eax

PUSH, mete un valor a la pila.

Ej: push, eax     ;pone el valor de eax en la pila

PUSHPAD, mete todos los registros a la pila.

Ej: pushpad

POP, saca un calor de la pila.

Ej. pop eax     ;toma el valor apuntado por esp y lo pone en eax

POPAD, saca los últimos valores de la pila y los pone en los registros.

Ej. popad         ;equivale a pop edi,esi,ebp,esp,ebx,edx,ecx,eax

JMP, salta hacia la direccion que se indique.

Ej.  mov eax, [0x01005FD2]

jmp eax        ; se salta hacia 01005FD2

jmp [eax]      ; se salta hacia el contenido de 01005FD2

A este salto se le conoce como salto incondicional porque procede sin importar nada. En cambio existen algunos  saltos que conllevan una condición para poder proceder.

  • JE    – Salta si los números comparados son iguales.
  • JNE – Salta si los valores no son iguales.
  • JG    – Salta si Valor1 es mayor que Valor2.
  • JGE – Salta si Valor1 es mayor o igual que Valor2.
  • JB    – Salta si Valor1 es menor que Valor2.
  • JBE – Salta si Valor1 es menor o igual que Valor2.

CMP, compara dos valores y es el complemento para los saltos condicionales y otras instrucciones.

Ej: mov eax, 6

cmp  eax,4

je  01005FD2   ; Aquí si eax es igual a 4 saltaría a la direccion de memoria

dec eax             ; pero como no es el caso se ignora el salto y sigue la otra instrucción

CALL, llama a una función, prácticamente guarda en la pila la próxima direccion a ejecutarse después del CALL para tener un punto de retorno y luego salta hacia la direccion de la función, se ejecuta y cuando encuentra un ret, salta hacia la direccion que se había guardado en la pila (que es la direccion próxima desde donde se llamo al CALL).

Cuando se hace uso de la instrucción CALL para llamar a una función primero se colocan dentro de la pila los parámetros de la función.

Ej: push 0

call    ExitProcess    ; con esto se llama a la API ExitProcess y se termina la ejecución.

Estas son las nociones mas básicas del lenguaje con esto ya están listos para adentrarse en temas más profundos y empezar con el cracking. : )

0x02 Ollydbg

Ollydbg es un debugger de 32 bits bits para Windows. Es nuestra herramienta crucial para la ingeniería inversa y el cracking porque es  práctico, fácil y versátil además de ser gratis.

……

Hasta ahí escribí.

 

Hasta pronto.

Leave a Reply

Powered by WordPress. Theme by Sash Lewis modified by 0aps.