PicoCTF 2014 Write-ups

Format - 70 (Binary Exploitation)

Writeup by patil215

Created: 2014-11-08 12:25:57

Last modified: 2014-11-09 23:56:57

Problem

This program is vulnerable to a format string attack! See if you can modify a variable by supplying a format string! The binary can be found at /home/format/ on the shell server. The source can be found here.

Hint

There is already a pointer to the variable on the stack! Also, %n may be useful!

Answer

Overview

Use a standard format string exploit to set the value of the secret variable to 1337.

Details

The title of this problem and the description lead us to believe that our exploit needs to take advantage of the format string used in the problem - there's probably a vulnerability when it is used.

Let's take a look at the source. On line 15, we can find the mistake this programmer made:

printf(argv[1]);

printf should be taking in an additional argument. Normally, printf works by taking in a format string followed by variables to print (see here for more info). The problem is, this person only has printf printing the argument with no format string specified. This is something we can exploit - since printf can take any number of arguments, we can pass in our own format string like "%p %p %p" and the program will blindly print the next three addresses on the stack.

This post is a great resource for introduction to this kind of exploit. Once you've read that, we'll apply it to this program.

Let's now write our exploit. The hint tells us that there's a pointer to a variable on the stack that we can take advantage of.

Looking through the code, we see that secret is that variable. It is first initialized to zero:

int secret = 0;

and then if the value of secret is 1337, we get a shell with full permissions that we can use to get our flag:

if (secret == 1337){
    give_shell();
}

What we need to do is use the format string exploit to set the variable of secret to 1337. The first thing we need to do to accomplish this is figure out the memory address of secret so we know where to write to. The easiest way to do this is with gdb (during the competition, this was available by default on the PicoCTF shell).

Let's run the program through gdb and find the value of secret. In the shell we type:

[email protected]:~$ gdb format
Reading symbols from format...(no debugging symbols found)...done.
(gdb) p &secret
$1 = (<data variable, no debug info> *) 0x804a030 <secret>

Using p &secret gdb prints the memory address of secret, 0x804a030. The next thing we need to do is see where on the stack the memory address is when the program runs. So, we do:

./format "%p %p %p %p %p %p %p %p %p"

%p causes the program to print out the memory address next on the stack, so doing this 9 times as above will print the first 9 memory addresses on the stack, giving us:

0xffffd7e4 0xffffd7f0 0xf7e4f39d 0xf7fc83c4 0xf7ffd000 0x804852b 0x804a030 0x8048520 (nil)pico8

We see 0x804a030 in the 7th spot - so we know that the address of secret is the 7th variable on the stack!

The introduction to format string exploits linked above told us most of what we needed to know to format our string. The %n format string lets us write "the number of characters written so far into the integer indicated by the pointer argument" - so if we create a string 1337 characters long (using %1337) and then provide memory address pointing to secret, we can set the value of secret to 1337. We provide this memory address with x%7 - this returns the 7th variable on the stack, the location of secret. $(python -c allows us to pipe python output into the program, great since we have a long string of characters. $hn writes this input to the stack a few bytes at a time. Putting it all together we get:

./format "$(python -c 'import sys; sys.stdout.write("%1337x%7$hn")')"

Running this sets the value of secret to 1337, executing give_shell() in the source, so now we have access to the shell with full permissions! Typing cat flag.txt into this shell gives us our flag. Interestingly enough, this exploit works without having to use %n as the hint says.

Flag

who\_thought\_%n_was_a_good_idea?