El pasado 15 de noviembre, sec/UMA y Bitup realizaron un Capture The Flag (CTF) online y presencial en la universidad de Málaga, España. Afortunadamente pude participar de manera remota resolviendo algunos retos, de los cuales comparto la solución a dichos retos. Las categorías fueron Reversing, Web, Stego, Forensics, Crypto, Networking y Quizz.

Omito las soluciones a los retos de Web ya que aun no han salido de manera oficial los writeups. Quizz lo omito porque google+^f.

Reversing

cs30 (150pts)

Se ha encontrado un ejecutable en los servidores de E-Corp tras el ataque DDoS que recibieron hace unos días. Tenemos razones para pensar que hay un miembro infiltrado de la organización criminal en ECorp. Como miembros de allsafe cybersecurity, debemos conseguir acceder al archivo ya que creemos que podría ser un mensaje para su infiltrado.


El primer reto nos entrega un ejecutable, el cual tras validarlo con file veremos que se trata de un ejecutable ELF64:

file over fsociety

Tras esto, abrimos el binario en radare2 y le damos una pasada de análisis experimental.

r2 AAAA

Analicemos los imports de este binario:

Num Vaddr Bind Type Name
1 0x00000e50 GLOBAL FUNC sym.imp.std::__cxx11::basic_string<char,std::char_traits,std::allocator>::compare(std::__cxx11::basic_string<char,std::char_traits,std::allocator>const&)const
2 0x00000e60 GLOBAL FUNC sprintf
3 0x00000e70 GLOBAL FUNC sym.imp.std::__cxx11::basic_string<char,std::char_traits,std::allocator>::c_str()const
4 0x00000000 WEAK FUNC __cxa_finalize
5 0x00000e80 GLOBAL FUNC sym.imp.std::__cxx11::basic_string<char,std::char_traits,std::allocator>::basic_string(std::__cxx11::basic_string<char,std::char_traits,std::allocator>const&)
6 0x00000e90 GLOBAL FUNC memset
7 0x00000ea0 GLOBAL FUNC sym.imp.std::__cxx11::basic_string<char,std::char_traits,std::allocator>::~basic_string()
8 0x00000eb0 GLOBAL FUNC memcpy
9 0x00000ec0 GLOBAL FUNC __cxa_atexit
10 0x00000ed0 GLOBAL FUNC sym.imp.std::basic_ostream<char,std::char_traits>&std::operator«<char,std::char_traits,std::allocator>(std::basic_ostream<char,std::char_traits>&,std::__cxx11::basic_string<char,std::char_traits,std::allocator>const&)
11 0x00000ee0 GLOBAL FUNC sym.imp.std::basic_ostream<char,std::char_traits>&std::operator«<std::char_traits>(std::basic_ostream<char,std::char_traits>&,charconst*)
12 0x00000ef0 GLOBAL FUNC sym.imp.std::allocator::~allocator()
13 0x00000f00 GLOBAL FUNC __stack_chk_fail
14 0x00000f10 GLOBAL FUNC sym.imp.std::__cxx11::basic_string<char,std::char_traits,std::allocator>::operator=(std::__cxx11::basic_string<char,std::char_traits,std::allocator>&&)
15 0x00000f20 GLOBAL FUNC sym.imp.std::basic_istream<char,std::char_traits>&std::operator»<char,std::char_traits,std::allocator>(std::basic_istream<char,std::char_traits>&,std::__cxx11::basic_string<char,std::char_traits,std::allocator>&)
16 0x00000f30 GLOBAL FUNC sym.imp.std::__cxx11::basic_string<char,std::char_traits,std::allocator>::basic_string(charconst*,std::allocatorconst&)
17 0x00000f40 GLOBAL FUNC sym.imp.std::__cxx11::basic_string<char,std::char_traits,std::allocator>::basic_string()
18 0x00000f50 GLOBAL FUNC sym.imp.std::__cxx11::basic_string<char,std::char_traits,std::allocator>::length()const
19 0x00000f60 GLOBAL FUNC sym.imp.std::ios_base::Init::Init()
20 0x00000000 GLOBAL FUNC __gxx_personality_v0
21 0x00000000 WEAK NOTYPE _ITM_deregisterTMCloneTable
22 0x00000f70 GLOBAL FUNC _Unwind_Resume
23 0x00000f80 GLOBAL FUNC sym.imp.std::allocator::allocator()
24 0x00000000 GLOBAL FUNC __libc_start_main
25 0x00000000 WEAK NOTYPE gmon_start
26 0x00000000 WEAK NOTYPE _ITM_registerTMCloneTable
27 0x00000000 GLOBAL FUNC sym.imp.std::ios_base::Init::~Init()
4 0x00000000 WEAK FUNC __cxa_finalize
20 0x00000000 GLOBAL FUNC __gxx_personality_v0
21 0x00000000 WEAK NOTYPE _ITM_deregisterTMCloneTable
24 0x00000000 GLOBAL FUNC __libc_start_main
25 0x00000000 WEAK NOTYPE gmon_start
26 0x00000000 WEAK NOTYPE _ITM_registerTMCloneTable
27 0x00000000 GLOBAL FUNC sym.imp.std::ios_base::Init::~Init()

Listamos las funciones las cuales tenemos lo siguiente:

