We’ve just got back to work after spending a fantastic few days in Kentucky for DerbyCon 2016.  As with previous years, there was an awesome CTF event, so we thought it’d be rude not to participate.  This post will provide a walk-through of some of the many interesting challenges.  We operated under the team name ‘Spicy Weasel’ and, having submitted a very last minute 3,500 point flag, came in at a fairly respectable third place.

Previous Write Ups

Here are the write ups from previous years:

War Games

There was a host called HELPDESK which was found to have two ports open:

  • 139/TCP
  • 3456/TCP

While performing an nbtscan we were able to divulge the hostname and WORKGROUP for the machine but not the operating system or any other information.

nbtscan

By performing a simple ping against the host we noted the time-to-live (TTL) was 127, which insinuated the host was one hop away and most likely Windows. This technique is not always a guarantee, but typically does the trick.  Linux variants usually have a TTL value of 64, whereas Solaris or Cisco type hosts are 255.

ttl

The only other port that appeared to be exposed was 3456/TCP. Once connected to this service, it was immediately apparent that this was the challenge. By circulating through the games as demonstrated below, the response from “GLOBAL_THERMONUCLEAR_WAR” returned a statement which was along the lines of “the only way to win the game is by not playing”.

the-game

We tried some common injection techniques which appeared to all result in failure. Then, the DerbyCon CTF team sent a tweet out with the following information:

tweet1

After a while trying many other functions, we had a response from the following function calls:

  • File()
  • Exec()

We attempted to find the source language without success, but combining the tip from @DerbyConCTF and the Exec() functions, we were able to use the following technique to input commands on the host.

  • str$(exec(“ping 172.16.70.92”))

By running Wireshark and looking out for ICMP traffic we were able to see the response from the host and prove the concept of command execution. One interesting finding was the response from the service. We attempted many other executables and if the return value was , this meant that the attempt failed. This was really strange; when we ran ping and telnet it worked, whereas cmd /c ping did not. We still thought that the challenge would be fairly easy at this point; just run PowerShell to get code execution on the host and the job would be almost done, however this also failed with a return code of .

To our disbelief, we ran command.com /c ping 172.16.70.92 and got a response. At this point we knew the host was  either Windows XP or even earlier and that we had no PowerShell or cmd shell. We tried a technique like net use to see if we could capture the challenge response hash from the user with Responder, however this failed and the CTF team tweeted about this:

tweet2

Looking at the screenshot this appeared to be older than Windows XP; perhaps Windows 98 or 95. We then searched for other techniques to gets files on the box in a one liner, such as TFTP, but this was not present either.

We thought that we may need to run a VBS script or similar on the host by appending each line separately. The only issue with that was that we would need to add speech marks inside of speech marks. Every attempt to bypass escaping the internal speech marks failed, until we noticed that the service accepted values in the form of their ASCII character.

We then threw together a simple script to return each value in their ASCII format and attempted this but failed when running the VBS file; some of the functions we were using may not have been released on this version of windows.

asciipy

After a while searching for alternatives to get executables on a host, we remembered the technique FTP –I –s:<FILE> .  This basically opens FTP and runs the series of commands in a text file. This was the final set of commands we needed to get nc.exe and reg.exe on the host.

rce

At this point we were able to telnet to port 9999 and have full command line access to the host. After searching for flags we found two of them on the host. One was on the root of the C:\ drive and another was hidden in the registry. Using regedit.exe /e reg.txt we were able to download a complete copy of the registry and search for Run Keys.  The registry flag was FlagMalwareBytes and it was this flag that netted us 3,500 points in the final two minutes.

regedit

After the CTF was finished we also found out that there was another flag in the user.dat file, but we were unable to find that in the time we had left.

Hillary’s Email

In this exercise we were provided with three files: two apparently containing corresponding plaintext and ciphertext (“classification_guide_utf32le.txt” and “classification_guide_utf32le.enc”), and one containing ciphertext to be decrypted (“hillary.eml.enc”).  These files can be downloaded here: hillary_encrypted.zip.

Block ciphers, such as DES or AES, typically act on a block of 64 or 128 bits of data at a time. If you want to encrypt a message of arbitrary length then you need some method for applying the block cipher multiple times. This is called a “cipher mode”. The simplest cipher mode, known as ECB (Electronic Code Book) mode, simply breaks the message into chunks of the appropriate size, then encrypts or decrypts each one separately.

