Reversing guiado de una aplicación de Windows

Reversing guiado de una aplicación de Windows


1. Introducción al ejercicio de reversing.

Para llevar a cabo este ejercicio de reversing, hemos optado por el uso de las herramientas “IDA PRO”, y “x32dbg”, ya que la aplicación a analizar fue compilada en 32 bits. Con “IDA PRO” se llevará a cabo un análisis estático del binario a analizar, mientras que con “x32dbg” se llevará a cabo el análisis dinámico. De esta manera se busca tener una mayor “conciencia situacional” (SA) con la que poder contrastar resultados y reconstruir el código fuente del binario en cuestión con mayor exatitud.

De la misma manera, se van a realizar los trabajos bajo un entorno virtualizado con Windows 7 con la intención de desactivar el Address Space Layout Randomization (ASLR) y el Data Execution Prevention (DEP), que son dos mecanismos de seguridad implementados en sistemas operativos modernos para dificultar la explotación de vulnerabilidades en el software, ya que ambos mecanismos añaden capas adicionales de protección contra ataques como desbordamientos de buffer (BoF).

2. Reconocimiento de la aplicación.

Tras desensamblar la aplicación con ambas herramientas, rápidamente nos percatamos de que se trata de una aplicación encargada de la activación de un producto. De la misma manera, se puede observar en las cadenas de caracteres aportadas en texto claro por ambos debuggers, que la clave de la licencia para la activación se encuentra sin ofuscar, en texto claro, como vemos a continuación.

Incluso, se puede encontrar la cada en texto claro al llegar a la función “strcmp” donde compara las dos cadenas de caracteres para determinar si son iguales (la introducida por el usuario, y la de la clave de licencia).

En la siguiente captura podemos observar cómo la clave de la licencia encontrada es válida.

No obstante lo anterior, el objetivo de esta práctica es profundizar en el funcionamiento y no ir directamente a la activación del producto. Es por ello que a continuación se verá cómo se lleva a cabo el funcionamiento de activación, a través de qué funciones y qué algoritmos.

3. Estudio de funciones y funcionamiento.

El código de la función “main()” se muestra a continuación. Como se puede observar, la función mas relevante es la denominada “request_license_key()”, por lo que será la primera a analizar.

El código de la función citada anteriormente es el siguiente. Se comenta de manera escueta sobre el propio código las acciones que realiza dicha función.

Para analizar paso a paso cada instrucción (F7,F8), se ha hecho uso de distintos breakpoints (F2) con el objetivo de ir controlando el flujo de la ejecución del proceso en cuestión, de manera que se pueda determinar qué es lo que se va realizando a tiempo real.

Se presenta también el código fuente de dicha función proporcionado por IDA PRO.

Así pues, se determina que se realizan las siguientes instrucciones en el orden correspondiente:

  1. mov dword ptr [esp], offset Format ; «\n>> Enter your license key to activate the platform:»: Esta instrucción coloca en la cima de la pila la dirección de la cadena presentada.
  2. call _printf: Esta instrucción llama a la función printf para imprimir la cadena mencionada anteriormente.
  3. mov dword ptr [esp+4], 64h ; ‘d’ ; : Coloca el valor hexadecimal 64h (que es 100 en decimal) en la pila, para indicar un tamaño máximo del buffer.
  4. lea eax, [ebp+Buffer] mov [esp], eax ; : Carga la dirección de un buffer llamado “Buffer” en eax y la coloca en la pila.
  5. call _fgets: Llama a la función fgets para leer una cadena desde la entrada estándar hasta el buffer hasta un máximo de 100 caracteres.
  6. lea eax, [ebp+Buffer] mov [esp], eax ; : Coloca de nuevo la dirección del buffer en la cima de la pila.
  7. call _strlen: Llama a la función strlen para calcular la longitud de la cadena que ha introducido el usuario y que se ha guardado en el buffer.

Interfaz de usuario gráfica, Texto, Aplicación

