Análisis de malware

Comprender las instrucciones de la pila

31 de marzo de por Srinivas

Este artículo presentará a los lectores los conceptos de ensamblaje en relación con la pila. Discutiremos conceptos básicos relacionados con la pila y varios registros, y las instrucciones utilizadas cuando se trabaja con una pila.También veremos ejemplos prácticos de cómo funcionan instrucciones comunes como PUSH y POP mediante el uso de un depurador.

¿Qué es una pila?

Una pila es una estructura de datos que se utiliza para guardar el contenido del registro para su posterior restauración, pasar parámetros a procedimientos y guardar direcciones para que los procedimientos puedan regresar al lugar correcto.

La pila opera estrictamente según la regla “Último en entrar, primero en salir”, es decir, los datos que se insertan en la pila deben extraerse de la pila en orden inverso.

puntero de pila

Un puntero de pila es un registro de CPU (ESP) que realiza un seguimiento de los datos de la pila. Es un puntero que siempre apunta a la parte superior de la pila.

La anterior muestra que el registro ESP contiene la dirección 0065FEDC, que apunta a la parte superior de la pila.

Instrucciones de pila

PUSH y POP son las dos instrucciones más comunes que se sabe que se utilizan para insertar datos en la pila y extraer datos de la pila. El siguiente ejemplo muestra el uso de instrucciones PUSH y POP cuando se llama a una subrutina.

empujar eax

llamar a subrutina

mov edx, eax

eax pop

La instrucción PUSH EAX se utiliza para preservar el valor de EAX antes de llamar a una subrutina. Esto se debe a que el valor de retorno se enviará a EAX cuando se llame a una subrutina. Una vez que se ejecuta la subrutina y se devuelve la ejecución, el valor de retorno se mantiene en el registro EDX. Finalmente, el valor original de EAX se saca de la pila y se coloca en el registro EAX.

Cabe señalar que el valor enviado se almacena en la dirección actual del registro ESP y el tamaño del registro ESP se incrementará según el tamaño del valor enviado.

Veamos un ejemplo de instrucción PUSH usando un depurador. El siguiente extracto está tomado de un programa C compilado y abierto en un depurador.

PUSH EBP ;prólogo

MOV EBP,ESP ;prólogo

.

.

.

.

.

SALIR ;epílogo

RETENIR

Comprobemos el estado del registro EBP y de la pila antes de ejecutar la instrucción PUSH EBP.

EBP 0065FF68

ESP 0065FEDC

PILA:

DATOS DE DIRECCIÓN

0065FEDC 0040138B

0065FEE4 00690DA0

0065FEE8 006914C8

0065FEEC 00000000

0065FEF0 00000000

0065FEF4 00000004

0065FEF8 00690DA0

0065FEFC 00690D5C

0065FF00 FFFFFFFF

0065FF04 F67394D0

0065FF08 C3F41E41

0065FF0C 00000000

Ahora ejecutemos la instrucción PUSH EBP y observemos los cambios en la pila.

DATOS DE DIRECCIÓN

0065FED8 / 0065FF68

0065FEDC |0040138B VOLVER a la función.0040138B desde la función.00401510

0065FEE0 |00000001

0065FEE4 |00690DA0

0065FEE8 |006914C8

0065FEEC |00000000

0065FEF0 |00000000

0065FEF4 |00000004

0065FEF8 |00690DA0

0065FEFC |00690D5C

0065FF00 |FFFFFFFF

0065FF04 |F67394D0

0065FF08 |C3F41E41

Después de ejecutar la instrucción PUSH EBP, hay algunas cosas que se cambian, como se muestra a continuación.

  1. El valor del registro EBP se coloca en la parte superior de la pila.
  2. La dirección del registro ESP se cambia de 0065FEDC a 0065FED8, lo que supone un incremento de cuatro bytes.

Ahora repasemos todas las líneas y esperemos antes de ejecutar la instrucción LEAVE. La instrucción LEAVE se compone de las dos instrucciones siguientes:

MOV ESP, EBP POP EBP

Esto significa que el valor del registro EBP debe moverse al registro ESP, y luego el valor que reside en la parte superior de la pila debe colocarse en el registro EBP cuando se ejecuta la instrucción LEAVE. Observemos el estado de EBP, ESP y la pila antes de ejecutar la instrucción LEAVE.

ESP 0065FEC0

EBP 0065FED8

PILA:

DATOS DE DIRECCIÓN

0065FEC0 0000000A

0065FEC4 00000014

0065FEC8 00000026

