Tal como venimos compartiendo con ustedes durante las últimas semanas, hoy tenemos una nueva entrega de esta seguidilla de posts sobre la parte teórica, un poco pesada pero muy útil sobre cómo funcionan los sistemas y de qué manera nos sirven para el análisis de códigos maliciosos. En este post vamos a mencionar cómo se compone una instrucción, qué son y para qué sirven los registros los flags y punteros de la arquitectura x86.
Cuando hicimos una introducción a la arquitectura x86 hablamos acerca de las instrucciones y los opcodes que las representan. Ahora veremos un poco más en detalle a los operadores que pueden utilizar esas instrucciones y algunas cosas más.
Operadores
Los operadores son utilizados para identificar los datos utilizados por una instrucción. Entre los diferentes tipos de operadores existen podemos diferenciar tres:
- Operadores Inmediatos: Son valores fijos, que no cambian durante la ejecución del programa y hacen referencia a un número, cadena de texto valor booleano, etc.
- Operadores de registro: En este caso el operador que se adjunta con una instrucción uopcode hace referencia a uno de los registros específicos del CPU como son el caso de EAX, ECX y demás.
- Direcciones de memoria: El tercer tipo de operador que podemos tener dentro de un sistema hace referencia a una dirección de memoria y el contenido que se aloja en él. Para saber que estamos halando de una dirección de memoria y no de un valor, dentro de los desensambladores, las direcciones de memoria suelen ir cerradas entre corchetes []
Registros
Un registro es un espacio de almacenamiento disponible para el CPU. Una de las principales características de estos, es que pueden ser accedidos más rápido que cualquier otro dispositivo de almacenamiento de una computadora. Los procesadores x86 cuentan con una serie de registros disponibles para utilizar como almacenamiento temporal para variables, valores y demás información que utilizan durante la ejecución de instrucciones como así también punteros a secciones de memoria como la pila. Podemos mencionar 4 categorías diferentes:
- Registros generales
- Segmentos de registros
- Flags (banderas de estado)
- Instruction Pointer (IP), puntero a la próxima instrucción a ejecutar.
En una arquitectura x86, todos los registros tienen un tamaño de 32 bits, sin embargo se pueden referenciar tanto los de 32 como de 16 bits. Por ejemplo, ECX hace referencia a los 32 bits del registro, sin embargo, CX solo referencia a los 16 bits de la parte baja.
Registros generales
Normalmente, los registros generales almacenan datos o direcciones de memoria y son utilizados de manera intercambiable para lograr que se ejecuten las instrucciones del programa. Algunos de estos registros generales son utilizados para funciones específicas. Por ejemplo, para realizar multiplicaciones o divisiones se utilizan los registros EAX y EBX.
Flags
El registro que nombramos como EFLAG es un registro de estado. En esta arquitectura, tiene una longitud de 32 bits y cada uno de sus bits es una bandera. Según el valor de 0 o 1 de cada bit serán utilizados para controlar las operaciones de la CPU luego de la ejecución de una instrucción. Entre los registros más importantes para remarcar en relación al análisis de malware podemos enumerar:
- ZF (Zero Flag): Este bit se activa cuando el resultado de una operación es igual a cero.
- CF (Carry Flag): Este bit se activa cuando el resultado de una operación es muy grande o muy pequeño para el operador de destino.
- SF (Sign Flag): Según si el resultado de una operación es un valor positivo o negativo. Si el valor es positivos se queda en cero y es uno en caso contrario.
- TF (Trap Flag): Este flag se utiliza para depurar (debugging) un programa. En caso de que esté activo el procesador ejecutará una instrucción a la vez.
Un ejemplo práctico, la instrucción mov
De todas las instrucciones que se pueden ejecutar dentro de un procesador existen algunas de ellas que son las más comunes y habituales como así también las menos complejas. Una instrucción muy común es la instrucción mov cuya función es mover los datos desde una ubicación a otra. Esta instrucción utiliza dos operadores para realizar su trabajo. El primero es el destino al cual se quieren mover los datos y el segundo está relacionado con la dirección u ubicación de destino.
Dentro de los operadores con los que se puede utilizar la instrucción podemos incluir registros o direcciones de memoria directamente. A continuación podemos observar algunas de los usos más comunes de esta instrucción:
Para probar estas instrucciones que estamos demostrando aquí es bastante sencillo, tomando cualquier máquina con un debugger como el OllyDbg o Immunity Debugger podemos sobrescribir una instrucción y comenzar a jugar moviendo los datos de un lado a otro del sistema:
En la imagen anterior podemos ver cómo luego de que se ejecutó la instrucción “mov ebp, 0×42” el valor del registro EBP se ha actualizado al nuevo valor y queda resaltado en celeste. Este caso práctico y sencillo, que ustedes pueden probar en cualquier máquina utilizando un debugger, de igual manera que se podemos probar cómo funciona esta instrucción también podemos jugar con muchas instrucciones más como add, sub, mul, call, etc. A medida que continuemos avanzando con esta serie de post veremos algunos de los usos que los cibercriminales utilizan para atentar contra la seguridad de los sistemas.
Imagen de Smial en Wikimedia. Licencia CC0 1.0