Descripción generada automáticamente

  1. sub eax, 1 mov [ebp+var_10], eax: Disminuye en uno el valor en eax (que es la longitud de la cadena) y almacena el resultado en una variable local.
  2. lea edx, [ebp+Buffer]; mov eax, [ebp+var_10]; add eax, edx; movzx eax, byte ptr [eax]; cmp al, 0Ah; : Estas instrucciones verifican si el último carácter de la cadena es un salto de línea (0Ah en hexadecimal).
  3. jnz short loc_40150C: Si el último carácter no es un salto de línea, salta a 40150C para continuar con la ejecución.
  4. lea edx, [ebp+Buffer]; mov eax, [ebp+var_10]; add eax, edx; mov byte ptr [eax], 0; : Si el último carácter es un salto de línea, lo reemplaza con un carácter nulo, terminando así la cadena.

La función “is_license_key” verifica si la cadena de caracteres ingresada (la clave de licencia) es válida o no. A continuación, se muestra el código de dicha función.

Interfaz de usuario gráfica, Texto

Descripción generada automáticamente

Comentamos brevemente qué es lo que sucede en dicha función:

  1. push ebp y mov ebp, esp: Son instrucciones estándar para establecer el marco de pila de la función.
  2. sub esp, 28h: Reserva 28h (40 en decimal) bytes en la pila para variables locales.
  3. mov eax, [ebp+Str]: Carga la dirección del argumento Str (la cadena de la clave de licencia proporcionada) en el registro eax.
  4. mov [esp], eax: Coloca la dirección de la cadena en la pila, preparándose para llamar a una función.
  5. call _strlen: Llama a la función “strlen”, que devuelve la longitud de la cadena proporcionada.
  6. mov [ebp+var_C], eax: Almacena la longitud de la cadena en una variable local (var_C).
  7. cmp [ebp+var_C], 24h: Compara la longitud de la cadena con el valor 24 (hexadecimal 24h es 36 en decimal). De esta manera comprueba si la longitud de la clave de licencia es exactamente de 36 caracteres, lo cual es cierto sabiendo que la clave es la siguiente:

35363FC4-8671-4F2C-AE70-4BC9045EC6A3

  1. jz short loc_40144B: Si la longitud es 36, salta a “loc_40144B”. Se expone código que analizaremos en el siguiente punto.

En caso de que la longitud de la cadena no sea 36, salta a “locret_4014A2”, es decir, a la función is_license_key(char *Str), indicando que la clave es inválida. En la siguiente imagen vemos cómo al introducir una clave de 36 caracteres continua su flujo correctamente.

Interfaz de usuario gráfica, Texto, Aplicación

Descripción generada automáticamente

Tras comprobar que la cadena presenta 36 caracteres, el flujo de ejecución continua con la función “is_master()”.

Interfaz de usuario gráfica, Texto, Aplicación

Descripción generada automáticamente

A continuación, se trata de explicar las instrucciones de esta función en su orden correspondiente:

  1. push ebp y mov ebp, esp: Son instrucciones estándar para establecer el marco de pila de la función.
  2. sub esp, 48h: Reserva 48h (72 en decimal) bytes en la pila para variables locales.
  3. mov eax, [ebp+Source]: Carga la dirección de la cadena Source (la cadena proporcionada como argumento) en el registro eax.
  4. lea eax, [ebp+Destination]: Carga la dirección de la variable local “Destination” en el registro eax. Se trata del buffer donde se almacenará una copia de la cadena “Source”.
  5. call _strcpy: Llama a la función “strcpy” para copiar la cadena “Source” a “Destination”.
  6. mov dword ptr [esp+4], offset Str2: Establece el segundo argumento para la función “strcmp”, que es la dirección de la cadena fija «35363FC4-8671-4F2C-AE70-4BC9045EC6A3».
  7. lea eax, [ebp+Destination]: Carga la dirección de la variable local “Destination” (que ahora contiene una copia de “Source”) en el registro eax.
  8. call _strcmp: Llama a la función “strcmp” para comparar “Destination” (la copia de “Source”) con la cadena fija. Esta función devolverá “0” si las dos cadenas son idénticas.
  9. test eax, eax y jnz short loc_401386: Verifica el valor devuelto por “strcmp”. Si no es 0 (lo que significa que las cadenas no son idénticas), salta a “loc_401386”, donde establecerá eax a 0.

  1. mov eax, 1: Si las cadenas son idénticas (porque no saltó en el paso anterior), establece eax a 1, y procederá a reportar el mensaje de éxito. En caso de que la cadena introducida por el usuario sea numérica, procederá a ingresar en la función “sum_numbers”.

