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.
- El valor del registro EBP se coloca en la parte superior de la pila.
- 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
- Randal Hyde, “El arte del lenguaje ensamblador”, No Starch Press, marzo de
- Michael Sikorski y Andrew Honig, “Practical Malware Analysis”, No Starch Press, febrero de
- Ingeniería inversa para principiantes , Dennis Yurichev