Case Study: LEGIT_00004

LEGIT_00004 was a challenge from Defcon CTF that implemented a file system in memory. The intended bug was a tricky memory leak that the challenge author didn't expect Mayhem to get. However, Mayhem found an unintended null-byte overwrite bug that it leveraged to gain arbitrary code execution. We heard that other teams noticed this bug, but thought it would too hard to deal with. Mayhem 1 - Humans 0. In the rest of this article,  we will explain what the bug was, and how Mayhem used it to create a full-fledged exploit.

To better understand the bug in this post, we will look at the challenge source code that the author sent us after the CTF (Thanks Steve!). During the competition however, Mayhem did not have access to source and exploited the challenge solely by analyzing the binary.

The null-byte overwrite was in the "copy" functionality of the challenge. Below is a snippet of that function containing the relevant code. Note that the cmdline argument is user-controlled (and is up to ~1024-byte long).

int copy( fileSystemType *fs, char *cmdline, unsigned int owner ) {
char sourcefile[FILENAME_SIZE];
char destfile[FILENAME_SIZE];
int x;

// skip over leading whitespace characters
while ( *cmdline != 0 && isspace(*cmdline) )
++cmdline;

// if we hit the end of the line there were no filenames specified
if ( *cmdline == 0 ) {
return ERROR_BAD_PARMS;
}

x = 0;
while ( *cmdline != 0 && !isspace(*cmdline) ) {
if ( x < FILENAME_SIZE ) {
sourcefile[x] = *cmdline;
}

++cmdline;
++x;
}

sourcefile[x] = 0;
...
}

The null-byte overwrite bug is on line 25. The loop right before iterates through all the bytes in cmdline, even though it copies only the first FILENAME_SIZE (or 20) bytes. Note that x keeps being incremented at each iteration. Therefore on line 25, we can make x take any value from 1 to 1024 even though sourcefile is only 20 bytes.

The bug enables us to write a single null byte on the stack. To see how this can be exploited, we need to look at the calling context and the assembly code. Let's start with the calling context:

int main(void) {
char command[1024];
while (1) {
bzero(command, 1024);
getline(command, 1024);

i = 0;
while (command[i] != ' ' && i < strlen(command)) {
++i;
}
command[i] = 0;

if ( strcmp(command, "list") == 0 ) {
...
}
else if ( strcmp( command, "copy" ) == 0 ) {
retcode = copy( currentFS, command+i+1, currentUser );
}
...
}
}

You can see the call to copy at line 17. You can also see that a local buffer (command) is passed as the cmdline argument to copy. In fact, the address of that buffer is loaded relative to ebp:

804cd85: 8d 8d b0 f4 ff ff lea -0xb50(%ebp),%ecx

It turns out that the copy function saves ebp on the stack, and Mayhem decided to overwrite the lowest byte of the saved ebp. Instead of restoring the original value 0xbaaaaff4, Mayhem made the program restore 0xbaaaaf00. Given that the command buffer address is computed relative to ebp, its address changed as well. It went from 0xbaaaa4a4 to 0xbaaaa3b0, which happens to be below esp.

On the next getline call, the program will start writing our data at 0xbaaaa3b0. The saved return instruction pointer is right above it at 0xbaaaa408. By giving enough data without a new line, Mayhem overwrites the return pointer, and gains arbitrary code execution when getline returns. Neat.

How long did this all take? LEGIT_00004 was released on round 65, at 14:10:25 UTC according to our database. Mayhem found the first crash at 14:32:28 and found a crash on a return instruction at 14:35:00. We had to wait until 15:55:26 to get a crash that mayhem could convert to a full fledged exploit. Overall, it took Mayhem 1h45m from looking at the program for the first time to developing the exploit described above.


Comments are closed.

Categories

Stay Connected

Subscribe to updates