A continuación, se muestra captura del pseudocódigo generado por el reconstructor de código de IDA PRO (F5) sobre la función “sum_numbers(char *Source)”, y su código en ensamblador.

Texto

Descripción generada automáticamente

Texto

Descripción generada automáticamente con confianza media

Interfaz de usuario gráfica, Texto

Descripción generada automáticamente

La función “sum_numbers” toma como argumento un puntero a la cadena introducida por el usuario “(char *Source)” y devuelve un entero.

Su funcionamiento es el siguiente:

    • Copia la cadena de caracteres de “Source” a “Destination” usando “strcpy”.
    • Inicializa “v3” a “0”, que se usará para almacenar la suma de los números.
    • Recorre cada carácter en “Destination” usando un bucle “for”.
    • Dentro del bucle, verifica si el carácter actual (representado por ASCII) es un número entre “0” y “9” (los valores ASCII de “0” y “9” son 48h y 57h, respectivamente).
    • Si el carácter es un número, se le resta 48 (el valor ASCII de ‘0’) para obtener el valor numérico real del carácter y luego se suma a “v3”.
    • Una vez que se han procesado todos los caracteres, la función devuelve “v3”, que es la suma de todos los números en la cadena.

Básicamente podríamos decir que la función suma todos los dígitos numéricos que se encuentran en la cadena dada y devuelve el total. Por lo tanto, como vemos en la siguiente imagen, en la cadena introducida “1aaaaaaaaa2bbbbbbbbb3ccccccccc4ddddd” la función devolverá Ah, equivalente a 10 en decimal, porque 1 + 2 + 3 + 4 = 10.

Tabla

Descripción generada automáticamente

Para concluir, podemos afirmar que el funcionamiento general de la aplicación se resume como sigue:

  1. Se imprime el mensaje para que el usuario ingrese la clave de licencia.
  2. Se almacena la entrada del usuario en un buffer.
  3. Se verifica si la última entrada del buffer es un salto de línea, y en caso de ser así se reemplaza con un carácter nulo para terminar la cadena.
  4. Se verifica la clave de licencia ingresada llamando a la función “is_license_key”.
  5. Si la clave es válida:
    • Se imprimen mensajes indicando que la clave es válida, que la activación está en progreso y que queda confirmado que la plataforma ha sido activada.
  6. Si la clave no es válida:
    • Se imprime un mensaje de error mostrando cuántos intentos de activación quedan.
    • Se incrementa el contador de intentos.
    • Se verifica si el contador de intentos es menor o igual al número máximo de intentos permitidos, y si es así, se reinicia el bucle solicitando al usuario que ingrese nuevamente la clave.
  7. Si se han superado todos los intentos permitidos, la función regresa un valor de 0 y termina.

4. Identificación de constantes literales.

Determinamos que en este ejercicio las constantes literales son las que se muestran en la siguiente captura:

En el caso de esta aplicación se constituye un error tremendo de programación al utilizar una constante literal para contener información sensible, como es el caso de la clave de licencia.

En cuanto al uso de las cadenas de caracteres que se observan en la captura anterior, tienen por objetivo el de comunicar al usuario el estado de la activación del producto, y las oportunidades que tiene este de volver a intentar el introducir una clave válida en caso de que haya fallado el intento anterior, principalmente.

5. Identificación de bucles y su funcionamiento.

En la aplicación se encuentran dos bucles y ambos son de tipo “for”.

A continuación, se muestra captura del pseudocódigo generado por el reconstructor de código de IDA PRO (F5) sobre la función “request_license_key(int a1)”.

Interfaz de usuario gráfica, Texto

Descripción generada automáticamente

