Monday, October 3, 2011

DerbyCon CTF Results and Notes

This weekend I attended DerbyCon, a hacker convention being held for its first time in Louisville, Kentucky. It had great talks by industry heavyweights in security, and a really awesome and fun CTF game. Initially, I wasn't even planning on playing the CTF. I had never done anything like the CTF before, and expected to be trounced. As it turns out though, a friend of mine, TheLightCosine, and I were bored and decided to check it out. TheLightCosine was actually taking Win32 exploit development training from corelanc0der and wasn't able to compete very much. Even when not in training, his brain was fried. The training was a bootcamp. With some help from TheLightCosine, however, I was able to place 5th on the CTF. Next year, I plan to be more organized and take the game a bit more seriously. All the notes I list here were just kept in my head, so I may miss a few things. This is also an abridged version.

The rules were simple. A small network was setup (derbycon_ctf) with no internet connection. There were two public targets that you were allowed to hack on (10.1.1.15,10.1.1.16), and one public target that was off limits (10.1.1.10). The latter was where the scoreboard resided and where you submitted your flags for your points.

The first thing I did when joining was nmapping the two targets that we were allowed to hack.

nmap -sS -O -PN -PU 10.1.1.15,16


This gave me an initial idea of what services the boxen were running. 10.1.1.15 had ports 80, 13370, and 3389. 10.1.1.16 was running 21,80,443, and 3389. Both were windows 2003 boxes. Occasionally, ports 23, 25, and 1337 would open on 10.1.1.16. This really confused me, but I assumed right off the bat that they were actually netcat listeners. I was never able to connect to one as someone always found them before I did. During the closing ceremonies, when the CTF prizes were given out, my assumption was confirmed by the CTF admins. They were netcat listeners.

One of the most important stages when hacking into machines like this is simple information gathering. We have HTTP/S ports, we have FTP, and we have MS Terminal Services. It turns out that port 13370 on 10.1.1.15 is also HTTP. The SSL Certificate for the HTTPS port on 10.1.1.16 was invalid, something to note for later use.

First things first, I hit up the ftp port. 'Lo and behold, a flag was waiting for me in the banner (Flag=AnonymousFTP). Logging in I found about 10 or so files on the ftp and two folders. One folder was locked down, I couldn't get in. The other, however, contained a text file with usernames and hashes. Other files on the root of the ftp were firefox databases for saved credentials, an .NET exe that you were required to reverse engineer (I almost figured this one out), a pcap file, and a file with a .docx extension (though it is just a plain text file). I downloaded these files to a local folder for later processing. My first action was to crack those hashes I found on the ftp server. I used john for this.


root@gits-and-shiggles:/home/upgraydd/Pictures/hidden# john --show secretdata
Administrator:NO PASSWORD:500:28361B9A6A28663E73EB37AA1787B284:::
derbycon:KENTUCKY:1012:8CFC8328E285BAE5702FB32AE7C95F87:::
ftpuser:FTP1:1013:2AED8B7C119F79B4F81D3FF9EB1760F3:::
jamesbond:007:1015:0B0412D8761239A73143EFAE928E9F0A:::
root:TOOR:1014:AFC44EE7351D61D00698796DA06B1EBF:::
sqldb:NO PASSWORD:1007:9CB9DCE36C9566A195A42282ADC6A404:::
texasranger:CHUCKNORRIS:1016:167A7A68DEA1D4FBD7B3F4F444690F24:::

9 password hashes cracked, 0 left
root@gits-and-shiggles:/home/upgraydd/Pictures/hidden#


This gave me credentials to work with now. None of these creds allowed me to get into the locked folder on ftp like I expected. I set these aside for later use. Once I had these, I decided to take a look at the terminal services ports. I used tsclient to connect to both 10.1.1.15 and 10.1.1.16. This gave a me a flag, but none of my credentials worked to log in. The flag, interestingly enough, was WasteOfTime. I decided to start perusing the http ports next.

10.1.1.16:80 gave two flags actually. One in the title of the index page, and one as an HTML comment. Super easy stuff. 10.1.1.16:80 also gives you a url to 10.1.1.15:13370/upload/upload.aspx. I wasn't able to break this script and get the flag I wanted.

Before I forget, one of the files on the FTP root was a file called qr.jpg. opening this up and reading the qr code with my phone yielded a flag. Dumping the exif data showed and interesting sup3rs3cr3tk3y string, apparently this was a flag, but it was not as apparent as the rest. When I found this out, I /headdesk'ed.

