Diseccionando binarios - 0x02 Instrucciones básicas de x86 Intel

Publicada en Publicada en Reversing

Muy buenas a todos, me gustaría documentar un conjunto de instrucciones assembly en x86 que me he ido encontrando. Recomiendo la lectura de las dos entradas anteriores (0x00 y 0x01):

Diseccionando binarios - 0x00 Introduccion

Diseccionando binarios - 0x01 Introduccion

Transferencia de datos

Comenzamos con la instrucción mov. Mueve el valor 0 como contenido de la variable local_4h de 4 Bytes (Integer/Dword):

mov dword [local_4h], 0 

Mueve el contenido o el valor de la variable local al registro EAX:

mov eax, dword [local_4h] 

Mueve el contenido o el valor del registro EAX a la variable local:

mov dword [local_4h_2], eax

Mueve una dirección de memoria al Top del Stack Frame o ESP. Previo a llamada de una función printf() como argumento por ejemplo. Recordamos que antes de llamar a una función es necesario pasarle los argumentos si tiene:

mov dword [esp], 0x8048544

Mueve el primer Byte de la variable(su contenido) al registro EAX. Si el contenido de la variable es 8000, rellenaria los bits sobrantes por la izquierda y como en esta instrucción se tiene en cuenta el signo, rellenaría con FFFF al ser negativo si es positivo con 0000. Por tanto al ejecutar la instrucción quedaría el valor de EAX=FFFF8000:

movsx eax, [local_24h]

Es igual que el anterior pero sin tener cuenta el signo por lo tanto el valor de EAX=00008000, tomándose como si fuese positivo:

movzx eax, [local_24h]

Mueve los últimos 8 Bits(AL) a EAX(Recordamos que este registro es de 32 Bits):

movsx eax, al

En este caso lo que hace es mover la dirección de memoria de la variable [local_4h] o [ebp-4] = ebp – 0x4 situada en la pila a EAX:

lea eax, [local_4h]

Manda o pushea el operando hacia la pila de los registros FPU [ST(i)]:

fld [local_4h] o [ebp+var_4]

Esta instrucción intercambia los valores de dos registros. El valor de EAX pasara a ECX y viceversa:

xchg eax,ecx

Guarda el contenido de los registros en la pila. Equivale a push EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI:

pushad

Extrae los valores de la pila a los registros. Es el proceso inverso y equivale a pop EDI,ESI,EBP,ESP,EBX,EDX,ECX,EAX:

popad

Instrucciones lógicas

Operación bit a bit de los registros o el contenido de una dirección de memoria. 0 y 0=0, 0 y 1=0, 1 y 0=0, 1 y 1=1:

and eax,ecx

Operación bit a bit de los registros o el contenido de una dirección de memoria. 0 y 0=0, 0 y 1=1, 1 y 0=1, 1 y 1=1:

or eax,ecx

Operación bit a bit de los registros o el contenido de una dirección de memoria. 0 y 0=0, 0 y 1=1, 1 y 0=1, 1 y 1=0:

xor eax,ecx

Operación bit a bit del registro o contenido de una dirección de memoria. Invierte el valor siendo 0=1 y 1=0:

not eax

Test pone la ZF=1 cuando el resultado de la operación AND entre dos registros es 0. También (AF, CF, OF, PF, SF, ZF):

test eax, eax

Comparación y saltos condicionales

Comparación entre el valor en hexadecimal 0xa(10 en hexadecimal) con la variable, depende del resultado activará las eflags para el posterior salto condicional:

cmp dword [local_4h], 0xa

Comparación entre un valor decimal y la variable:

cmp dword [local_4h], 9

Salta si es más grande o salta si no es menor o igual (ZF=0 y SF=OF). Si salta veremos en radare2 la bifurcación en verde(True), sino en rojo(False):

jg 0x8048544

Salta si el PF=1, que se activa cuando el resultado de la comparación tiene una cantidad par de unos. Por ejemplo si el resultado es 4, en binario corresponde a 100 y como solo tiene un uno, no se activaría la flag y por consiguiente no realiza el salto:

jp 0x8048544

Salta si el PF=0, sea siendo la paridad impar justo lo contrario a lo anterior:

jnp 0x8048544

Salta si no es igual o no es cero (ZF=0). Podría ser una estructura de control "if  (i==9)":

jne 0x8048544

Salta cuando hay overflow activando la OF=1:

jo 0x8048544

Salta cuando no hay overflow estando la OF=0:

jno 0x8048544

Salta si es menor y tiene en cuenta el signo de los números. Se activa la SF=1 y OF=1:

jl 0x8048544

Salta si es menor o igual o menor o salta si no es mas grande (ZF=1 o SF!=OF):

jle 0x8048544

Salta si es más grande o igual. (SF=OF):

jge 0x8048544

Salta si el ZF=1, y no salta si esta a cero. Un ejemplo podría ser el introducir un serial(por el usuario) quedándose almacenado en un registro y realizando la comparación (cmp) con otro registro para ver si son iguales, es decir, su resta sería igual a cero y se activaría la ZF entrando en el salto condicional:

jz 0x8048544