direccion nbbs size nombre
0x00000249 7 67->112 fcn.00000249
0x00000e28 3 23 sym._init
0x00000e50 1 6 sym.std::__cxx11::basic_string_char_std::char_traits_char__std::allocator_char __::compare_std::__cxx11::basic_string_char_std::char_traits_char__std::allocator_char__const__const
0x00000e60 1 6 sym.imp.sprintf
0x00000e70 1 6 sym.std::__cxx11::basic_string_char_std::char_traits_char__std::allocator_char __::c_str__const
0x00000e80 1 6 sym.std::__cxx11::basic_string_char_std::char_traits_char__std::allocator_char __::basic_string_std::__cxx11::basic_string_char_std::char_traits_char__std::allocator_char__const
0x00000e90 1 6 sym.imp.memset
0x00000ea0 1 6 sym.std::__cxx11::basic_string_char_std::char_traits_char__std::allocator_char __::_basic_string
0x00000eb0 1 6 sym.imp.memcpy
0x00000ec0 1 6 sym.imp.__cxa_atexit
0x00000ed0 1 6 sym.std::basic_ostream_char_std::char_traits_char___std::operator___char_std::char_traits_char__std::allocator_char___std::basic_ostream_char_std::char_traits_char____std::__cxx11::basic_string_char_std::char_traits_char__std::allocator_char__const
0x00000ee0 1 6 sym.std::basic_ostream_char_std::char_traits_char___std::operator___std::char_traits_char___std::basic_ostream_char_std::char_traits_char____charconst
0x00000ef0 1 6 sym.std::allocator_char_::_allocator
0x00000f00 1 6 sym.imp.__stack_chk_fail
0x00000f10 1 6 sym.std::__cxx11::basic_string_char_std::char_traits_char__std::allocator_char__::operator__std::__cxx11::basic_string_char_std::char_traits_char__std::allocator_char
0x00000f20 1 6 sym.std::basic_istream_char_std::char_traits_char___std::operator___char_std::char_traits_char__std::allocator_char___std::basic_istream_char_std::char_traits_char____std::__cxx11::basic_string_char_std::char_traits_char__std::allocator_char
0x00000f30 1 6 sym.std::__cxx11::basic_string_char_std::char_traits_char__std::allocator_char__::basic_string_charconst__std::allocator_char_const
0x00000f40 1 6 sym.std::__cxx11::basic_string_char_std::char_traits_char__std::allocator_char__::basic_string
0x00000f50 1 6 sym.std::__cxx11::basic_string_char_std::char_traits_char__std::allocator_char__::length__const
0x00000f60 1 6 sym.std::ios_base::Init::Init
0x00000f70 1 6 sym.imp._Unwind_Resume
0x00000f80 1 6 sym.std::allocator_char_::allocator
0x00000f90 1 6 sub.__cxa_finalize_f90
0x00000fa0 1 43 entry0
0x00000fd0 4 50->40 sym.deregister_tm_clones
0x00001010 4 66->57 sym.register_tm_clones
0x00001060 5 58->51 sym.__do_global_dtors_aux
0x000010a0 1 10 entry1.init
0x000010aa 4 65 sym.check_std::__cxx11::basic_string_char_std::char_traits_char__std::allocator_char___std::__cxx11::basic_string_char_std::char_traits_char__std::allocator_char
0x000010eb 3 522 sym.flag
0x000012f5 8 634->498 main
0x0000156f 4 73 sub.std::ios_base::Init._Init_56f
0x000015b8 1 21 sym._GLOBAL__sub_I__Z5checkNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES4
0x000015ce 1 27 sym.MD5::MD5
0x000015ea 1 95 sym.MD5::MD5_std::__cxx11::basic_string_char_std::char_traits_char__std::allocator_char__const
0x0000164a 1 84 sym.MD5::init
0x0000169e 4 175 sym.MD5::decode_unsignedint__unsignedcharconst__unsignedint
0x0000174e 4 215 sym.MD5::encode_unsignedchar__unsignedintconst__unsignedint
0x00001826 1 1021 sym.MD5::transform_unsignedcharconst
0x0000247e 9 306 sym.MD5::update_unsignedcharconst__unsignedint
0x000025b0 1 44 sym.MD5::update_charconst__unsignedint
0x000025dc 8 279 sym.MD5::finalize
0x000026f4 9 307->255 sym.MD5::hexdigest_abi:cxx11___const
0x00002827 2 98->103 sym.operator___std::ostream__MD5
0x000028a3 1 5 loc.000028a3
0x000028af 3 113 sym.md5_std::__cxx11::basic_string_char_std::char_traits_char__std::allocator_char
0x00002920 4 73 sym.__static_initialization_and_destruction_0_int_int
0x00002969 1 21 sym._GLOBAL__sub_I__ZN3MD5C2Ev
0x0000297e 1 33 sym.MD5::F_unsignedint_unsignedint_unsignedint
0x000029a0 1 33 sym.MD5::G_unsignedint_unsignedint_unsignedint
0x000029c2 1 24 sym.MD5::H_unsignedint_unsignedint_unsignedint
0x000029da 1 26 sym.MD5::I_unsignedint_unsignedint_unsignedint
0x000029f4 1 24 sym.MD5::rotate_left_unsignedint_int
0x00002a0c 1 106 sym.MD5::FF_unsignedint__unsignedint_unsignedint_unsignedint_unsignedint_unsignedint_unsignedint
0x00002a76 1 106 sym.MD5::GG_unsignedint__unsignedint_unsignedint_unsignedint_unsignedint_unsignedint_unsignedint
0x00002ae0 1 89 sym.MD5::HH_unsignedint__unsignedint_unsignedint_unsignedint_unsignedint_unsignedint_unsignedint
0x00002b39 1 3 fcn.00002b39
0x00002b4a 1 106 sym.MD5::II_unsignedint__unsignedint_unsignedint_unsignedint_unsignedint_unsignedint_unsignedint
0x00002bc0 4 101 sym.__libc_csu_init
0x00002c30 1 2 sym.__libc_csu_fini
0x00002c34 1 9 sym._fini