Unfortunately, ECB mode has one serious weakness: if you encrypt the same block twice then the ciphertext will be identical. Indeed, the presence of identical blocks of ciphertext is a strong indication that ECB mode has been used. It was not necessary to look far: the first two 64-bit blocks of “classification_guide_utf32le.enc” are identical:

As expected, the corresponding plaintext shows identical content for the first two blocks:

ecb2

This tells us that, provided the encryption key remains the same, anywhere we see the ciphertext 7a058ae62050197f then the corresponding plaintext will be fcff0000fcff0000. Unfortunately this particular sequence does not occur in “hillary.eml.enc”, but there are other blocks which do.

For example, the first and second blocks in “hillary.eml.enc”:

Can be found at offsets 7bf0 in “classification_guide_utf32le.enc“:

ecb4

and offset 0800:

Looking up the corresponding blocks in “classification_guide_utf32le.txt“ at offset 7bf0:

ecb6

and offset 0800:

ecb7

allows us to decrypt the first 16 bytes of the message:

52 00 00 00 65 00 00 00 63 00 00 00 65 00 00 00

Why all the zeros? Because this is a somewhat contrived example. The files appear to be using UTF-32, a Unicode encoding method which stores only two characters in each 64-bit block – thereby greatly improving the odds of seeing identical blocks when compared to more common encodings such as UTF-8. It is unusual to be see UTF-32 used in files because of the amount of space it occupies, and it is unlikely that cryptanalysis of UTF-8 would be as successful. However, it is not unusual for non-text formats to contain patterns that would be vulnerable to this technique, and in text files even short fragments might be damaging.

It is a straightforward matter to automate the decryption:

  • Construct a codebook of known mappings from blocks of ciphertext to blocks of plaintext, by reading the encrypted and unencrypted copies of “classification_guide_utf32le” in step with each other one block at a time.
  • Attempt to decrypt “hillary.eml.enc” by reading it one block at a time, then looking for an entry in the codebook. If found then print the corresponding plaintext, otherwise print a marker to indicate that the block could not be decrypted.

We threw the following code together.  Note that it is not a good example of how to safely read a file in POSIX, but it only had to work once.

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
#include <map>
#include <string>
#include <iostream>
std::map<uint64_t, std::string> codebook;
std::map<uint64_t, int> notfound;
int main() {
        int ptfd = open("classification_guide_utf32le.txt", O_RDONLY);
        int ctfd = open("classification_guide_utf32le.enc", O_RDONLY);
        int sz = 0;
        while (1) {
                char pt[8];
                uint64_t ct;
                ssize_t ptc = read(ptfd, &pt, 8);
                ssize_t ctc = read(ctfd, &ct, 8);
                std::string pts;
                for (int i = 0; i != 8; ++i) {
                        if (pt[i] != 0) {
                                pts += pt[i];
                        }
                }
                codebook[ct] = pts;
                sz += 8;
                if ((ptc != 8) || (ctc != 8)) break;
        }
        printf("%d\n", sz);
        close(ptfd);
        close(ctfd);
       int fd = open("hillary.eml.enc", O_RDONLY);
        while (1) {
                uint64_t ct;
                ssize_t c = read(fd, &ct, 8);
                if (c != 8) break;
                if (codebook.find(ct) != codebook.end()) {
                        std::cout << codebook[ct];
                } else {
                        std::cout << "??";
                        notfound[ct] += 1;
                }
        }
        std::cout << std::endl;
}

This resulted in the decryption of 451 blocks out of 530 (approximately 85% of the message). Using a double question mark for each missing block (on account of the fact that blocks typically contain two non-null characters of plaintext):

