Análisis de malware

Construcciones de código importantes en lenguaje ensamblador: conceptos básicos

8 de abril de por Ninja de seguridad

Este artículo representa una continuación del curso de análisis de malware que comenzamos anteriormente con la estructura completa de PE y ELF. Otro concepto importante que a menudo viene al rescate para juzgar qué está haciendo el programa malicioso es a través de la comprensión del código ensamblador y cómo se ven varias construcciones de código en binario. En este artículo, aprenderemos cómo reconocer construcciones de código en ensamblador.

Nota: Este artículo asume que la audiencia tendrá una comprensión adecuada del lenguaje ensamblador. Si no, no te preocupes, intentaré explicarte las cosas de la forma más sencilla.

Alcance de las variables

Las variables se declaran en código para contener valores de constantes, cadenas, etc. El alcance de estas variables puede ser local o global. Las variables locales tienen significado solo dentro de una función, mientras que las variables globales se pueden usar en cualquier parte de la lógica del código. Cuando analizamos un código descifrado, todas las referencias hechas a las variables globales son a alguna dirección de memoria, mientras que a las variables locales se hace referencia con un desplazamiento al registro ebp. Para aquellos que no están familiarizados con los prólogos del lenguaje Assemble, cada vez que una nueva función comienza a ensamblar instrucciones como

  • empujar ebp
  • mov ebp, esp
  • ebp conun marco de pila para nuevas funciones y la segunda instrucción establecerá ebp como base. Dado que la pila crece hacia direcciones más bajas, lo que significa que se puede acceder a las variables locales como mov eax, [ebp-4]; lo que moverá lo que esté a 4 bytes más abajo para ebp a eax. Ahora, si llegamos a las variables globales, dado que no son locales para ninguna función, significa que hacer referencia a ellas con ebp no tendrá sentido, por lo que se refieren a la dirección de memoria, por ejemplo, mov eax, dword_50CF60, donde la variable global está en 0x50CF60.

    Sentencia condicional

    La declaración If se usa con mayor frecuencia en la lógica del código. A continuación se muestra un uso muy básico de la declaración If:

    Y a continuación se muestra el lenguaje ensamblador para esto.

    mov [ebp + var-4],1

    movimiento [ebp+var_8],2

    mov eax,[ebp+var_4]

    cmp eax,[ebp+var_8]

    jnz corto loc_50101B

    push offset aequalsb;”a y b son iguales”

    ……

    Empuje el desplazamiento anotequalb; “a y b no son iguales”

    ……

    Observe el uso combinado de las instrucciones cmp y jnz-compare’ y ‘jump if no zero’ que corresponden a if(a==b) en el código. La instrucción cmp realiza la operación de instrucción sub-resta internamente. Por lo tanto, significa comparar ambas variables y, si no son iguales, saltar a esta ubicación en la memoria e imprimir la cadena “a y b no son iguales”. De lo contrario, si las variables son iguales, se omitirá la instrucción jnz y se imprimirá la cadena “a y b son iguales”. En el caso de declaraciones if anidadas, verá múltiples cmp,jnz/jz, algunas cadenas de impresión o cualquier otra operación seguida de otra instrucción cmp o directamente.

    Bucles

    Las declaraciones de bucle se utilizan en la lógica del código para iterar alguna operación y ejecutar alguna instrucción hasta que se cumpla alguna condición. Los bucles For y while se utilizan con más frecuencia en la lógica del código.

    El bucle For busca 4 cosas; inicialización, comparación, ejecución, incremento/decremento.

    para(int i=0;i10;i++)

    {

    Printf(“El valor actual de i es %dn”,i);

    }

    Entonces, en el ensamblaje también, bucle para estos 4 componentes.

    movimiento [ebp+var_4],0

    jmp corto loc_102345

    loc_987654

    movimiento eax, [ebp+var_4]

    añadir eax,1

    mov [ebp+var_4],eax

    loc_102345

    cmp [ebp+var_4],Ah

    jge corto loc_23456

    mov ecx,[ebp+var_4]

    empujar ecx

    push offset iValue; “El valor actual de i es %dn”

    llamar a printf

    agregar esp, 8

    jmp loc_987654

    Como podemos ver en el código ensamblador anterior, la primera instrucción se usa para la inicialización, luego se realiza un salto para comparar el valor con 10 (Ah), si es mayor o igual a 10, saltará a loc_ 23456, de lo contrario imprimirá el valor de I. y luego salte a loc_987654 e incremente el valor de i. Luego, el valor se compara nuevamente (es (i=1) =( i=10)). De lo contrario, el valor se imprime y se incrementa nuevamente. Todo este proceso continúa.

    Los bucles while se encuentran comúnmente en el código, ya que son más fáciles de rastrear . Por ejemplo

    int i=0;

    mientras(yo10)

    {

    printf(“el valor actual de I es %dn”,i)

    yo ++;

    }

    ensamblar lenguaje

    Movimiento [ebp+var_4], 0

    Jmp corto loc_12345

    Loc_123456 :

    movimiento eax, [ebp+var_4]

    añadir eax,1

    mov [ebp_var_4],eax

    Loc_102345 :

    cmp [ebp+var_4],Ah

    jge corto loc_234567

    mov ecx,[ebp+var_4]

    empujar ecx

    push offset iValue, “el valor actual de I es %dn”

    añadir esp,8

    jmp loc_1023456

    Como podemos ver en este código ensamblador para el bucle while, el código es similar al bucle for.

    Declaraciones de cambio

    Los programadores suelen utilizar cajas de conmutación y toman decisiones en función del valor de bytes. Por ejemplo:

    Cambiar(yo)

    {

    Caso 1:

    Printf(“El valor actual de I es %dn”,i+1);

    romper;

    Caso 2:

    Printf(“El valor actual de I es %dn”,i+1);

    romper;

    Caso 3:

    Printf(“El valor actual de I es %dn”,i+1);

    romper;

    Caso 4:

    Printf(“El valor actual de I es %dn”,i+1);

    romper;

    por defecto:

    romper;

    }

    En lenguaje ensamblador, esto a menudo parece una serie de declaraciones If donde se utilizarán muchas instrucciones cmp y jmp, como se indicó anteriormente. Este es el caso cuando los casos no están en orden como el caso 1, caso 12, caso 17, etc. En este caso habrá mucha estructura de declaraciones if –else en el código ensamblador. En el caso de que la variable siga un orden continuo como el caso 1, caso 2, caso 3, etc., los cumplidores optimizan inteligentemente el código como se muestra a continuación:

    Mov ecx,[ebp+var_4]

    Sub ecx,1

    cmp [ebp+var_8],3

    ja loc_12345

    mov edx,[ebp+var_8]

    jmp corto loc_ 987650 [edx*4]

    loc_234564 : _

    …..

    Jmp loc_ 12345 :

    loc_234565 : _

    …..

    Jmp loc_ 12345 :

    loc_234566 : _

    …..

    Jmp loc_ 12345 :

    loc_234567 : _

    …..

    Jmp loc_ 12345 :

    loc_12345 : _

    // código de limpieza de pila

    loc_ 987650
    desplazamiento loc_ 234564 //saltar tabla

    posición desplazada_ 234565

    posición desplazada_ 234566

    posición desplazada_ 234567

    Entonces, ¿qué ha pasado aquí? Primero, hice un código de colores para una dirección de memoria similar (¡espero que ayude!). Inicialmente, la variable de caso, por ejemplo ‘i’, se almacena en ecx y luego se reduce en 1. ¿Por qué? Aquí viene el concepto de tablas de salto, es decir, para declaraciones de casos en orden, el compilador optimiza el código mediante la construcción de tablas jmp que son una matriz para ubicaciones de memoria para varias declaraciones de cambio. Entonces, ecx se reduce porque se usará como un puntero de desplazamiento para saltar a la tabla que comienza en 0. La variable de caso más grande se compara primero porque en ese caso irá directamente al caso predeterminado si está presente o fuera de la estructura. Para otras variables, se establecerán como un desplazamiento para saltar a la tabla.

    ¡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

    Estas son estructuras muy básicas que se pueden reconocer en el código. En la segunda parte de esta serie, analizaré estructuras más complejas como matrices, estructuras y listas vinculadas.

    Agregue un comentario

    Su dirección de correo no se hará público. Los campos requeridos están marcados *