Tras analizar el tamaño de las funciones y reconocer que estamos tratando con un programa en C++, vamos a la función main a ver que sucede:

pdf de main

Lo primero que resalta es el string str.8a24367a1f46c141048752f2d5bbd14b, tras recordar que varias de las funciones tienen la palabra md5 en sus nombres, esto pareciera algún indicativo hacia la flag. Continuemos sobre las instrucciones.

alloc

Las primeras instrucciones se tratan de allocators que reservan la memoria para los strings que usa la funcion main. Podemos ver por ejemplo que RBP-0xc0 tiene el valor del string en cuestión que estábamos revisando en el bloque pasado. Las instrucciones son las siguientes:

ostream

El siguiente bloque se encarga de imprimir en pantalla el mensaje “Introduzca la contraseña: " y “\n”

istream

El siguiente se encarga de tomar el valor introducido y guardarlo en una variable tipo string ubicada en RBP-0xa0.

reasigna

Esta variable es reasignada para conservar la original y trabajar con una copia.

md5 del valor ingresado

Como se podía intuir, se le pasa la variable con el valor ingresado a la función md5, la cual almacena el resultado en RBP-0x40

Muevo y limpio

Después el valor de RBP-0x40 pasa a ser de RBP-0x80, tenemos unos procesos de limpieza y imprimimos “\n” para no llenar la pantalla.

check

En esta parte, asigno local_c0h (string hasta arriba en main) y local_80h hacia local_40h y local_60h. Ambas se pasan a la función check. Veamos que hace esta función:

función check

Básicamente compara sus dos argumentos y devuelvo 1 si son iguales o 0 si son diferentes, invertido el asunto. Continuamos con el siguiente bloque de instrucciones:

función check

Empezamos por salvar el valor de EAX en EBX, tenemos un proceso de limpieza y continuamos con un test sobre BL, que es básicamente el valor 1 si el string es igual o 0 si es diferente. Si check fue 1, la siguiente parte je no brincara y nos mandara a sym.flag (tal vez aquí se genera la flag). Si el check por otra parte devuelve 0, entonces el je si se ejecutara y nos mandara un mensaje diciendo que es incorrecto.

A partir de aquí tenemos dos formas, romper el hash md5 o romper la función flag. Hagamos ambas :D

  1. Cracker la hash md5

En mi caso para identificar el valor que genero el hash, use hashcat de la siguiente manera:

función check

  1. Reversing de función flag

función flag1

Lo primero que vemos al llegar a la función flag es que se guarda mucha información en el stack, desde RBP-0x60 hasta RBP-0x18, guardando muchos caracteres que son reconocibles.

función flag1

Después vemos que hay un string raro, diciendo “OnE_oRzr\n” que pareciera ser con lo que trabaja en las siguientes funciones. Trabaja con substrings de ese string mas el resultado que se almacena en RAX.

función flag1

Esto sigue hasta el final de la función, donde antes de salir se valida el canario de la muerte. También tenemos un bonito NOP haciendo bulto.

Para agilizar este resultado, decidí resolverlo por análisis dinámico, iniciando el programa y colocando el RIP sobre esta función, ya que no requiere nada previo ni devuelve algún valor.

función flag1

Usamos ese NOP como breakpoint y la flag se libera ante nosotros.

secuma18{OnE_oR_zEro}

brEakCORP (300pts)

If could you break the restriction, I will be so happy. I think that you can do it.


Para el segundo reto de reversing, tenemos nuevamente un ejecutable ELF64:

file de brEakCORP

Cargamos el ejecutable en radare2 y comenzamos por el análisis experimental:

funciones de brEakCORP

Listamos las funciones en donde no vemos otras funciones locales aparte de main.

imports de brEakCORP

Los imports nos dicen lo mismo de las funciones, no hay nada extraño hasta el momento. Analicemos main:

bloque1 de brEakCORP

Lo primero que notamos, es que hay muchos caracteres cargándose en sobre el stack, una señal clara que aquí se utilizaran para alguna función.

bloque2 de brEakCORP

Termina de subir cosas al stack y se guardan algunos enteros. Interesante que local_ch y local_a8h se inician en 0, mientras que local_8h se inicia en 4815159197. Después tenemos un brinco muy adelante hasta 0x400728.

bloque3 de brEakCORP

Lo que tenemos a continuación es probablemente un ciclo for, ya que mientras local_ch (que vale 0) sea menor igual a 26, no podrá salir del bucle.

bloque4 de brEakCORP

Termina nuestro bucle de operaciones matemáticas y llamamos a printf, scanf y fflush, para poner en pantalla el mensaje de “Ecorp Enter Access Code: " y tomar el valor entero que ingrese el usuario. Este se almacena en local_a8h.