Como se puede observar, hay un bucle en la función anterior, y es del tipo “for”. En cuanto a la descripción del bucle, se determina que:

    • El bucle “for” comienza con la inicialización “i = 1” y tiene una condición de continuación “i <= a1”. La variable “i” se incrementa en 1 en cada iteración con “++i”.
    • En cada iteración del bucle, se solicita al usuario que ingrese una clave de licencia, y luego se verifica si esta clave es válida o no.
      • Si la clave es válida, se muestra un mensaje de éxito y la función retorna “1”, finalizando su ejecución.
      • Si la clave no es válida, se muestra un mensaje de error informando al usuario cuántos intentos le quedan para ingresar una clave válida.

Los condicionantes para salir del bucle son los siguientes:

    • Cuando la variable “i” supera el valor de “a1. Esto indica que el bucle se ejecutará un total de “a1” veces si no se cumple la condición de licencia válida antes.
    • Cuando la función “is_license_key(Buffer)” devuelve un valor que indica que la clave de licencia es válida. En este caso finalizará la ejecución de toda la función y, por tanto, finalizará el bucle.

A continuación, se muestra captura del pseudocódigo generado por el reconstructor de código de IDA PRO (F5) sobre la función “sum_numbers(char *Source)”.

Texto

Descripción generada automáticamente

Como se puede observar, hay un bucle en la función anterior, y también es del tipo “for”. En cuanto a la descripción del bucle, se determina que:

    • El bucle “for” comienza con la inicialización “i = 0” y tiene una condición de continuación “Destination[i]”. La variable “i” se incrementa en 1 en cada iteración con “++i”.
    • Recorre cada carácter almacenado en “Destination. Cuando procesa el último carácter procede a finalizar el bucle.
    • Dentro del bucle, verifica si el carácter actual (representado por ASCII) es un número entre “0” y “9” (los valores ASCII de “0” y “9” son “48” y “57”, respectivamente).
    • Si el carácter es un número, se le resta 48 (el valor ASCII de ‘0’) para obtener el valor numérico real del carácter y luego se suma a “v3”.
    • Una vez que se han procesado todos los caracteres, la función devuelve “v3”, que es la suma de todos los números en la cadena. En este momento finaliza el bucle.

6. Identificación de bucle anidado y su funcionamiento.

Como se observa a continuación, se puede determinar que existe un bucle anidado, ya que se define como un bucle anidado aquel que ocurre cuando hay un bucle dentro de otro bucle.

Para ser más exacto en la respuesta:

    • La función “request_license_key” tiene un bucle “for” que en su interior realiza una llamada a la función “is_license_key”, y esta función contiene a su vez a la función “sum_numbers”, la cual contiene bucle “for” en su interior, como hemos visto en el punto anterior.

Se adjuntan capturas de las funciones analizadas:

7. Métodos o funciones propias de la aplicación.

Las funciones propias de la aplicación son las siguientes.

Patrón de fondo

Descripción generada automáticamente con confianza media

No obstante, para su correcto funcionamiento tiene que hacer uso de muchas otras que no son propiamente de la aplicación, sino de librerías dinámicas del propio sistema operativo (DLLs), como vemos a continuación, pertenecientes a las librerías “kernel32.dll” y a “msvcrt.dll”.

Tabla

Descripción generada automáticamente

Para no hacer muy extenso el ejercicio, decir que ya se han analizado anteriormente los códigos fuente de las distintas funciones, entre las que destacan “request_license_key()” y “is_license_key”. En cuanto a las “funciones librería” se explican brevemente aquellas más relevantes para el funcionamiento de la aplicación:

    • printf: Es una función utilizada para la salida formateada, empleada para mostrar mensajes al usuario.
    • fgets: Es una función que lee caracteres desde el flujo de entrada especificado y la almacena en la variable proporcionada.
    • strlen: Es una función que devuelve la longitud de una cadena de caracteres, excluyendo el carácter nulo de terminación.
    • strcpy: Es una función utilizada para copiar una cadena de caracteres de una ubicación a otra. Sin embargo, “strcpy” no verifica si el buffer de destino puede alojar la cadena de origen, lo que puede llevar a desbordamientos de buffer, como veremos más adelante.
    • strcmp: Esta función compara dos cadenas de caracteres. Como curiosidad decir que es importante tener en cuenta que las comparaciones que involucran claves o contraseñas sensibles pueden ser vulnerables a ataques de tiempo, ya que “strcmp” puede devolver un resultado antes de tiempo, en caso de encontrar una diferencia en los primeros caracteres. Esto no aplica en el caso de este ejercicio.