0065FECC 00690D5C

0065FED0 00000026

0065FED4 00690D5C

0065FED8 /0065FF68

0065FEDC |0040138B VOLVER a la función.0040138B desde la función.00401510

0065FEE0 |00000001

0065FEE4 |00690DA0

0065FEE8 |006914C8

0065FEEC |00000000

0065FEF0 |00000000

0065FEF4 |00000004

Ahora ejecutemos la instrucción LEAVE y observemos los cambios en ESP, EBP y la pila.

ESP 0065FEDC

EBP 0065FF68

PILA:

DATOS DE DIRECCIÓN

0065FEDC 0040138B VOLVER a la función.0040138B desde la función.00401510

0065FEE0 00000001

0065FEE4 00690DA0

0065FEE8 006914C8

0065FEEC 00000000

0065FEF0 00000000

0065FEF4 00000004

0065FEF8 00690DA0

0065FEFC 00690D5C

0065FF00 FFFFFFFF

0065FF04 F67394D0

0065FF08 C3F41E41

0065FF0C 00000000

0065FF10 00000000

Primero, el valor de EBP se traslada a ESP. A continuación, el valor en la parte superior de la pila se sacó de la pila y se colocó en EBP. El puntero de la pila también se redujo en cuatro bytes, lo que dio como resultado que 0065FEDC fuera el valor de ESP.

Instrucción MOV en lugar de PUSH

Si bien PUSH y POP se utilizan para insertar datos en la pila y extraer datos de la pila respectivamente, los compiladores pueden optar por utilizar otras instrucciones para realizar las mismas operaciones. Veamos un ejemplo de cómo se usa la instrucción MOV para insertar datos en la pila.

El siguiente programa en C se compila con el compilador cruzado MingW32 y se abre con OllyDbg.

#incluir stdio.h

void test_function(int arg1, int agr2);

vacío principal()

{

función_prueba(10,

}

void test_function(int arg1, int arg2){

entero x = 50;

int y = 40;

}

En el código anterior, se llama a una función llamada test_function y tiene dos argumentos con los valores 10 yHay dos variables locales inicializadas en la función llamada test_function.

El siguiente extracto está tomado del desensamblaje del binario obtenido usando el código anterior.

EMPUJAR EBP

MOV EBP ESP

Y ESP,FFFFFFFF0

SUBESP,10

Función LLAMADA.004015E0

MOV DWORD PTR SS:[ESP+4],14

MOV DWORD PTR SS:[ESP],0A

Función LLAMADA.00401535

NOP

DEJAR

RETENIR

Las líneas resaltadas en este extracto demuestran lo que sucede antes de que se llame a una subrutina.

Observe cómo los dos argumentos representados en hexadecimal se colocan en la pila mediante la instrucción MOV. Hex 14 y 0A se traducen a valores decimales 10 respectivamente. Estos son los argumentos pasados ​​a la función denominada test_function. Si te fijas, los argumentos se colocan en la pila en orden inverso. El segundo argumento se coloca en la pila, seguido del primer argumento.

EMPUJAR EBP

MOV EBP ESP

SUBESP,10

MOV DWORD PTR SS:[EBP-4],32

MOV DWORD PTR SS:[EBP-8],28

NOP

DEJAR

RETENIR

Una vez que se llama a la función, observe cómo dos variables locales representadas en hexadecimal se mueven a la pila usando la instrucción MOV. 32 en hexadecimal se traduce en 50, que es la variable “x”. El siguiente valor hexadecimal, 28, se coloca en la pila. 28 en hexadecimal se traduce en el valor decimal 40, que se utiliza para inicializar la variable “y”.

Al invertir un binario compilado, los analistas no tendrán control sobre qué instrucciones utiliza el compilador para insertar datos en la pila. Por lo tanto, es necesario conocer varias instrucciones que pueden usarse cuando se trabaja con la pila.

¡Conviértete en un ingeniero inverso certificado!

Obtenga capacitación práctica en vivo sobre análisis de malware desde cualquier lugar y conviértase en un analista certificado de ingeniería inversa. Comienza a aprender

Conclusión

En este artículo, exploramos varias instrucciones que se utilizan en relación con las pilas. Hemos visto ejemplos de cómo funcionan PUSH y POP mediante el uso de un depurador. También aprendimos que diferentes compiladores pueden usar instrucciones diferentes para realizar la misma operación. Como ejemplo, analizamos cómo se pueden utilizar las instrucciones MOV en lugar de PUSH.

Fuentes