bloque5 de brEakCORP

Continuamos con un cmp, en donde se evaluá si el valor que ingresamos es igual a local_8h.

Si es verdadero, local_18h es igual a 0 y brincamos a 0x4007a9, en donde se compara local_18h contra 26, que es la longitud del flag. Básicamente construye el flag y lo imprime en pantalla al finalizar.

En caso falso, imprime “Access_Denied” y sale de main devolviendo 0.

Continuemos ahora con el análisis dinámico.

Si nos fijamos bien, conociendo el valor de local_8h en el cmp, pudiéramos descifrar la contraseña necesaria para imprimir la flag, así que coloquemos un breakpoint ahí:

analisis dinamico de brEakCORP

Validemos que este sea el valor y que podamos imprimir la flag:

analisis dinamico de brEakCORP

secuma18{eVeRyThInghaPPendSBYaReaSoN}

Strange file (500pts)

Tras estar toda la noche pwneando una máquina de HTB, encuentras un extraño archivo en el directorio /tmp de tu máquina.


Para este reto descargamos el archivo anexo SimulacroySimulacion, el cual analizamos con file:

file de simulacro

Como podemos ver tratamos otra vez un archivo ELF64, el cual si nos llama la atención el resultado de file, nos indica que no tiene section header… uy.

ejecución de simulacro

Tras ejecutar el binario, nos pide la contraseña y sencillamente nos comenta si es o no correcto.

Veamos que podemos ver por strings:

strings de simulacro