Received: from m??.clintonmail.com (10.1??10.10) by
 clientaccess.clintonmail.com (1??10.1??12) with Microsoft S??P Server (??S)??id 15.1.225.?? via Mailb?? Transport; Th?? 1 Sep 2012 12:14:02 -??00
 Subject: Good News the Saudi Prince will pay to meet!
 To: <Hillar??clintonmail.co??
 Date: Thu, 1 Sep 2016 1??14:00 ??000
 Message-ID: <1??????????clintonmail.com.co??
 From: Huma ??edin <??edin.H??lintonmail.com??MIME??ersion: 1.0
 ??MS??xchange-Organization??etwork??essage???? 3??02ade-??????????????-0??????????5
 ??MS??xchange-Organization????tamp??nterprise: 1.0??????Exchange??rganization-??thAs: Anonymous
 ??MS??xchange-Transport-En????nd??tenc?? 00:00:01.01????4
 Content-type: Text??lain????y ????Its ??ma.&nbsp; I have good news, we can a ??00??'donation' to the global intitive
 if you meet with the Saudi Prince this saturday.&nbsp; I did a little co??ing of
 the books over there ahead of time so we should be able launder it and have
 it in the campaign war chest by the following Thursday at the latest.????ap?? to help secure a little more ??ag??lintonCash$ ????

English text – and especially e-mail messages (with their machine-generated headers) – contain enough redundancy to guess most of the remainder. This also allows a small amount of further cryptanalysis to be performed, since any block guessed can be entered into the codebook and potentially used to decrypt other blocks. In any event, the last line of the output was sufficient to successfully determine the flag.

RNC Website

Continuing with the political theme for most of the boxes, the RNC decided to join the fun by announcing on Twitter that they too had connected a box to the CTF network.

rnc1

Viewing the rendered site in a browser didn’t reveal anything, but it did when you looked at the HTML source code. This showed that a link to a page called test.php had been commented out. Navigating to this page revealed a text box that allowed you to run PHP functions when you clicked the Login button. By default the value in the text box was phpinfo() which, when submitted, resulted in the contents from phpinfo() being returned to the client.  Interestingly, though, there was also the comment “code looks good”, which implied there was some kind of filtering going on.

rnc2

Our first attempts at executing payloads such as exec(“whoami”) did indeed show that filtering was being performed. Through trial and error, the following characters were found to trigger the filter.

  • ;
  • .
  • $
  • |
  • %
  • &
  • …etc.

Each of these did, however, reward us with a low value flag.

One of our favourite techniques for bypassing XSS filters is to use JavaScript’s String.fromCharCode() function. This allows you to create a string by passing a sequence of integers, each of which represents the numerical value of each character of the string that you want to encode. PHP has a similar function called chr() which will return a string from the numerical value passed to it.

Previous testing showed that the dot operator was banned. This ruled out concatenating a series of calls to the chr() function together. Further testing showed, though, that there no was problem calling either the array() function, which takes a variable number of parameters and returns an array, or implode()which combines the values in an array together and returns a string.

Here is an example.  The payload:

exec(‘whoami’);

would be encoded into:

implode(array(chr(0x65),chr(0x78),chr(0x65),chr(0x63),chr(0x28), chr(0x27),chr(0x77),chr(0x68),chr(0x6F),chr(0x61),chr(0x6D), chr(0x69),chr(0x27),chr(0x29)));

This command worked in so much as it bypassed the filter, but it appeared that exec() had been disabled on the server (or perhaps the tiredness was just really kicking in at this point).

It’s also worth noting that passing code that didn’t eval correctly leaked the location of test.php on the drive. In all fairness though, it would be have been pretty easy to guess, plus it’s buried in phpinfo() output too.

rnc3

It was possible to read this by encoding the path using a script we quickly threw together and then passing that encoded value to the function readfile(). This gave us all the flags within the file and a proper idea about what was going on.

readfile(implode(array(chr(0x27),chr(0x2F),chr(0x76),chr(0x61),chr(0x72),chr(0x2F),chr(0x77),chr(0x77),chr(0x77),chr(0x2F),chr(0x68),chr(0x74),chr(0x6D),chr(0x6C),chr(0x2F),chr(0x74),chr(0x65),chr(0x73),chr(0x74),chr(0x2E),chr(0x70),chr(0x68),chr(0x70),chr(0x27))));

rnc4

Using a combination of print_r(), which prints human readable info about a variable, and scandir(), which enumerates all the files in a path, we were able to start having a look around the drive. We first found the file /flag.txt.

rnc5

We then discovered that there was a backup of the public and private ssh keys in the path /home/rnc/backup. Once again encoding and using readfile(), we were able to download the private key and log on to the box.