8. Identificando puntos vulnerables de la aplicación.

Analizando la aplicación, se pueden identificar algunas áreas que podrían ser potencialmente vulnerables o que, al menos, podrían mejorar en términos de seguridad:

    • Uso de “fgets” para la entrada de datos: Sí que es cierto que “fgets” es más seguro que funciones como “gets” (conocida por ser vulnerable a BoF), pero también presenta riesgos potenciales si no se gestiona adecuadamente. Así pues, es esencial asegurarse de que el tamaño especificado como límite (en este caso 100) coincida con el tamaño del buffer de destino. Sino podría introducirse una vulnerabilidad.

Si hacemos un pequeño estudio, se comprueba que con un total de 197 caracteres encuentra su límite funcional. En la siguiente captura podemos ver cómo con 198 caracteres la aplicación finaliza, no dando opción a nuevos intentos de introducir la clave.

Una vez alcanzada la instrucción “ret”, realiza la llamada a la salida de la ejecución del proceso.

Como se puede observar, la función “fgets” se utiliza para leer hasta 100 caracteres (incluido el carácter de nueva línea) en el buffer “Buffer”. Así pues, la función “fgets” leerá caracteres hasta que:

    • Se hayan leído n-1 caracteres (en este caso, 99 caracteres).
    • Se haya encontrado un carácter de nueva línea (\n).

Entonces, si un usuario introduce 198 caracteres sin un carácter de nueva línea, “fgets” leerá los primeros 99 caracteres en la primera iteración del bucle. En la segunda iteración, leerá los siguientes 99 caracteres. Es importante saber que “fgets” no discrimina entre diferentes líneas de entrada, pues simplemente lee caracteres hasta que se encuentre con un límite.

Entonces, después de dos iteraciones del bucle, se han leído 198 caracteres en total. En la tercera iteración, si no hay más entrada (o simplemente un carácter de nueva línea), “fgets” considerará que es otra entrada de licencia. Si no se introduce nada y simplemente se presiona «Enter», “fgets” leerá solo el carácter de nueva línea.

Como resultado, al introducir 198 caracteres seguidos de un carácter de nueva línea, se considera que el usuario ha realizado tres intentos para introducir la clave de licencia. Por eso es importante tener precaución y comprensión completa de cómo las funciones de entrada/salida estándar operan para evitar confusiones o posibles errores en el comportamiento del programa.

    • Falta de validación de la entrada: Aunque el código verifica la longitud de la entrada y elimina el carácter de salto de línea, no hay una validación completa de lo que el usuario podría ingresar.
    • Exposición de intentos restantes: Informar al usuario sobre la cantidad de intentos restantes podría proporcionar a un atacante información útil, permitiendo, por ejemplo, saber cuándo se bloqueará un sistema después de múltiples intentos fallidos.
    • No se ocultan los mensajes de error: Mostrar mensajes de error detallados puede proporcionar a un atacante pistas sobre la lógica interna del sistema.
    • Falta de ofuscación en cadenas de caracteres sensibles: Aquí se pone como ejemplo la cadena de caracteres de la activación de la licencia sin ofuscar, embebida en el propio código.
    • Uso de funciones vulnerables: Como “strcpy” y “strcmp”. En el siguiente punto se tratará el desbordamiento de buffer (BoF) que pueden llegar a provocar.

9. ¿Se hacen uso de funciones vulnerables del tipo strcpy, strcmp, etc.?

Efectivamente, el código utiliza dos funciones que son comúnmente reconocidas por ser potencialmente vulnerables si no se usan correctamente: “strcpy” y “strcmp”.

Como se observa a continuación, están presentes en distinas funciones de la aplicación.

Captura de pantalla de un celular

Descripción generada automáticamente

Interfaz de usuario gráfica, Texto, Aplicación