Vuelve a aparecer el mensaje de copyright, y también algo que parece la flag, “uma18{”.

rabin de simulacro

Rabin2 nos entrega información interesante, tal como que no existen imports, no hay secciones cosa muy común con los packers. Veamos la entropía para confirmar si el binario se encuentra empaquetado. Comencemos por cargar el binario en radare2 para su análisis estático:

r2load de simulacro

Nuevamente los warnings que ya hemos visto.

Veamos la entropía:

entropy de simulacro

Esto nos confirma como todo se va de un lado, lo cual implica que hay un pedazo que desempaca.

Ahora que sabemos que se trata de un packer, analicemos nuevamente el string del copyright:

strings de aaa de simulacro

Eso de las aaa, se parece a otro tipo de packer, busquemos en google el copyright pero si las AAA, por lo que llegamos a esta linea en el código de un packer en particular, UPX.

https://github.com/upx/upx/blob/master/src/packer.cpp#L1064

Tras identificar que estamos tratando con UPX, probemos a descomprimir el binario directamente con el comando upx:

upx failed de simulacro

El binario no puede ser descomprimido… Probemos a sustituir esas fastidiosas AAA por UPX. En bless sera mas fácil esta sustitución. Guardamos el archivo como SimulacroySimulacion.upx

bless de simulacro

Tratemos de identificarlo con file y upx:

upx listed de simulacro

Como podemos apreciar, el archivo es ahora correctamente identificado. Procedamos a descomprimirlo:

upx unpcacked de simulacro

Tras la descompresión, carguemos el ejecutable en radare2:

load upx unpcacked de simulacro

Como podemos ver ya no tenemos los mismos mensajes de warning o de información faltante. El único mensaje de warning que tenemos es acerca del tamaño del bloque en cierta función.

Veamos la tabla de Imports

Num Vaddr Bind Type Name
1 0x00000f30 GLOBAL FUNC isspace
2 0x00000f40 GLOBAL FUNC sprintf
3 0x00000f50 GLOBAL FUNC strstr
4 0x00000f60 GLOBAL FUNC sym.imp.std::__cxx11::basic_string<char,std::char_traits,std::allocator>::compare(charconst*)const
5 0x00000f70 GLOBAL FUNC sym.imp.std::__cxx11::basic_string<char,std::char_traits,std::allocator>::c_str()const
6 0x00000000 WEAK FUNC __cxa_finalize
7 0x00000f80 GLOBAL FUNC sym.imp.std::__cxx11::basic_string<char,std::char_traits,std::allocator>::basic_string(std::__cxx11::basic_string<char,std::char_traits,std::allocator>const&)
8 0x00000f90 GLOBAL FUNC memset
9 0x00000fa0 GLOBAL FUNC sym.imp.std::__cxx11::basic_string<char,std::char_traits,std::allocator>::~basic_string()
10 0x00000fb0 GLOBAL FUNC open
11 0x00000fc0 GLOBAL FUNC memcpy
12 0x00000fd0 GLOBAL FUNC __cxa_atexit
13 0x00000fe0 GLOBAL FUNC sym.imp.std::basic_ostream<char,std::char_traits>&std::operator«<char,std::char_traits,std::allocator>(std::basic_ostream<char,std::char_traits>&,std::__cxx11::basic_string<char,std::char_traits,std::allocator>const&)
14 0x00000ff0 GLOBAL FUNC sym.imp.std::basic_ostream<char,std::char_traits>&std::operator«<std::char_traits>(std::basic_ostream<char,std::char_traits>&,charconst*)
15 0x00001000 GLOBAL FUNC sym.imp.std::allocator::~allocator()
16 0x00001010 GLOBAL FUNC __stack_chk_fail
17 0x00001020 GLOBAL FUNC sym.imp.std::__cxx11::basic_string<char,std::char_traits,std::allocator>::operator=(std::__cxx11::basic_string<char,std::char_traits,std::allocator>&&)
18 0x00001030 GLOBAL FUNC sym.imp.std::basic_istream<char,std::char_traits>&std::operator»<char,std::char_traits,std::allocator>(std::basic_istream<char,std::char_traits>&,std::__cxx11::basic_string<char,std::char_traits,std::allocator>&)
19 0x00001040 GLOBAL FUNC sym.imp.std::__cxx11::basic_string<char,std::char_traits,std::allocator>::basic_string(charconst*,std::allocatorconst&)
20 0x00001050 GLOBAL FUNC sym.imp.std::__cxx11::basic_string<char,std::char_traits,std::allocator>::basic_string()
21 0x00001060 GLOBAL FUNC read
22 0x00001070 GLOBAL FUNC sym.imp.std::__cxx11::basic_string<char,std::char_traits,std::allocator>::length()const
23 0x00001080 GLOBAL FUNC sym.imp.std::ios_base::Init::Init()
24 0x00000000 GLOBAL FUNC __gxx_personality_v0
25 0x00000000 WEAK NOTYPE _ITM_deregisterTMCloneTable
26 0x00001090 GLOBAL FUNC _Unwind_Resume
27 0x000010a0 GLOBAL FUNC sym.imp.std::allocator::allocator()
28 0x00000000 GLOBAL FUNC __libc_start_main
29 0x00000000 WEAK NOTYPE gmon_start
30 0x00000000 WEAK NOTYPE _ITM_registerTMCloneTable
31 0x00000000 GLOBAL FUNC sym.imp.std::ios_base::Init::~Init()
6 0x00000000 WEAK FUNC __cxa_finalize
24 0x00000000 GLOBAL FUNC __gxx_personality_v0
25 0x00000000 WEAK NOTYPE _ITM_deregisterTMCloneTable
28 0x00000000 GLOBAL FUNC __libc_start_main
29 0x00000000 WEAK NOTYPE gmon_start
30 0x00000000 WEAK NOTYPE _ITM_registerTMCloneTable
31 0x00000000 GLOBAL FUNC sym.imp.std::ios_base::Init::~Init()

También tenemos Exports…

Num Paddr Vaddr Bind Type Size Name
054 0x000028c2 0x000028c2 GLOBAL FUNC 279 MD5::finalize()
055 0x00001930 0x00001930 GLOBAL FUNC 84 MD5::init()
058 0x00002764 0x00002764 GLOBAL FUNC 306 MD5::update(unsignedcharconst*,unsignedint)
060 ———- 0x00204060 GLOBAL NOTYPE 0 _edata
061 0x000018b4 0x000018b4 GLOBAL FUNC 27 MD5::MD5()
064 0x00002f20 0x00002f20 GLOBAL OBJ 4 _IO_stdin_used
069 0x00001571 0x00001571 GLOBAL FUNC 740 main
071 0x00004008 0x00204008 GLOBAL OBJ 0 __dso_handle
072 0x000011ca 0x000011ca GLOBAL FUNC 224 check(std::__cxx11::basic_string<char,std::char_traits,std::allocator>)
075 0x00002f14 0x00002f14 GLOBAL FUNC 0 _fini
084 0x000018d0 0x000018d0 GLOBAL FUNC 95 MD5::MD5(std::__cxx11::basic_string<char,std::char_traits,std::allocator>const&)
085 0x000010c0 0x000010c0 GLOBAL FUNC 43 _start
087 0x000012aa 0x000012aa GLOBAL FUNC 323 flag(std::__cxx11::basic_string<char,std::char_traits,std::allocator>)
090 0x00000f00 0x00000f00 GLOBAL FUNC 0 _init
091 ———- 0x00204060 GLOBAL OBJ 0 TMC_END
094 ———- 0x00204060 GLOBAL OBJ 272 _ZSt4cout@@GLIBCXX_3.4
096 0x00001b0c 0x00001b0c GLOBAL FUNC 3160 MD5::transform(unsignedcharconst*)
099 0x00004000 0x00204000 GLOBAL NOTYPE 0 __data_start
100 0x000013ed 0x000013ed GLOBAL FUNC 388 debuggerIsAttached()
101 0x00002896 0x00002896 GLOBAL FUNC 44 MD5::update(charconst*,unsignedint)
102 ———- 0x002042a0 GLOBAL NOTYPE 0 _end
107 ———- 0x00204060 GLOBAL NOTYPE 0 __bss_start
108 0x00002b95 0x00002b95 GLOBAL FUNC 113 md5(std::__cxx11::basic_string<char,std::char_traits,std::allocator>)
110 0x00002ea0 0x00002ea0 GLOBAL FUNC 101 __libc_csu_init
111 0x000029da 0x000029da GLOBAL FUNC 307 MD5::hexdigestabi:cxx11const
112 0x00001984 0x00001984 GLOBAL FUNC 175 MD5::decode(unsignedint*,unsignedcharconst*,unsignedint)
114 0x00001a34 0x00001a34 GLOBAL FUNC 215 MD5::encode(unsignedchar*,unsignedintconst*,unsignedint)
118 0x00002f10 0x00002f10 GLOBAL FUNC 2 __libc_csu_fini
119 ———- 0x00204180 GLOBAL OBJ 280 _ZSt3cin@@GLIBCXX_3.4
124 0x000018b4 0x000018b4 GLOBAL FUNC 27 MD5::MD5()
125 0x00002b0d 0x00002b0d GLOBAL FUNC 136 operator«(std::ostream&,MD5)
126 0x000018d0 0x000018d0 GLOBAL FUNC 95 MD5::MD5(std::__cxx11::basic_string<char,std::char_traits,std::allocator>const&)

De la tabla de imports concluimos que tenemos muchas de las funciones del challenge1, por lo que básicamente trabajaremos con strings y md5 nuevamente.

Analicemos main:

main1 de simulacro

Interesante, en este caso tenemos un mecanismo de protección para que el programa no pueda ser debuggeado, adicional al stack canary que hemos visto en los demás programas.

main2 de simulacro

La primera parte se encarga de definir algunos strings, entre ellos “QwertyPassword” y “s3cuma18{TheRockOrLoL?}”. Yo se que este ultimo parece flag, pero no camina como flag, no dice secuma18, dice s3cuma18.

main3 de simulacro

Después el siguiente bloque se encarga de imprimir el mensaje “Introduzca la contraseña: " y el mensaje “\n”, continuamos por tomar el valor y almacenarlo como string en local_a0h. Imprimimos un “\n” después de tomar el valor string para darle padding.

main4 de simulacro

El valor ingresado lo pasamos como parámetro a la función md5, que genera el hash del valor ingresado y lo almacena en RBP-0x60.

main5 de simulacro

Preparamos el hash que hemos generado y lo enviamos a la función check. Ojo que el challenge1 enviaba ambos parámetros, en este caso solo enviamos el hash.

Continuemos en un momento mas con la función check:

main6 de simulacro

Si check retorna 1, bricamos el jump, si check retorna 0, se ejecuta el jump.

main7 de simulacro

Caso verdadero, o que check retorne 1, llamamos a flag y al retornar, limpiamos y terminamos el programa.

main8 de simulacro

Caso falso desplegamos el valor de incorrecto, limpiamos y terminamos el programa.

Analicemos check:

check1 de simulacro

Apenas iniciamos la función, observamos como se carga una serie de caracteres dentro del stack, iniciando desde RBP-0x30 hasta RBP-0x11, osea 32 caracteres.

check2 de simulacro

Continuamos por cargar el parámetro que recibió la función check y el inicio del string y hacemos un compare. Esto básicamente es lo que sucedía en el reto1, donde comparamos directamente los strings a fin de validar si la pass convertida a hash de md5 es igual a la que esta hardcodeada.

check3 de simulacro

Después que termina la comparación se prueba si es igual a 0 el retorno de compare, si es igual a 0, check devuelve 1, si es 1, check devuelve 0. Esto hace sentido a lo que veíamos que sucedía en main.

Con esto nos queda claro que para identificar la contraseña y pasar el if (sin modificar el binario claro), que nos llevara a la función flag, debemos romper el hash. Para ello primero extraemos el hash el cual es:

  b960720998179dc8ddcf01201fd0dcab

Utilizando hashcat para atacarlo por diccionario:

hashcat de simulacro

Ejecutamos ahora el binario e ingresamos la contraseña correcta:

run de simulacro

Nos aparace un mensaje que indica que debemos hacer un xor del string “thematrixhasyou” y el hexadecimal 57654c6c436f4d113c0a390931005a390436430b2331.

flag de simulacro

Tras resolver el xor la password se revela.

secuma18{WeLlCoMeToThEr3AlW0rLD}

Stego

Discographyde (150pts)

When I hack…

There is always a tone for each key, there is always a melody that reminds you of that moment of tension, adrenaline … everything.

And that makes us unable to avoid hiding our darkest secrets in the deepest of those songs.


Comenzamos por descargar el archivo macquayle.7z, el cual contiene 2 archivos, el primero es impenetrable.sd2, que se compone de tema de la serie honor de este CTF y el segundo es un archivo oculto llamado “.hint”. Iniciamos por impenetrable.sd2

Tras realizar un file y un exiftool notaremos que no hay nada de especial:

file de impenetrable

exiftool de impenetrable

Luego con sox genere un rapido espectrograma via sox impenetrable.sd2 -n spectrogram

spectrogram de impenetrable via sox

El espectrograma no nos revela ningún texto legible o algo que podamos identificar rápido a simplemente vista.

spectrogram de impenetrable via spek

Trate de imprimirlo con otra herramienta diferente, que en este caso fue spek, pero tampoco pude visualizar algo particular. Aquí fue cuando un pequeño hit de que existía un archivo dentro del audio, lo cual hace sentido viendo la estructura del espectrograma, que es bastante cuadrado, sobre todo esos picos. Esto es una clara señal de un hide por LSB.

Si recordamos, al inicio se nos dieron dos archivos, un wav y un txt, si leemos el contenido del txt, tenemos lo siguiente:

66736f63696574793030

Que si lo convertimos de hexa a string seria:

pass de impenetrable

Probamos este texto como la contraseña y usamos steghide para extraer archivos del wav:

pass de impenetrable

secuma18{Hide_your_criminal_notes}

What is poetry? (300pts)

Dices, mientras clavas tu pupila azul en mi pupila.


Iniciamos este reto por descargar el archivo What_is_poetry.7z de la pagina del reto el cual al descomprimir, nos entregara un archivo llamado “tyrell.jpg”. Si analizamos este archivo con file y exiftool tendremos lo siguiente:

file de tyrell

Hasta aquí nada extraño, veamos si hay strings dentro del archivo:

strings de tyrell

Strings nos revela 2 cosas importantes, la primera parte es que tenemos un archivo dentro de esta imagen, y el segundo es una clave morse que esta hasta mero abajo.

Convirtamos esa clave morse a texto:

morse2ascii de tyrell

Ahora usemos intensito como contraseña de steghide:

steghide extract de tyrell

El contenido nos manda a una url de una pagina publica donde tenemos los scripts de televisión. Ahí encontraremos el script del piloto de Mr robot, el archivo “Mr_Robot_1x01_-_Pilot.pdf”.

Al abrir este archivo encontraremos muchos diálogos incluidos los que tiene Tyrell y Elliot cuando se conocen. Tomando como referencia “last_word”, osea el nombre del archivo, nos vamos hasta la ultima pagina del script:

ultima pagina del script

Aquí podemos ver que la ultima palabra del documento es BLACK, la cual resulta ser nuestra flag.

secuma18{BLACK}

0ld_m3m0r13s (500pts)

My father and I were very close. He was an example to follow for me.

But … one day I failed him. And from that day he stopped talking to me and looking at me, even the night he died. He did not say anything to me.

I remember … he always told me that people try hard to hide their fears and shadows, but with a little light, everything is discovered.


Tras descargar el archivo r3m3mb3rme.jpg, verificaremos con file y exiftool en busca de información del archivo:

file de r3m3mb3rme

Parece fácilmente identificable que tenemos una imagen dentro de otra imagen vía thumbnails.

Continuamos analizado son vía strings:

strings de r3m3mb3rme

Strings nos revela que tenemos una imagen con steghide entre manos, pero también aparece la palabra flag.txt dentro de los strings reconocibles. Hagamos un binwalk para tener mas detalle:

binwalk de r3m3mb3rme

Con binwalk logramos extraer un poco mas de información y vemos que tenemos dos archivos reconocibles por sus headers dentro de la imagen, flag.txt y A037.zip.

Tratemos de leer flag.txt y abrir A037.zip:

7z de r3m3mb3rme

Como podemos ver, el archivo flag.txt se encuentra dentro del zip, pero como no tenemos la contraseña no podemos ver su contenido.

Muchas horas despues y un hit inadvertido, la imagen r3m3mb3rme.jpg tiene un pequeño texto dentro de la televisión que dice: toseeyouagain

Usando dicho texto como pass del zip:

flag de r3m3mb3rme

secuma18{m4g1c_curv3s}

Forensics

Your time’s running out… (150pts)

jigsaw joker de mr robot

Your computer files have been encrypted. Your photos, videos, documents, etc… But, don´t worry! I have not deleted them, yet. You have 10 hours to decypt this file to get the flag and win!


El reto nos entrega un archivo cifrado llamado flag.txt.fun, el cual al leerlo no podemos identificar ni tenemos ningún indicio.

Tras leer la descripción del reto, vemos que esto se parece mucho a un ataque de un ransomware, por lo que buscamos ransomware fun extensión en google nos lleva a un ransomware llamado jigsaw, el cual tiene un mensaje de estafa muy parecido al que tenemos en el reto:

jigsaw ransom

Tras buscar si existe un decryptor gratuito para este ransom, me tope con el siguiente:

jigsaw-decrypter

El cual también esta recomendado por INCIBE.

Cargamos el archivo fun y el decryptor en una maquina virtual para realizar la extracción:

load de jigsaw

Ejecutamos el decryptor sobre la carpeta donde tenemos el archivo fun:

decrypt de jigsaw

Tras realizar el decrypt del archivo fun, podemos ver claramente la flag:

flag de jigsaw

secuma18{D3scifr4ndo_4ndo}

Rummage (300pts)

Tras una incidencia de seguridad sucedida en Allsafe Cybersecurity hemos detectado que hay comportamientos anómalos en algunos ordenadores. Después de una ardua investigación, los forenses se han hecho con una copia de la memoria RAM de uno de ellos para finalmente llegar a la conclusión de que el culpable se encuentra tras el registro de la configuración del usuario. ¿Crees que podrás encontrarlo?


Tras descargar el archivo Rummage .7z y descomprimirlo, tendremos el archivo Rummage.raw. Sabemos por la descripción del reto que tenemos entre manos un archivo de volcado de memoria, por lo que comenzamos a identificar la versión del sistema operativo con volatility:

imageinfo de rummage

Como podemos ver por la identificación del perfil, debemos tener entre manos un Win7 x86 con SP1. Sabemos de antemano que estaremos trabajando con registros, por lo que iniciamos por hacer el dump a archivo de todos los archivos de registros de la imagen:

dumpregistry de rummage

Ahora si procesamos los registros con hivexml en un for, podemos grepear strings de interés, como secuma:

flag de rummage

Bingo, hemos encontrado la flag dentro de los registros:

secuma18{Tyr3ll_W3llick!!}

Crypto

Cl4s1c P4r4n01d (150pts)

“Tenemos que ayudar a la agente DiPierro, ha conseguido interceptar 3 mensajes de algunos usuarios, pero cada uno usa un cifrado distinto. ¿Puedes ayudarla? Se encontró un texto sin cifrar que decía: La union es la clave.”

BAnkS: ONSWG5LNMEYTQ62BIJ2WO===

irVInG: Cf_V3j3eDhah_n

ROboT: _K1qryi3}


