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.
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:
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.
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:
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:
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:
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.
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.