Descripción generada automáticamente

Analizando el código se observa una vulnerabilidad clásica de desbordamiento de búfer en la función “is_master”. Específicamente, se utiliza la función “strcpy” para copiar una cadena de caracteres desde el parámetro “Source” hacia el arreglo “Destination”, que tiene un tamaño fijo de 44 bytes. Si “Source” contiene más de 43 caracteres (44 – 1 para el carácter nulo), ocurrirá un desbordamiento de búfer (BoF).

Para explotar este desbordamiento de búfer y causar un crash en la aplicación, basta con proporcionar una clave de licencia que tenga más de 43 caracteres. Dado que “is_license_key” requiere que la longitud sea exactamente de 36 caracteres para que “is_master” se ejecute, el desbordamiento no es directamente explotable con solo la clave de licencia. Sin embargo, si se tuviera acceso directo a la función “is_master” (o si la lógica del programa cambia en el futuro), se podría usar un payload como:

“AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA”,

para causar el crashseo de la aplicación, que bien puede radicar en una denegación de servicio (DOS), en una ejecución de código remoto (RCE), entre otros.

A continuación, vamos a introducir el payload anterior de 50 caracteres, y vamos a ir redirigiendo/modificando el flujo del proceso para que llegue a la función “is_master” y comprobar que, efectivamente, se realiza un BoF.

Como se observa en la captura anterior, se realiza un Buffer Overflow con el payload introducido, tras haber modificado el flujo de ejecución para forzar a la aplicación alcanzar la función “is_master”.

Llegados a este punto, un actor maligno podría elaborar una versión alterada del software, ajustando los códigos de operación necesarios para que el programa siempre acceda a la función comprometida. Esto permitiría que se valide como legítima una clave específica diseñada como payload, explotando el desbordamiento de búfer para conseguir una ejecución remota de código (RCE) u otros fines. Esta nueva versión “maligna” la podría subir a un repositorio desde la que poder descargarla y distribuirla como “aplicación medicinada o crackeada”, pudiendo “infectar” de esta manera a aquellos que se la descarguen, y evadiendo incluso antivirus y demás sistemas perimetrales de seguridad, dado que la aplicación en sí misma no contiene código ni comportamiento dañino. Es por todo lo anterior que no debe descargarse software pirata, ni descargarse software de repositorios que no sean oficiales o de alta fiabilidad.

Obviamente, no lo vamos a llevar a cabo porque no es el propósito de este ejercicio, pero creo conveniente mencionarlo dado que deja de manifiesto la importancia de la seguridad del software en campañas de cibercrimen.

Con la intención de mitigar la vulnerabilidad encontrada, se recomienda:

  • Reemplazar “strcpy” por una función más segura como “strncpy”, que toma un tamaño máximo como argumento y evita escribir más allá de ese límite.

10. ¿Qué condicionales se han encontrado? ¿Qué condición se realiza? ¿Qué sucede en cada caso?

Se han identificado los siguientes condicionales:

  • En la función “request_license_key” se han identificado los siguientes condicionales:
    • if ( Buffer[v4] == 10 )

      • Condición: Verifica si el último carácter de “Buffer” es un salto de línea (\n, cuyo valor ASCII es 10).
      • Acción si es verdadero: Sustituye el salto de línea por el carácter nulo (\0), esencialmente eliminando ese salto de línea de la cadena.
    • if ( (unsigned __int8)is_license_key(Buffer) )

      • Condición: Verifica si la función is_license_key devuelve un valor que se pueda interpretar como true cuando se convierte a unsigned __int8.
      • Acción si es verdadero: Imprime mensajes indicando que la clave de licencia es válida y termina la función devolviendo 1.
      • Acción si es falso: Imprime un mensaje de error con el número de intentos restantes. Esta acción no está dentro de un bloque “else”, pero se ejecuta después del bloque “if” si la condición es falsa.
    • En la función “is_license_key” se han identificado los siguientes condicionales:
    • if ( strlen(Str) != 36 )

Tabla

Descripción generada automáticamente

      • Condición: Verifica si la longitud de la cadena contenida en “Str” no es igual a 36.
      • Acción: Si la condición se cumple, retorna 0 (indicando que no es una clave de licencia válida).
    • if ( is_master(Str) )