Para este reto, tenemos 3 partes y una llave. Cada texto cifrado viene acompañado de un nombre extraño: BAnkS, irVInG, ROboT…

Si tomamos las mayúsculas de estas palabras tenemos: BAS, VIG, ROT.

Estando un poco mas familiarizados con algoritmos de criptográfica clásicos (tal como el nombre del reto lo indica), podemos relacionar dichas palabras a BASE, Vigenère y rotation.

El primero se trata de un tipo de encoding, el cual siendo puras mayúsculas me llamo la atención, puesto que el encoding mas famoso, base64, hace uso de mayúsculas y minúsculas para la representación de datos. Esto me llevo a probar al hermano menor, base32:

base32 de classic

Excelente, ya tenemos la primera parte, secuma18{ABug.

El siguiente, que se trata de un Vigenère, requiere de una llave, aquí es cuando la palabra “union” entra en juego (la union es la clave)

vigenere de classic

Aunque el output no es claramente visible, realizando los ajustes de mayusculas minusculas y agregando caracteres numeros y de signos tenemos Is_N3v3rJust_a

El ultimo bloque de rotación fue el que más me costo trabajo, ya que rot tiene varias presentaciones, tenemos rot13 que es el mas conocido, pero también tenemos rot18, rot5, rot47, cada uno con sus características y los signos incluidos en las rotaciones. El primer delimitador fue “}” y “_”, ya que la rotación no los debía incluir, pues forman parte de la flag, ahora solo quedaban los números, mayúsculas y minúsculas. Al probarlo ningún mensaje entendible sobresalió, pero al intentar con rot24:

rot24 de classic

La ultima parte es _M1stak3}, por lo que uniendo todo tenemos:

secuma18{ABugIs_N3v3rJust_a_M1stak3}

Fucks0ci3ty (300pts)

Darlene

I think she has to be the key to all this…


Nos entregan un archivo llamado “thisnotasecret.txt” con contenido encodeado en base64, al pasarlo a base64 a un archivo tenemos un file tipo openssl cifrado con una pass con salt.

file de Fucks0ci3ty

Tras revisar arduamente el archivo imagen de Darlene, no encontré ninguna prueba o evidencia que me indique que algún tipo de llave se encontraba dentro del archivo, por lo que literal empece a usar el nombre del archivo Darlene y darlene para tratar de romper el archivo cifrado.

Para ello, trate de ser mas amplio (gracias al hit del autor) y no solo usar AES, que es el cipher mas utilizado para cifrar documentos, sino aplicarle a todos. Para esta tarea cree rápidamente un for para usar todos los parámetros posibles de ciphers y este fue el resultado:

file de Fucks0ci3ty

secuma18{N0b0dY_MaKe_R00tk1ts_L1K3_m3}

Networking

Botnet (50pts)

Uno de nuestros sensores IDS ha detectado tráfico anómalo en las redes de Allsafe Cybersecurity. Tras un análisis exhaustivo de los logs, el equipo de forense se ha dado cuenta de que nuestro router forma parte de una famosa botnet y que el vector de infección final viene determinado por un downloader que hace unas peticiones para descargarse el cliente y poder comunicarse con el C&C con la clave secreta dentro del fichero descargado.

