CSCAMP CTF Quals 2014: CTF KeyGen write-up

Created:2014.11.24, last modified: 2015.12.26 by xorpse
Status: Complete

This write-up covers solutions to both ‘CTF KeyGen’ (solved jointly by csn and xorpse) and ‘netcat , cryptcat Who Cares?’ (solved by csn).

netcat , cryptcat Who cares?

The challenge presents us with a pcap packet capture. Using Wireshark it’s possible to visualise the packet capture:

$ wireshark ./pcaponly.pcap

By observation, we find what appears to be a conversation between users at 192.168.1.20 and 192.168.1.21; by filtering on TCP stream 18 (tcp.stream eq 18) we obtain the following transcript:

> hey herp
< hey derp
> sup dude, u remember what we talked about earlier?
< yeah the ctf keygen :D
> hush dude, we don`t want them to log this
> i`ll send u a chat that supports encryption
> open port 6060 and listen for a file transfer
< ok, gimme a sec
> ok i sent it to u
< fine i got it
> switch to cryptcat with key Dagaga
> and port 7070, ill connect
< roger that

The next logical move is to find out what has been transferred on port 6060. Using Wireshark filters ‘tcp.srcport == 6060 || tcp.dstport == 6060’ and then finding the correct stream (TCP stream 19) we obtain a Windows PE file:

This turns out to be netcat. Using the rest of the information from the transcript and applying ‘tcp.srcport == 6060 || tcp.dstport == 6060’ as a filter gives us the encrypted cryptcat conversation (TCP stream 21). We dump it to a file and replay the conversation using netcat:

$ nc -l -p 7070 < dump.raw

…and decrypt it using cryptcat:

$ cryptcat -k Dagaga 127.0.0.1 7070
here
yeah
ok so its stable
now close and i`ll send the file we talked about
on port 7070 too
ok bye

Following the conversation, we again extract the next TCP stream on port 7070, and repeat the above process; this gives us another Windows PE file:

$ nc -l -p 7070 < dump0.raw
$ cryptcat -k Dagaga 127.0.0.1 7070 > decrypted.bin
$ file decrypted.bin
decrypted.bin: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows

The challenge states the flag is the MD5 hash of the decrypted file (which can be calculated using the md5sum utility).

CTF KeyGen

Using the decrypted file, we first execute it to see how it behaves (under Linux this can be done using mono):

$ mono decrypted.bin
Try again

Since there exist tools for automatic decompilation of .NET binaries, we opt for one of those over IDA; using the trial version of .NET Reflector we obtain the C# source code of the ‘KeyGen’. Its Main method is recovered as:

private static void Main(string[] args)
{
    string password = Environment.MachineName.ToLower().Substring(0, 3).PadRight(10, 'Z');
    password = password + (((password[0] % '\x0002') == 0) ? "E" : "O");
    try
    {
        Console.WriteLine(DecryptText("PUIrT3Y/JtMMnOoxew7Mj937vKbBYgVPfOL5MdQ1VgE=", password));
        Console.ReadLine();
    }
    catch (Exception)
    {
        Console.WriteLine("Try again");
    }
}

We see that password is based upon the MachineName of the computer used to generate the encrypted text. Since we have no knowledge of the environment used to develop the executable, our only option is to use a bruteforce attack to guess the correct password. The Main method can be modified as follows to perform the attack:

private static void Main(string[] args)
{
    char[] chars = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
                     'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
                     'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3',
                     '4', '5', '6', '7', '8', '9', '-' };

    foreach (char x in chars)
    {
        foreach (char y in chars)
        {
            foreach (char z in chars)
            {
                String password = ("" + x + y + z).PadRight(10, 'Z');
                password += (((password[0] % 2) == 0) ? "E" : "O");
                
                try
                {
                    Console.WriteLine(DecryptText("PUIrT3Y/JtMMnOoxew7Mj937vKbBYgVPfOL5MdQ1VgE=", password));
                    Console.WriteLine("Password: " + password);
                }
                catch (Exception)
                {
                }
            }
        }
    }
}

Note: since many passwords may produce a ‘correct’ decryption (and there is no fixed flag format), we must enumerate all possibilities and manually filter the correct value.

Reading the rest of the decompiled source code, we notice that additional information from the environment is used to perform encryption/decryption (the UserName):

public static byte[] AES_Decrypt(byte[] bytesToBeDecrypted, byte[] passwordBytes)
{
    byte[] buffer = null;
    byte[] salt = Encoding.ASCII.GetBytes(Environment.UserName.ToLower().PadLeft(8, 'a'));
    using (MemoryStream stream = new MemoryStream())
    {
        using (RijndaelManaged managed = new RijndaelManaged())
        {
            managed.KeySize = 0x100;
            managed.BlockSize = 0x80;
            Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(passwordBytes, salt, 0x3e8);
            managed.Key = bytes.GetBytes(managed.KeySize / 8);
            managed.IV = bytes.GetBytes(managed.BlockSize / 8);
            managed.Mode = CipherMode.CBC;
            using (CryptoStream stream2 = new CryptoStream(stream, managed.CreateDecryptor(),
                   CryptoStreamMode.Write))
            {
                stream2.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length);
                stream2.Close();
            }
            buffer = stream.ToArray();
        }
    }
    return buffer;
}

Since bruteforcing both the MachineName and UserName would be very time consuming, we look for clues for possible values for each within the packet capture. After collecting some candidates and attempting decryption with various combinations of user name (herp) and (derp), and machine name (Computer15-PC) and (Computer06-PC) with no luck, we check to see what meta information has been embedded within the ‘KeyGen’ binary itself.

Running the strings command reveals some build information: an absolute path containing a user name (Adham):

$ strings decrypted.bin
...
C:\Users\Adham\Documents\CTF\bruter\bruter\obj\x86\Release\bruter.pdb
...

Hard-coding this user name into our bruteforcer and letting it run for a while produces the following plausable decryption:

mono bruteforcer.exe
...
IWISHCTFSHADKEYGENS
Password: oziZZZZZZZO
...

…which just so happens to be the flag!