If you go to the HTTP root of 10.1.1.15:13370, you find a replica of the derbycon.com website. It is slightly altered however, a few flags are thrown around inside and in cookies. There is also a new News page, which I figured out a sql injection for to receive another flag. Thankfully, TheLightCosine showed me how to save the post request with the sql injection via burpsuite and pass the request to sqlmap. This was a gold mine, giving me many more flags. I missed one however, and I have no idea where it would have been. It also turns out the version of sqlmap in the Ubuntu repos is very old. I needed to download the latest release from sourceforge in order to use this functionality (the -r flag in sqlmap). Also on this news page was an HTML comment with some credentials. I found this very early on and tried it on the FTP with no success. This bothered me because the credentials were ftpuser:ThisWillGetYouIn. It turns out the admins mistyped the username. It was supposed to be ftpadmin:ThisWillGetYouIn. Once they realised what happened, they updated the scoreboard with some vague information about an FTP credential on the site being fixed. I saw this, went back and grabbed the new creds. This worked on the FTP and got me into the folder I was not allowed in earlier. Inside the folder was a textfile with another flag.

I also remembered at this point I had yet to look at the robots.txt file on any of the web servers. This also led to two flags being found. One in the robots.txt file itself, and one that was referenced by the robots.txt.

While I let sqlmap dump what it found, I decided to go ahead and look at the files I got off ftp one more time. Three files jumped out at me. signons.sqlite, cert8.db and keey3.db. These files are how Firefox stores its stored credentials. I don't use firefox, and actually uninstalled it quite a long time ago off my netbook. I installed it, dropped the files into my user profile, went to Properties > Security > Show passwords in firefox and got another flag.

One thing I found in the /download folder of 10.1.1.15:13370 was a testkey.pem.txt. This was a private key. The pcap file on the ftp had SSL traffic in it, so TheLightCosine showed me how to decrypt the SSL traffic in the pcap file through wireshark. Once decrypted we found another flag. However, I felt like there was more to this pcap file than met the eye. I ran the pcap file through strings and ended up finding yet another flag.

At this point, I felt like I had exhausted the web servers for clues. I decided to run nikto on each port offering HTTP on both 10.1.1.15 and 10.1.1.16. This yielded a flag in the SSL cert and a vulnerable version of FCKeditor which I was unable to pop. During the closing ceremonies, the admins also showed us a file that nobody had gotten. A web.config.txt was sitting on the root of one of the web servers.

At this point, I turned my attention to the .NET exe. I was able to use mono to run it.


root@gits-and-shiggles:/home/upgraydd/Pictures# mono fu.exe
WARNING: The runtime version supported by this application is unavailable.
Using default runtime: v1.1.4322
No flag for you.
root@gits-and-shiggles:/home/upgraydd/Pictures#


I decided to see what happened when I passed it an argument.


root@gits-and-shiggles:/home/upgraydd/Pictures# mono fu.exe fdjskla
WARNING: The runtime version supported by this application is unavailable.
Using default runtime: v1.1.4322
Try Harder N00b.
root@gits-and-shiggles:/home/upgraydd/Pictures#


Interesting, so it recognizes an argument was passed and changes its output. At this point I decided to disassemble the executable using monodis.



WARNING: The runtime version supported by this application is unavailable.
Using default runtime: v1.1.4322
.assembly extern mscorlib
{
.ver 4:0:0:0
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
}
.assembly extern System.Core
{
.ver 4:0:0:0
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
}
.assembly 'fu'
{
.custom instance void [mscorlib]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = (
01 00 29 2E 4E 45 54 46 72 61 6D 65 77 6F 72 6B // ..).NETFramework
2C 56 65 72 73 69 6F 6E 3D 76 34 2E 30 2C 50 72 // ,Version=v4.0,Pr
6F 66 69 6C 65 3D 43 6C 69 65 6E 74 01 00 54 0E // ofile=Client..T.
14 46 72 61 6D 65 77 6F 72 6B 44 69 73 70 6C 61 // .FrameworkDispla
79 4E 61 6D 65 1F 2E 4E 45 54 20 46 72 61 6D 65 // yName..NET Frame
77 6F 72 6B 20 34 20 43 6C 69 65 6E 74 20 50 72 // work 4 Client Pr
6F 66 69 6C 65 ) // ofile

.custom instance void class [mscorlib]System.Reflection.AssemblyTitleAttribute::'.ctor'(string) = (01 00 07 64 72 6F 70 70 65 72 00 00 ) // ...dropper..

.custom instance void class [mscorlib]System.Reflection.AssemblyDescriptionAttribute::'.ctor'(string) = (01 00 00 00 00 ) // .....

.custom instance void class [mscorlib]System.Reflection.AssemblyConfigurationAttribute::'.ctor'(string) = (01 00 00 00 00 ) // .....

.custom instance void class [mscorlib]System.Reflection.AssemblyCompanyAttribute::'.ctor'(string) = (01 00 09 4D 69 63 72 6F 73 6F 66 74 00 00 ) // ...Microsoft..

.custom instance void class [mscorlib]System.Reflection.AssemblyProductAttribute::'.ctor'(string) = (01 00 07 64 72 6F 70 70 65 72 00 00 ) // ...dropper..

.custom instance void class [mscorlib]System.Reflection.AssemblyCopyrightAttribute::'.ctor'(string) = (
01 00 1B 43 6F 70 79 72 69 67 68 74 20 C2 A9 20 // ...Copyright ..
4D 69 63 72 6F 73 6F 66 74 20 32 30 31 31 00 00 ) // Microsoft 2011..

.custom instance void class [mscorlib]System.Reflection.AssemblyTrademarkAttribute::'.ctor'(string) = (01 00 00 00 00 ) // .....

.custom instance void class [mscorlib]System.Runtime.InteropServices.ComVisibleAttribute::'.ctor'(bool) = (01 00 00 00 00 ) // .....

.custom instance void class [mscorlib]System.Runtime.InteropServices.GuidAttribute::'.ctor'(string) = (
01 00 24 65 34 65 37 63 61 36 63 2D 63 32 61 62 // ..$e4e7ca6c-c2ab
2D 34 32 34 32 2D 61 33 65 35 2D 34 63 39 33 33 // -4242-a3e5-4c933
63 37 30 65 66 62 30 00 00 ) // c70efb0..

.custom instance void class [mscorlib]System.Reflection.AssemblyFileVersionAttribute::'.ctor'(string) = (01 00 07 31 2E 30 2E 30 2E 30 00 00 ) // ...1.0.0.0..

.custom instance void class [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::'.ctor'(int32) = (01 00 08 00 00 00 00 00 ) // ........

.custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = (
01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx
63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows.

.hash algorithm 0x00008004
.ver 1:0:0:0
}
.module fu.exe // GUID = {B3456451-E34C-4B2C-A452-4A83679B44EF}


.namespace fu
{
.class private auto ansi beforefieldinit Program
extends [mscorlib]System.Object
{

// method line 1
.method private static hidebysig
default void Main (string[] args) cil managed
{
// Method begins at RVA 0x2050
.entrypoint
// Code size 98 (0x62)
.maxstack 2
.locals init (
string V_0,
string V_1,
string V_2,
bool V_3)
IL_0000: ldc.i4.1
IL_0001: br.s IL_0006

IL_0003: ldc.i4.0
IL_0004: br.s IL_0006

IL_0006: brfalse.s IL_0008

IL_0008: nop
IL_0009: ldstr "290e1babf4daa83eb606f0b4e02c73be"
IL_000e: stloc.0
IL_000f: ldstr "/cqhcfUx1LO/mUsiT5fV2WijYMEDdvsi/gh214qRVPfauxChLplgBDMHScj8v/PDYt1F03x1r4FAdNe2uP9iHeAsPqcwEWzw3WTk7UN0jQ0="
IL_0014: stloc.1
IL_0015: ldarg.0
IL_0016: ldlen
IL_0017: conv.i4
IL_0018: ldc.i4.1
IL_0019: ceq
IL_001b: stloc.3
IL_001c: ldloc.3
IL_001d: brtrue.s IL_002d

IL_001f: nop
IL_0020: ldstr "No flag for you."
IL_0025: call void class [mscorlib]System.Console::WriteLine(string)
IL_002a: nop
IL_002b: br.s IL_0061

IL_002d: ldarg.0
IL_002e: ldc.i4.0
IL_002f: ldelem.ref
IL_0030: call string class fu.Program::GetMd5Hash(string)
IL_0035: stloc.2
IL_0036: ldloc.2
IL_0037: ldloc.0
IL_0038: call bool string::Equals(string, string)
IL_003d: stloc.3
IL_003e: ldloc.3
IL_003f: brtrue.s IL_004f

IL_0041: nop
IL_0042: ldstr "Try Harder N00b."
IL_0047: call void class [mscorlib]System.Console::WriteLine(string)
IL_004c: nop
IL_004d: br.s IL_0061

IL_004f: ldloc.1
IL_0050: call void class [mscorlib]System.Console::WriteLine(string)
IL_0055: nop
IL_0056: ldstr "Fix me :P"
IL_005b: call void class [mscorlib]System.Console::WriteLine(string)
IL_0060: nop
IL_0061: ret
} // end of method Program::Main

// method line 2
.method private static hidebysig
default string GetMd5Hash (string input) cil managed
{
// Method begins at RVA 0x20c0
// Code size 90 (0x5a)
.maxstack 3
.locals init (
class [mscorlib]System.Security.Cryptography.MD5 V_0,
unsigned int8[] V_1,
class [mscorlib]System.Text.StringBuilder V_2,
int32 V_3,
string V_4,
bool V_5)
IL_0000: nop
IL_0001: call class [mscorlib]System.Security.Cryptography.MD5 class [mscorlib]System.Security.Cryptography.MD5::Create()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: call class [mscorlib]System.Text.Encoding class [mscorlib]System.Text.Encoding::get_UTF8()
IL_000d: ldarg.0
IL_000e: callvirt instance unsigned int8[] class [mscorlib]System.Text.Encoding::GetBytes(string)
IL_0013: callvirt instance unsigned int8[] class [mscorlib]System.Security.Cryptography.HashAlgorithm::ComputeHash(unsigned int8[])
IL_0018: stloc.1
IL_0019: newobj instance void class [mscorlib]System.Text.StringBuilder::'.ctor'()
IL_001e: stloc.2
IL_001f: ldc.i4.0
IL_0020: stloc.3
IL_0021: br.s IL_0041

IL_0023: nop
IL_0024: ldloc.2
IL_0025: ldloc.1
IL_0026: ldloc.3
IL_0027: ldelema [mscorlib]System.Byte
IL_002c: ldstr "x2"
IL_0031: call instance string unsigned int8::ToString(string)
IL_0036: callvirt instance class [mscorlib]System.Text.StringBuilder class [mscorlib]System.Text.StringBuilder::Append(string)
IL_003b: pop
IL_003c: nop
IL_003d: ldloc.3
IL_003e: ldc.i4.1
IL_003f: add
IL_0040: stloc.3
IL_0041: ldloc.3
IL_0042: ldloc.1
IL_0043: ldlen
IL_0044: conv.i4
IL_0045: clt
IL_0047: stloc.s 5
IL_0049: ldloc.s 5
IL_004b: brtrue.s IL_0023

IL_004d: ldloc.2
IL_004e: callvirt instance string object::ToString()
IL_0053: stloc.s 4
IL_0055: br.s IL_0057

IL_0057: ldloc.s 4
IL_0059: ret
} // end of method Program::GetMd5Hash

// method line 3
.method private static hidebysig
default string EncryptString (string plainText, string Key) cil managed
{
// Method begins at RVA 0x2128
} // end of method Program::EncryptString

// method line 4
.method private static hidebysig
default string DecryptString (string cipherText, string Key) cil managed
{
// Method begins at RVA 0x228c
} // end of method Program::DecryptString

// method line 5
.method public hidebysig specialname rtspecialname
instance default void '.ctor' () cil managed
{
// Method begins at RVA 0x24c4
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void object::'.ctor'()
IL_0006: ret
} // end of method Program::.ctor

} // end of class fu.Program
}


I immediately notice that it stores an MD5 hash in a string, and another string that appears to in base64. Following the logic, I realised that it took the argument, md5'd it and compared the result to the stored md5 sum. If it matched, it would print the base64 string and tell you to fix it. Technically, you don't even need to know what the argument it is expecting is, but I wanted to be thorough. The md5sum is an md5sum of the string 'kc57' (one of the CTF admins @_kc57).



root@gits-and-shiggles:/home/upgraydd/Pictures# mono fu.exe kc57
WARNING: The runtime version supported by this application is unavailable.
Using default runtime: v1.1.4322
/cqhcfUx1LO/mUsiT5fV2WijYMEDdvsi/gh214qRVPfauxChLplgBDMHScj8v/PDYt1F03x1r4FAdNe2uP9iHeAsPqcwEWzw3WTk7UN0jQ0=
Fix me :P
root@gits-and-shiggles:/home/upgraydd/Pictures#


This string stumped me. I spent too much time on it and probably went every way I shouldn't have in order to figure it out. I never did.


I look forward to competing next year. TheLightCosine and I will probably team up for real and pwn some pants of.

5 comments:

  1. Nice writeup Brandon! One quick thing to fix, "sup3rs3cr3tk3y" wasn't a flag, it was the password used to extract the flag from the picture (steganography) :) I look forward to competing against you next year also!

    - Justin from TeamSparkleMotion

    ReplyDelete
  2. Great write up. FWIW, jukens comment was that 'sup3rs3cr3tk3y was the password to unsteg the qr.jpg'

    ReplyDelete
  3. Gah! I knew steganography would be involved, wish I had paid closer attention now.

    ReplyDelete
  4. Excellent writeup and nice job on the CTF! To expand on the qr.jpg, the flag when you figured out the QR code was double base64 encoded, and would have given you a clue to use steghide.

    ReplyDelete