En esta misión necesitamos encontrar el fichero para poder continuar con las investigaciones y denunciarlo ante las autoridades pertinentes.


Uno de los retos de mayor puntaje y tal vez una de mis categorías favoritas. Iniciamos descargando el pcapng.

capinfos de botnet

Empezamos por tomar información sobre el pcapng que tenemos, el cual nos da algunas de sus características de como fue capturado y que capturo. Tenemos 350 paquetes.

udp de botnet

Comenzamos por analizar si tenemos trafico extraño sobre UDP, de manera resumida tenemos que no.

icmp de botnet

Replicamos el mismo procedimiento sobre el trafico ICMP, no encontrando rastros de data extraña o anormal.

trafico http de botnet

Sobre el trafico http, el primer paquete se ve sospechoso, ya que el archivo 7z que descarga, tiene el nombre de una botnet muy famosa, mirai.

detalle del p1 de http de botnet

Al analizar el contenido de este primer paquete filtrado, tenemos el host y el path por lo que podemos replicar lo que la maquina infectada estuvo realizando.

wget de botnet

Descargamos el archivo.

7z fallido de mirai botnet

Tras intentar descomprimirlo, nos topamos con que esta protegido por contraseña. Intentemos romper esta contraseña, con suerte y esta en un diccionario.

generacion de hash de 7z de botnet

Generamos el hash con el script de perl 7z2hashcat.

hashcat del mirai botnet

Ingresamos el hash sobre hascat con las opciones indicadas en la documentacion de 7z2hashcat, como pueden ver en mi caso ya la rompi, por lo que me fui directo al show.

extraccion exitosa de botnet

Extraemos el archivo flag.txt de mirai.7z

flag de botnet

Hemos conseguido la ultima flag de esta entrada. Como nota, el flag no esta en utf-8, ojo.

secuma18{3vil_C0rp¡!}

Agradecimientos

Quiero agradecer a los organizadores de este evento por permitir que personas externas podamos participar. Gracias por hacer una mejor comunidad.