Interfaz de usuario gráfica

Descripción generada automáticamente con confianza media

      • Condición: Llama a la función “is_master” y verifica si retorna un valor verdadero (lo que indicaría que la clave de licencia es válida).
      • Acción: Si la condición se cumple, retorna 1 (indicando que es una clave de licencia válida).
  • En la función “is_master” se han identificado los siguientes condicionales:
    • strcmp(Destination, «35363FC4-8671-4F2C-AE70-4BC9045EC6A3») == 0:

      • Condición: Compara la cadena en Destination con la cadena literal «35363FC4-8671-4F2C-AE70-4BC9045EC6A3» usando strcmp. La condición verifica si ambas cadenas son idénticas.
      • Acción: Retorna verdadero (1) si las cadenas son iguales, y falso (0) si no lo son. En caso de ser iguales indicará que la licencia es válida, y en caso contrario indicará que es inválida.
    • En la función “sum_numbers” se han identificado los siguientes condicionales:
    • if ( Destination[i] > 47 && Destination[i] <= 57 )

      • Condición: Verifica si el carácter actual en “Destination” es un dígito numérico (valor ASCII entre 48 y 57, inclusive).
      • Acción: Si la condición se cumple, suma el valor del dígito (convertido a entero) al acumulador (“v3”).
    • En la función “is_divisible_by_a_and_b” se ha identificado el siguiente condicional:
    • return !(a1 % a2) && !(a1 % a3)

Tabla

Descripción generada automáticamente

      • Condición: Verifica si el número “a1” es divisible tanto por “a2” como por “a3”.
      • Acción: Retorna verdadero si “a1” es divisible por ambos números y falso en caso contrario.

11. Variables empleadas.

A continuación, se muestran las capturas de cada función de la aplicación, explicando brevemente las variables de cada una de ellas.

    • argc: Es un argumento entero que representa el número de argumentos pasados al programa. No interfiere en el funcionamiento de la aplicación.
    • argv: Es un puntero a un array de punteros a cadenas. Cada cadena es un argumento pasado al programa. El primer argumento (argv[0]) generalmente es el nombre del programa ejecutable. No interfiere en el funcionamiento de la aplicación.
    • envp: Es un puntero a un array de punteros a cadenas que representan las variables de entorno. Cabe destacar que no es un argumento estándar de la función “main” en C/C++, pero algunas implementaciones y sistemas operativos lo incluyen. No interfiere en el funcionamiento de la aplicación.

Texto

Descripción generada automáticamente

    • Buffer: Es una variable local de tipo byte (o char en C/C++), y está localizada a -75h desde el puntero base del stack (base pointer). Se trata del buffer donde se almacenará la clave de licencia introducida por el usuario.
    • var_11: Es una variable local de tipo byte (o char en C/C++), ubicada a -11h desde el puntero base del stack.
    • var_10: Es una variable local de tipo dword (un entero de 32 bits), ubicada a -10h desde el puntero base del stack.
    • var_C: Es una variable local de tipo dword, ubicada a -0Ch desde el puntero base del stack.
    • arg_0: Es un argumento que fue pasado a la función. Está localizado a 8 desde el puntero base del stack.

    • var_14: Es una variable local de tipo dword (un entero de 32 bits). Está ubicada a -14h desde el puntero base del stack.
    • var_10: Es una variable local de tipo dword, ubicada a -10h desde el puntero base del stack.
    • var_C: Es una variable local de tipo dword, ubicada a -0Ch desde el puntero base del stack.
    • Str: Es un argumento que fue pasado a la función. Está localizado a 8 desde el puntero base del stack. Contiene la cadena de caracteres contenida en “Buffer”.

    • arg_0: Es un argumento de tipo dword (que suele ser un entero de 32 bits en plataformas de 32 bits) y se encuentra a 8 desde el puntero base del stack. Se trata del número que se verificará si es divisible
    • arg_4: Es otro argumento de tipo dword, ubicado a 0Ch desde el puntero base del stack. Se trata de uno de los divisores.
    • arg_8: Es otro argumento de tipo dword, ubicado a 10h desde el puntero base del stack. Se trata del otro divisor.

    • Destination: Es una variable local de tipo byte que está ubicada a -34h desde el puntero base del stack.
    • var_10: Es una variable local de tipo dword (un entero de 32 bits) y se encuentra a -10h desde el puntero base del stack.
    • var_C: Es otra variable local de tipo dword, ubicada a -0Ch desde el puntero base del stack.
    • Source: Es un argumento de tipo dword y se encuentra a 8 desde el puntero base del stack. Dado que se pasa a la función cuando se llama, representa el parámetro que se suministra a la función.

