Skip to content

Instantly share code, notes, and snippets.

@dwendt
Last active August 29, 2015 14:08
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dwendt/c2fa167101d58e7d4b2e to your computer and use it in GitHub Desktop.
Save dwendt/c2fa167101d58e7d4b2e to your computer and use it in GitHub Desktop.
CyberSEED "ashare11"

#CyberSEED Software Challenge :: Phase1 :: ashare11

This past weekend was a "buffer overflow" competition put together by UConn. Our team of four went up and lost to UIUC who had a team of one! Great job Sam! We also got beaten by BUILDS, who did not use radare2, Jeff. 🙅

The first phase was three 32-bit challenges on a VM given to us, they were setuid binaries and we were told to automate exploiting them, spawning a shell(with tabcomplete/arrow support!) and include the key along with our script in our submission. Judging was performed manually and the scoreboard was not real-time at all. ASLR and NX were disabled, but we did have to take in mind that the stack would be located at a different spot for every restart/separate VM.

##Reversing The actual output of the bin:

Usage: ./ashare11 [command and options]
The valid commands are:
	ls	[dir or file in the vault]
	get	[file in the vault] [local dir or file]
	put	[local file] [file or dir in the valut]
	mkdir	[path in the valut]
	rmdir	[path in the valut]
	rm	[file in the valut]

Location of the vault: /swc/vault1

We see /swc/vault1 is only rw by the user this bin is setuid to.

So we try some(a lot) directory traversal -- no luck. But something interesting happens, it'll produce no output for a second and then exit when we try spamming some input at it. strace shows it is running nanosleep. I didn't consider this to be a crash/overflow at the time, because in IDA I noticed...

!()[image of the path checking stuff]

This is a screencap of the function that returns a value used to decide if the input is a valid path. That left branch is clearly comparing some variable values. If we follow those up, we see a function's return value being assigned to both of them before another function is called. This is a canary they're using to check for overflows.

##Canary

So, looking at how that value is generated. It takes the first 32 bytes of user input xored with cyberseed repeated to a length of 64 -- and runs md5 on this(easily identifiable by googling the magic numbers). An md5 returns 16 bytes of data or four integers worth of data. Treating the result as an array of integers, the function checks for: (short)(md5[2] ^ md5[3]) == 0x1234. If it's not equal, it'll insert a null byte into the return value... which is something we can't really replicate with our string-based inputs!

So, we've got to brute that md5. I was unsure if it was a normal md5, so I opted to rip the md5 function out and call it from my own C program when I brute it. Ripping it out was just: dd of=md5lol if=ashare11 count=3405 bs=1 skip=0xiforget

The C used to brute it is below. Kevin Colley, another team member, wrote up the combination logic superquick and fixed my gcc compilation issues related to calling a char buffer... the programming team apparently pays off!

lots of code maybe a link instead

Exploit

Since the canary is only determined by the first 32 bytes of our input, we put the result of the brute force at the front of our buffer. Now that the canary check passes -- we get our first real crash! It's some mov instruction in libc involving SSE registers.. not too good. Reducing our input length gets us crashes in different places, and eventually we see that we've got a crash where we control the first value on the stack as well as the value of EIP.

The system doesn't have ASLR, so we set EIP to the address of a jmp esp instruction. This is useful because it'll make the program try to execute the first value on the stack as an instruction. Since we have control over that, we can make that value represent an instruction that will jump further up the stack, around where our input buffer should be. To make it so we don't need to be too exact, we'll make as much of our input as possible be 0x90 aka nop.

So, after the first 32 bytes used for the md5, and the canary value, we've got a bunch of nops. We can put in some /bin/sh shellcode after those and we get a shell when it's run!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment