w0pr (@abeaumont en concreto) fue el primer y único equipo que consiguió hacer ingeniería inversa de ese monstruo ¯\_(ツ)_/¯ Alfredo ha publicado el write-up, merece la pena leerlo (y replicarlo) con detalle.
— BEGIN TRANSMISSION — PAYLOAD FOUND ON trololo@54.171.128.20:34342 ACCESS b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACD48tA2UHkNwf1gjzFoefbSiiA3s0+FIYWYOlxHuwDAVAAAALhSf19rUn9f awAAAAtzc2gtZWQyNTUxOQAAACD48tA2UHkNwf1gjzFoefbSiiA3s0+FIYWYOlxHuwDAVA AAAECeukBbUT2Vlozfd98BRRvKGCFRc0mdvRhAItlDfp1U7vjy0DZQeQ3B/WCPMWh59tKK IDezT4UhhZg6XEe7AMBUAAAALnJvb3RAaXAtMTcyLTMxLTYtNjUuZXUtd2VzdC0xLmNvbX B1dGUuaW50ZXJuYWwBAgMEBQYH IDENTIFY USING 20ce8a7cc776a39ad291d4648e3e39ae.hax SEND FLAG AS TEXT — END TRANSMISSION —
Si nos intentamos conectar por ssh trololo@54.171.128.20 -p 34342 vemos que el servidor responde, pidiendo clave pública. Descodificando el string de ACCESS de base64, vemos una referencia a una máquina remota (AWS) y esta keyword: openssh-key-v1nonenone3
Haciendo pruebas, detectamos que la clave sigue el algoritmo DSA (no RSA). La preparamos:
$ ssh -i id_dsa -p 34342 trololo@54.171.128.20 Last login: Sat Mar 28 14:34:31 2020 from xxxxxxx -sh-4.2$
Es una shell restringida (ni ls, ni gaitas). Además, si intentas borrar algo, te añade un espacio en blanco… En fin… El autocompletamiento con tabulador funciona. Vemos que existen algunos directorios, entre ellos /bin. Añadimos /bin al PATH y disponemos de cat, dig, ls y nsupdate.
El comando dig nos da una pista…. recordemos también que no hemos usado esta parte del enunciado:
IDENTIFY USING 20ce8a7cc776a39ad291d4648e3e39ae.hax
Investigando un poco vemos que existe un proyecto con TLD .hax para DNS dinámicos. La otra pista «SEND FLAG AS TEXT » nos parece indicar que necesitamos crear un registro TXT con la Flag. ¿Pero dónde está la flag? Bueno, no había muchos directorios en la máquina restringida donde nos encontramos, así que rastreando carpetas nos encontramos con /var/tmp/secret,
¡Ayuda! ¡Nos han hackeado la cuenta de Discord! Parece que han hecho cosas raras. Hemos conseguido un log de lo que han hecho…
Nos pasan un fichero .har (un dump de una conexión HTTP). Aunque internamente es simplemente un enorme fichero JSON, lo mejor para visualizarlo de forma rápida es abrirlo directamente con el editor de Chrome DevTools, pestaña Network. Importamos el fichero HAR para echarle un vistazo y vemos lo siguiente.
Seleccionando uno de esos puntos:
El problema es que no vale con recorrer esos mensajes de tipo POST. Hay una complicación: el autor del reto ha editado algunos trozos (mensajes con método HTTP PATCH) e incluso borrado otros (mensajes DELETE).
Este level parece que se le atragantó a muchos grupos. Aunque estuvimos unas cuantas horas dándole vueltas, una vez resuelto te das cuenta de que, lo que lo hacía complejo, realmente eran varios red-herring o falsas pistas. Si las seguías, estabas muerto. El level empieza con 3 ficheros: yellow, red, green. Aquí está el primer anzuelo: ¿para qué estos colores?… En fin, sacando strings, el que más llama la atención es red.
Vaya… una base de datos, probablemente SQLite. Y el campo redz de la tabla red1000 es de tipo blob. Estuvimos dándole vueltas y vueltas a esto. Conseguimos incluso importar la estructura de las tablas.
En la tabla red1, la columna reda tiene algo:
Pero eso ya salía en los strings, no hacía falta liarse la manta con SQLite… Mmmh, veamos qué significa:
misterio = [0x54,0x73,0x65,0x72,0x6d,0x34,0x33,0x52,0x00,0x0a]
import binascii
print("".join( chr(c) for c in misterio))
Tserm43R
¿Tserm43R? WTF? @ochoto comentó en el grupo que tal vez habría que darle la vuelta a cada par de valores (big endian?) porque los últimos bytes son un salto de línea + fin del string invertidos (0x00, 0x0a). Vamos allá (quitando el salto de línea):
misterio = [0x54,0x73,0x65,0x72,0x6d,0x34,0x33,0x52]
"".join([chr(a)+chr(b) for a,b in [i for i in zip(misterio[1::2], misterio[::2])]])
'sTre4mR3'
Tiene sentido, parece un trozo de string en h4x0r. Dejémoslo ahí y vayamos a por green. Este fue más fácil:
$ binwalk -e green
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
27337196 0x1A121EC HPACK archive data
33554432 0x2000000 gzip compressed data, has original file name: "trololo", from Unix, last modified: 2019-07-15 23:29:50
$ ls -al _green.extracted/
total 8
drwxr-xr-x 3 juanan wheel 96 Jul 25 21:28 .
drwxrwxrwt@ 70 root wheel 2240 Jul 29 22:15 ..
-rw-r--r-- 1 juanan wheel 8 Jul 25 21:28 trololo
$ cat _green.extracted/trololo
ce1VEd!
Vaya, si concatenamos red con green (mismo orden que el enunciado), obtenemos ‘sTre4mR3ce1VEd!’. Tiene muy buena pinta. Sólo nos queda un fichero, yellow. Es un fichero binario, sin ningún magic number ni strings asociados. Tras muchas vueltas, se nos ocurrió algo evidente (a que sí, @navarparty? XDDD), abrirlo con Audacity:
Bingo, se oye a alguien deletreando, en inglés y a mucha velocidad, la parte que nos falta del password. Ajustando la velocidad y teniendo en cuenta que las zonas más oscuras de la señal reflejan mayúsculas, obtenemos R0tT3nB1t.
Así que… R0tT3nB1tsTre4mR3ce1VEd!
Nota: este post no refleja la dificultad de la prueba. No fue «tan fácil» como parece 🙂 Estuvimos muuuuuuuuuucho tiempo analizando los 3 binarios hasta encontrar la secuencia de pasos y herramientas adecuadas.
DRM y HTML5. EME (Encrypted Media Extensions). Hay que empaparse algo sobre estos temas para resolver el nivel. EME ofrece un API que permite a las aplicaciones web interactuar con sistemas de protección de contenido para poder reproducir audio o video cifrado. El famoso DRM en HTML5, algo que muchos consideran una aberración (la web nació para ser abierta, no para ofrecer contenidos cerrados). Pero… ahí está el API. Y es precisamente lo que hay que intentar resolver. Básicamente el cliente tiene una etiqueta video. Al pulsar el play se visualizan 26 segundos. Pero a partir de ahí, todo está negro. Parece que el video webm está protegido. En el código vemos que en un momento dado se hace una petición de licencia a un servidor license, que nos envía la clave para desproteger el webm.
Pero esa petición sólo se puede hacer si rellenamos los bytes que faltan… esos bytes forman parte de la solución al sudoku que nos han puesto debajo del vídeo. ¿Qué hacer cuando tengamos la clave de desprotección del vídeo? Visualizarlo en el navegador 🙂 ¿Y después? Bueno, eso lo veremos enseguida… Vayamos por partes. Lo primero es solucionar el sudoku. Lo siguiente es automatizar el proceso de meter los números en las casillas del sudoku (hacerlo a mano es un infierno). Solucionar el sudoku es fácil. Entramos en sudoku-solutions.com, metemos los datos y pulsamos en check…
Vaya, tiene 9 soluciones posibles. No podía ser tan fácil …
Para no perder tiempo tecleando cada una de ellas, podemos automatizar el proceso. Abrimos la consola JavaScript y tecleamos:
var sudoku = $("#su input")var s ="852931647473862159961547283318476925549328761726159834637294518194685372285713496"for(var i =0; i < sudoku.length; i++){ sudoku[i].value= s.charAt(i);}
$("video")[0].needs_reload=1;
var sudoku = $("#su input")
var s ="852931647473862159961547283318476925549328761726159834637294518194685372285713496"
for (var i = 0; i < sudoku.length; i++){ sudoku[i].value = s.charAt(i); }
$("video")[0].needs_reload = 1;
Por cierto, en ese código ya va la solución correcta 🙂 La última línea informa al navegador que el sudoku ha cambiado y debe leer sus datos. Bien, todo preparado. Pulsamos play y vemos que pasamos del segundo 26. Es un trailer de «Inception». Hay una serie de fotogramas que muestran pixels distorsionados. Seguramente porque se haya introducido por ahí algún string que no debería estar… Habrá que bajar el webm, descifrarlo y abrirlo más o menos por esa parte, para ver de qué string se trata.
¿Pero cómo obtenemos la clave de descodificación del webm? (el navegador la conoce, pero necesitamos aislarla…) ¿Por cierto, cuántas claves habrá? Vamos allá.
Abrimos main.js y metemos un punto de ruptura en la línea 71
}).then(function(e){var n =(e =new Uint8Array(e)).slice(0,12);return window.crypto.subtle.decrypt({
name:"AES-GCM",
iv: n,
tagLength:128}, r, e.slice(12))}).then(function(e){
breakpoint --->return t.target.update(e)})
}).then(function(e) {
var n = (e = new Uint8Array(e)).slice(0, 12);
return window.crypto.subtle.decrypt({
name: "AES-GCM",
iv: n,
tagLength: 128
}, r, e.slice(12))
}).then(function(e) {
breakpoint ---> return t.target.update(e)
})
En e tendremos la clave. Ojo, veremos que el breakpoint se ejecuta dos veces, y necesitaremos apuntar ambas claves (una es para cifrar el vídeo y otra para cifrar el audio). Creo recordar que no era «tan sencillo», sino que había que convertir el formato de las claves obtenidas en «e» con una línea como
atob(String.fromCharCode.apply(null, new Uint8Array(e)))
y a continuación extraer las claves de 16 bytes con un script como el siguiente (una de las claves era w-UHS…):
var b64string ="w-UHS56ogAQacZLNj1TpqA";var buf = Buffer.from(b64string,'base64');var fs = require('fs');
fs.writeFile("key.txt", buf,"binary",function(err){if(err){
console.log(err);}else{
console.log("The file was saved!");}});
var b64string = "w-UHS56ogAQacZLNj1TpqA" ;
var buf = Buffer.from(b64string, 'base64');
var fs = require('fs');
fs.writeFile("key.txt", buf, "binary",function(err) {
if(err) {
console.log(err);
} else {
console.log("The file was saved!");
}
});
Momento de descargar el vídeo (cifrado) y descifrar. ¿Cómo desciframos? Bien, sabemos la clave y tenemos el vídeo cifrado. Nos falta saber cómo se cifró. Investigando un poco, nos encontramos con la utilidad webm_crypt de las webm-tools. Tiene una dependencia con libwebm, pero siguiendo las instrucciones de compilación del anterior enlace, lo podremos obtener sin problemas (en Linux, en macOS no iba).
Y por fin, podremos abrir el fichero decrypted.webm (por ejemplo, con vlc…)
o con strings (!)
$ strings-n10 decrypted.webm
$ strings -n 10 decrypted.webm
(Nota: -n 10 = dame los strings ASCII de decrypted.webm que puedas visualizar, siempre y cuando esos strings sean de longitud mayor o igual a 10)
Y analizando la salida de strings, veremos la clave para el siguiente nivel 🙂
PD: creo que hay una herramienta que te permite pasar como input un vídeo webm y una marca de tiempo (hh:mm:ss) y te da como salida el contenido del fotograma de esa marca de tiempo. Lo cual te evitaría el uso de strings (o lo facilitaría). Pero eso lo dejo para que la gente de W0pr, navarparty o Barcelona92 nos lo cuenten en los comentarios.