Imagen que contiene Texto

Descripción generada automáticamente

    • Destination: Es una variable local de tipo byte (una cadena de caracteres) que está ubicada a -2Ch desde el puntero base del stack.
    • Source: Es un argumento de tipo dword (un puntero a una cadena de caracteres) y se encuentra a 8 desde el puntero base del stack. Dado que se pasa a la función cuando se llama, representa el parámetro que se suministra a la función.

12. Muestra el uso del stack (pila) en algún punto de la ejecución.

En la siguiente imagen se puede observar el uso de la pila tras finalizar la ejecución la función “fgets”. Así pues, se puede ver que se guarda en la pila los distintos valores en hexadecimal de cada carácter introducido por el usuario (a=61h, b=62h, c=63h, etc.).

Como es obvio, estos valores serán utilizados más adelante para comprobar si la clave de licencia introducida es la correcta, mediante la cantidad de caracteres que hay (debe haber 36 bien calculados con “strleen”) y mediante la comparación directa en ASCII con la cadena de caracteres que corresponde a la clave de licencia legítima (con “strcmp”).

A continuación, se puede ver cómo se usan las instrucciones “push ebp” y “mov ebp, esp”, que son instrucciones estándar para establecer el marco de pila de una función. La instrucción “sub esp, 28h” Reserva 48h (40 en decimal) bytes en la pila para variables locales.

Concretamente, se emplea la instrucción “push ebp” para guardar en el stack (la pila) el valor del registro EBP, el cual corresponde a la clave de licencia en ASCII introducida por el usuario. Además, como ya se ha comentado, podemos ver más abajo que tras pasar la instrucción “fgets”, se guardaron en la pila los valores de cada carácter introducido por el usuario, en hexadecimal (a=61h, b=62h, etc.).

También se puede ver el uso de la pila en la función “sum_numbers”, donde se usa la pila para ir recorriendo la cadena de caracteres introducida por el usuario en busca de números para luego sumarlos.

Interfaz de usuario gráfica, Aplicación

Descripción generada automáticamente

Además de lo anterior, en el punto 7 hemos podido observar cómo se comporta la pila ante una vulnerabilidad de Buffer Overflow (BoF).

13. ¿Se puede modificar el flujo de ejecución?

Como se verá a continuación, la respuesta corta es: Sí.

El punto exacto donde se cumple la condición de éxito o de error se encuentra en la instrucción “JE program.40154B”. En este caso, para poder realizar un bypass de dicha condición y activar con éxito la aplicación, se procederá a cambiar la instrucción “JE” por “JNE”, o, lo que es lo mismo, cambiar el opcode “74” por el “75”.

Como se muestra a continuación, en el IDA en lugar de poner la instrucción JE aparece la instrucción JZ, lo cual son simplemente nombres diferentes para representar exactamente lo mismo, ya que ambos indican un salto condicional cuando ZF (la «bandera de cero») es igual a 1.

En la siguiente captura se muestra cómo ya se ha cambiado el opcode “74” por el “75” resultando el cambio de instrucción de “JZ” a “JNZ”.

Interfaz de usuario gráfica, Texto

Descripción generada automáticamente

Así pues, es el momento de comprobar que se ha “medicinado” el programa adecuadamente, y que de ahora en adelante aceptará cualquier tipo de licencia.

De esta manera se ha demostrado que, efectivamente, se puede alterar el flujo del código de la aplicación.

14. Otros recursos similares.

English

No puedes copiar el contenido