Salta si la comparación da un resultado negativo. La SF=1:

js 0x8048544

Salta si la comparación da un resultado positivo o lo opuesto a lo anterior. La SF=0:

jns 0x8048544

Salta si es más bajo como resultado de la comparación. Si es más bajo el primer operando con respecto al segundo saltaría. Se activa la CF=1 y se sobre entiende que el resultado es negativo. No se tiene en cuenta el signo de los números:

jb 0x8048544

Salta si la CF=0, siendo el resultado positivo de la comparación ya que el primer operando es mayor al segundo:

jnb 0x8048544

Salta si es más bajo o igual. Se activa las CF=1 y ZF=1:

jbe 0x8048544

Salta si la CF=0 y ZF=0. Lo opuesto a lo anterior:

jnbe 0x8048544

Salto a la dirección de memoria especificada:

jmp 0x8048544

CALL y RET

Llama a una función o una parte del programa o subrutina. Una vez termine de ejecutarse, continuará justo por la instrucción siguiente al call. Cuando entra en la subrutina, se pushea la dirección de retorno(ret) en la pila para que el programa sepa a donde tiene que volver cuando ejecute la instrucción ret:

call sym._printf

Esta instrucción es la finalización de la función o subrutina:

ret

Instrucciones aritméticas

Incrementa el valor del registro EAX. Podemos interpretarlo como el incremento del contador de un bucle for en 1. "i++":

inc dword [eax]

Decrementa el valor del registro EAX:

dec dword [eax]

También se puede incrementar o decrementar valores de direcciones de memoria:

dec dword ptr ds:[401000]

Incrementa el valor del registro EAX en 3. "i+=3":

add dword [eax], 3

Suma de los registros EAX y EDX, almacenándose en este último:

add edx, eax

Suma de ambos operandos y se le suma además la Carry Flag almacenandose en el primer operando:

adc edx, 5

Es similar a realizar XCHG y ADD en una sola instrucción. XCHG lo que realiza es intercambiar los contenidos de los registros. Por tanto en este caso, primero se intercambia el contenido y luego se suma y queda almacenado en EAX:

xadd eax,ecx

Resta de los registros EAX y EDX, almacenándose en este último:

sub edx, eax

Resta de ambos operandos y se le resta además la Carry Flag almacenandose en el primer operando:

sbb edx, 5

Multiplicación sin considerar los signos. Siempre se considera como operando el registro EAX por tanto multiplicará ECX por EAX y guardará el resultado en EDX y EAX. Si los bytes que se almacenan en EAX llega a su limite, almacenará el resto de bytes en EDX. También pueden multiplicarse el contenido de direcciones de memoria:

mul ecx

División sin considerar el signo y guardando el resultado en EDX y EAX:

div ecx

Divide EDX:EAX por ECX y el resultado va a EAX y el resto a EDX. Siempre antes de IDIV se usa el comando CDQ para preparar los registros EDX y EAX para la misma:

cdq
idiv ecx

Convierte a otro tipo de datos el float y lo saca del stack [ST(i)] a var_8:

fistp [local_8h] o [ebp+var_8]

Divide st(1) por st(0):

fdivp st(1), st

Desplaza hacia la izquierda 2 Bits a EAX. Es equivalente a EAX*4:

shl eax, 2

Prólogo y epílogo de una función

Función prólogo:

  • Coloca el puntero base en la pila o el Saved EBP.
  • Mueve el contenido del puntero de pila al puntero base con el objetivo de colocar a este último en el Top del stack.
  • Y finalmente resta el valor en hexadecimal 0x18 al puntero base, con el fin dejar espacio en la pila disponible para las variables locales.
push ebp
mov ebp, esp
sub esp, 0x18

El prólogo de la función carga un valor al marco de la pila entre la dirección de retorno y las variables locales. Este valor “canary” es un número aleatorio de 4 Bytes(dword) elegido cuando el programa comienza:

mov eax, dword gs:[0x14]
mov dword [local_ch], eax

El epílogo asegura que el valor está intacto mediante una operación XOR y un salto condicional. Si no es así, probablemente ocurrió un desbordamiento de búfer (o error) y el programa se aborta mediante la llamada a la función __stack_chk_fail. El valor “canary” lo obtiene del offset(desplazamiento) 0x14 del registro gs para una arquitectura de 32 bits y lo mueve al registro EAX. Por lo tanto, el “Stack Canaries” funcionan modificando las regiones de prólogo y epílogo de cada función para colocar y comprobar un valor en la pila, respectivamente:

mov eax, dword [local_ch]
xor eax, dword gs:[0x14]
je 0x80486d0
call sym.imp.__stack_chk_fail
leave
ret

Salida de una función o epílogo:

leave
ret

NOP

No operación. No provoca ningún cambio en la pila, registros o en memoria. Un uso sería anular cualquier instrucción no realizando nada:

nop

 

En la próxima entrada seguiremos Reverseando y aprendiendo, pero era importante antes de continuar saber instrucciones básicas en assembly y así interpretar mejor el análisis estático del mismo o cuando estamos debuggeando. Hasta la próxima entrada, un saludo.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *