Format String Technique Roundup

We're now at the point where we can start exploiting Linux format string bugs . Let's quickly review the fundamental techniques that we've used:

  1. If the format string is on the stack, we can supply the parameters that are used when we add format specifiers to the string. If we're brute forcing offsets for a format string exploit, one of the offsets we have to guess is the number of parameters we have to use before we get to the start of our format string.

    Once we can specify parameters:

    1. We can read memory from the target process using the %s specifier .

    2. We can write the number of characters output so far to an arbitrary address using the %n specifier.

    3. We can modify the number of characters output so far using width modifiers, and

    4. We can use the %hn modifier to write numbers 16 bits at a time, which allows us to write values of our choice to locations of our choice.

  2. If the address that we want to write to contains one or more null bytes, you can still use %n to write to it, but you must do this in two stages. First, write the address that you want to write to into one of the parameters on the stack (you must know where the stack is in order to do this). Then, use %n to write to the address using the parameter you wrote to the stack.

    Alternatively, if the zero byte in the address happens to be the leading byte (as is often the case in Windows format string exploits) you can use the trailing null byte of the format string itself.

  3. Direct parameter access (in the Linux implementations of the printf family) allows us to reuse stack parameters multiple times in the same format string as well as allowing us to directly use only those parameters that we are interested in. Direct parameter access involves using the $ modifier; for example:

     %272$x 

    will print the 272nd parameter from the stack. This is an immensely valuable technique.

  4. If for some reason we can't use %hn to write our values 16 bits at a time, we can still use byte-aligned writes and %n : we just do four writes rather than one and pad our number of characters output so that we're writing the low order byte each time. Table 4.1 shows an example of what we should do if we want to write the value 0x04030201 to the address X .

    Table 4.1: Writing to Addresses

    Address

    X

    X+1

    X+2

    X+3

    X+4

    X+5

    X+6

    Write to X

    0x01

    0x01

    0x01

    0x01

         

    Write to X+1

     

    0x02

    0x02

    0x02

    0x02

       

    Write to X+2

       

    0x03

    0x03

    0x03

    0x03

     

    Write to X+3

         

    0x04

    0x04

    0x04

    0x04

    Memory after four writes

    0x01

    0x02

    0x03

    0x04

    0x04

    0x04

    0x04

The disadvantage of this technique is that we overwrite the 3 bytes after the 4 bytes we're writing. Depending on memory layout, this may not be important. This problem is one of the reasons why exploiting format string bugs on Windows is fiddly.

Now that we've reviewed the basic reading and writing techniques, let's look at what we can do with them:

  • Overwrite the saved return address. To do this, we must work out the address of the saved return address, which means either guesswork, brute force, or information disclosure.

  • Overwrite another application-specific function pointer. This technique is unlikely to be easy since most programs don't leave function pointers available to you. However, you might find something useful if your target is a C++ application.

  • Overwrite a pointer to an exception handler, then cause an exception. This is extremely likely to work, and involves eminently guessable addresses.

  • Overwrite a GOT entry. We did this in wu- ftpd . This is a pretty good option.

  • Overwrite the atexit handler. You may or may not be able to use this technique, depending on the target.

  • Overwrite entries in the DTORS section. For this technique, see the paper by Juan M. Bello Rivas in the bibliography.

  • Turn a format string bug into a stack or heap overflow by overwriting a null terminator with non-null data. This is tricky, but the results can be quite funny .

  • Write application-specific data such as stored UID or GID values with values of your choice.

  • Modify strings containing commands to reflect commands of your choice.

If we can't run code on the stack, we can easily bypass the problem by the following:

  • Writing shellcode to the location of your choice in memory, using %n -type specifiers. We did this in our wu-ftpd example.

  • Using a register-relative jump if we're brute forcing, which gives us a much better chance of hitting our shellcode (if it's in our format string),

For example, if our shellcode is at esp+0x200 , we can overwrite some of the GOT with something like this:

 add 
 add $0x200, %esp jmp esp 
x200, %esp jmp esp

This gives us the location of the code that will jump to our shellcode, so when we overwrite our function pointer (GOT entry, or whatever) we know that we'll land in our shellcode. The same technique works for any other register that happens to be pointing at or close to our shellcode after the format string has been evaluated.

In fact, we can fairly easily write a small shellcode snippet that will find the location of a larger shellcode buffer, and then jump to it. See Gera and Riq's excellent Phrack paper at www.phrack.org/show.php?p=59&a=7 for more information.



The Shellcoder's Handbook. Discovering and Exploiting Security
Hacking Ubuntu: Serious Hacks Mods and Customizations (ExtremeTech)
ISBN: N/A
EAN: 2147483647
Year: 2003
Pages: 198
Authors: Neal Krawetz

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