Las variables en C son unidades de almacenamiento de datos que reservan espacio en la memoria. Hay diferentes tipos de variables. Cada tipo requiere diferentes cantidades de memoria, pero los requisitos de memoria están predeterminados. Las variables se rigen además por conjuntos de operaciones que se les aplican.

A continuación, analizaremos cómo identificar variables al analizar ejecutables. Se muestran fragmentos de código C, así como sus equivalentes en ensamblaje y cómo se usa la pila cuando se llaman subrutinas en un programa.

variables

Dependiendo de dónde se declaren, las variables son de dos tipos: variables globales y variables locales. Así es como se pueden identificar con un depurador.

Variables globales

El fragmento de código C muestra el uso de variables globales.

#incluir stdio.h

int a = 10;

vacío principal()

{

printf(«El valor de a es %dn», a);

}

Cuando se compilan, se hace referencia a las variables globales por ubicación de memoria, como se muestra en el extracto cuando se abre en OllyDbg.

EMPUJAR EBP

MOV EBP ESP

Y ESP,FFFFFFFF0

SUBESP,10

LLAMADA global_v.004015D0

MOV EAX, DWORD PTR DS: [403004] ; |

MOV DWORD PTR SS:[ESP+4],EAX ; |

MOV DWORD PTR SS:[ESP],global_v.00404000; |ASCII «El valor de a es %d»

LLAMAR JMP.msvcrt.printf ; imprimirf

NOP

DEJAR

RETENIR

El valor verificado tiene como referencia 00403004, como se muestra a continuación.

[00403004]=0000000A

0000000A es el equivalente hexadecimal de 10, que es lo que se almacena en la variable «a» en el programa C. Esto se verifica mediante un depurador.

Es necesario ejecutar el programa anterior para obtener un binario de Windows de 32 bits. Se utiliza un compilador cruzado conocido como MinGW para producir un ejecutable de Windows de 32 bits en una máquina Kali Linux. El siguiente comando se puede utilizar para hacer lo mismo.

i686-w64-mingw32-gcc global_variables.c -o global_variables.exe

Nota: El compilador cruzado para producir ejecutables de Windows no está preinstalado en Kali Linux.

El archivo ejecutable se abre en OllyDbg y el programa se ejecuta en el depurador. A continuación se muestra el resultado.

Se establece un punto de interrupción en la dirección 0040151E y el programa se ejecuta nuevamente. Cuando se alcanza el punto de interrupción, el valor del registro EAX se verifica y se muestra como 00000001, como se muestra a continuación.

Las instrucciones se realizan en un solo paso una vez, por lo que se ejecutará la siguiente instrucción.

MOV EAX,DWORD PTR DS:[403004]

Se vuelve a comprobar el valor de EAX. Debe contener el valor hexadecimal 0000000A, como se muestra a continuación.

La anterior muestra que las direcciones de memoria hacen referencia a las variables globales.

variables locales

El fragmento de código C muestra el uso de variables locales. Inicializa una variable dentro del método principal. La variable «a» es local del método principal.

#incluir stdio.h

vacío principal()

{

int a = 10;

printf(«El valor de a es %dn», a);

}

Cuando se compilan, las direcciones de pila hacen referencia a las variables locales. Esto se ve como se muestra a continuación cuando se abre en OllyDbg.

EMPUJAR EBP

MOV EBP ESP

Y ESP,FFFFFFFF0

SUBESP,p>

LLAMAR local_va.004015D0

MOV DWORD PTR SS:[ESP+1C],0A ; |

MOV EAX,DWORD PTR SS:[ESP+1C] ; |

MOV DWORD PTR SS:[ESP+4],EAX ; |

MOV DWORD PTR SS:[ESP],local_va.00404000; |ASCII «El valor de a es %d»

LLAMAR JMP.msvcrt.printf ; imprimirf

NOP

DEJAR

RETENIR

Como se muestra arriba, el valor almacenado en [ESP+1C] se está moviendo a EAX. Se verifica el valor en la pila a la que hace referencia [ESP+1C]. 1C en hexadecimal se traduce a 28 en decimal. Se comprueba el valor de 28 bytes del ESP.

El valor es 0000000A, que se tradujo al valor decimal 10. Se está moviendo al registro EAX. Una vez que se ejecuta esta instrucción, EAX debe tener el valor al que hace referencia la dirección de pila que se muestra a continuación.

Llamar a convenciones en C

El siguiente fragmento muestra cómo se utilizan las funciones en C.

#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;

}

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 código se toma del desmontaje del binario obtenido utilizando 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 arriba demuestran lo que sucede antes de que se llame a una subrutina.

Los dos argumentos representados en hexadecimal se colocan en la pila. Hex 14 y 0A se traducen a valores decimales 10, respectivamente. Estos son los argumentos pasados ​​a la función denominada test_function.

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, las dos variables locales representadas en hexadecimal se mueven a la pila. 32 en hexadecimal se traduce en 50, que es la variable «x». A continuación, se empuja el valor hexadecimal 28 a la pila. 28 en hexadecimal se traduce en el valor decimal 40, que se utiliza para inicializar la variable «y».

Conclusión

Las variables en C son marcadores de posición y espacios de trabajo dentro de la memoria. Tienen requisitos de memoria preestablecidos y están controlados por conjuntos de operaciones que se les aplican. Las variables locales y las variables globales escritas en programación C tienen referencias en lenguaje ensamblador. Se hace referencia a las variables globales mediante direcciones de memoria, mientras que a las variables locales se hace referencia mediante pila y argumentos. Las variables locales también se colocan en la pila cuando se llama a una función.

Fuentes

  1. Michael Sikorski y Andrew Honig, » Análisis práctico de malware: guía práctica para diseccionar software malicioso «, No Starch Press, febrero de
  2. Ingeniería inversa para principiantes , Dennis Yurichev
  3. Guía de ensamblaje x86 , Ciencias de la Computación de la Universidad de Virginia