rnc6

Privilege escalation of this box is left as an exercise for the reader.  If you really want to know, then get in touch.

Goats.wav

In this exercise we were supplied with an audio file named goats.wav (click to download):

The overt content was unenlightening, and nothing was found in the file metadata, so the most obvious line of enquiry was to look for information that had been steganographically hidden within the audio data.

The classic method for embedding data in an audio file is to hide it in the least significant bit of each sample. Analysis of the supplied file showed that there was indeed statistical evidence of something having been done to those bits, but with some unusual characteristics:

  • Channel 0 (left) was highly non-random (99.92% bias towards zeros), whereas the statistics for channel 1 (right) were consistent to zeroth-order with random content (50.1% zeros, 49.9% ones).
  • The 86 binary ones recovered from channel 0 all occur within the first 140 samples of the file (a local bias of 61% towards ones). All of the remainder were zeros. This is a strong indication that a message has been found.
  • Within those 140 samples, every fourth bit is a binary one. This contrasts which ASCII text, where you would expect structure with a period of 8 bits, with a high concentration of ones in bit 6, and a very high concentration of zeros in bit 7.
  • Furthermore, 140 bits is a multiple of 4 but not a multiple of 8.

This hints at the possibility that only part of the data had been found, so histograms were constructed for each bit of the samples from each channel:

 Bit Zeros (Left) Ones (Left) Zeros (Right) Ones (Right)
0 109418 86 54866 54638
1 54673 54831 54673 54831
2 54763 54741 109455 49
3 55407 54097 55407 54097
4 55921 53583 55921 53583
5 55822 53682 55822 53682
6 55813 53691 55813 53691
7 56028 53476 56028 53476
8 56472 53032 56472 53032
9 56335 53169 56335 53169
10 56499 53005 56499 53005
11 56452 53052 56452 53052
12 56461 53043 56461 53043
13 56882 52622 56882 52622
14 56671 52833 56671 52833
15 56667 52837 56667 52837

In addition to the bits already found, one other location within the data stands out as being non-random: bit 2 of the channel 1 (right). Assuming a periodicity of 4 bits on each channel, and considering only the first 140 pairs of samples:

Row Channel Bit Offset Zeros Ones
0 0 (Left) 0 +0 20 15
1 1 (Right) 2 +0 20 15
2 0 (Left) 0 +1 13 22
3 1 (Right) 2 +1 25 10
4 0 (Left) 0 +2 21 14
5 1 (Right) 2 +2 11 24
6 0 (Left) 0 +3 0 35
7 1 (Right) 2 +3 35 0

This shows a 100% bias towards ones in row 6 and zeros on row 7 – stronger than would be expected in normal ASCII text, but close enough to make an attempt at decoding it the reasonable next step (with rows 0 and 7 corresponding to the least and most significant bits respectively).  The following code was put together.

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char* argv[]) {
        int fd = open(argv[1], O_RDONLY);
        if (lseek(fd, 44, SEEK_SET) == -1) {
                perror("lseek");
        }
        while (1) {
                char buffer[16];
                ssize_t count = read(fd, buffer, sizeof(buffer));
                if (count != sizeof(buffer)) {
                        printf("\n");
                        break;
                }
                int d = 0;
                int i;
                for (i = 0; i != 4; ++i) {
                        d = (d * 2) + ((buffer[14 - i * 4] >> 2) & 1);
                        d = (d * 2) + ((buffer[12 - i * 4] >> 0) & 1);
                }
                if (d != 0) {
                        printf("%c", d);
                }
        }
}

This yielded the flag:

UpUpDownDownLeftRighLeftRighABStart

The reason for the having no zeros in bit 6 is now clear. Because the flag consists entirely of upper and lower case letters, all of the corresponding ASCII codes fall in the range 65 to 90 and 97 to 122 inclusive, so expressed in binary they all begin with the digits zero then one.

Conclusion

Thanks to everyone at DerbyCon for both the conference and, in particular, the CTF.  We felt privileged to be able to take part and to receive challenge coins for cracking some of the harder challenges.  We’d highly recommend the conference to anyone in the infosec community and look forward to attending next year!  Now, time to go and watch some of the talks we missed online…

challenge-coin