Project 2: Memory Corruption
Due: Sunday 18 October, 11:59 PM
Goal
Welcome to the special 24th anniversary remix edition of the memory corruption targets/sploits project!
The goal of this assignment is to gain hands-on experience with the effect of buffer overflow bugs and format string bugs.
You are given the source code for five exploitable programs,
target1, target4, target5, and target6. Your goal is to write
four exploit payloads (egg1.raw, egg4.b64, egg5.b64, and
egg6.b64), that, when supplied to the corresponding target program
should result in execution of shellcode. You are also given a warmup
towards exploiting target5, called target5easy; here the goal is
just to get the target to print out a success message when given
egg5easy.b64; shellcode is not involved.
Because we live in the future, there is no need to construct these
payloads using a C sploit program. Instead, you will write short
Python scripts (make-egg1.py, make-egg4.py, make-egg5easy.py,
make-egg5.py, and make-egg6.py) that write the corresponding
payload file.
Background
You have already read Aleph One’s “Smashing the Stack for Fun and Profit.”
For background on exploiting malloc metadata corruption, see the two classic articles by Maxx and anonymous in Phrack 57. For the theory behind malloc metadata, see the survey by Paul Wilson and friends.
For background on exploiting format string bugs, see the classic paper by scut and Team TESO. Read this paper closely, especially the sections on short writes (4.1) and direct parameter access (4.3).
You should read these papers for inspiration, not as recipes to follow literally. The 32-bit x86 environment they describe is different from our 64-bit RISC-V VM, and of course umalloc is not the same as 2001-era dlmalloc.
The environment
We assume that you have set up the course virtual machine environment for CSE 291Y as described in the project 1 writeup.
Running as the user (not as root), download and unpack the project tarball in the VM using the command
curl -O https://cseweb.ucsd.edu/classes/fa26/cse291Y-002/dist/proj2.tar.gz
and unpack the tarball using the command tar xavf proj2.tar.gz.
Run make to build the targets as well as the driver program.
If you run a target program directly from the shell, the base
addresses of its stack and heap will be randomized by ASLR. If you
run a target program through GDB, ASLR will be disabled but stack
addresses will still vary depending on the execution environment. To
make exploits consistent, we will launch the targets through the
driver program. The driver program takes the following command-line
arguments: the target binary to run; a filename to pass to the target
as an argument; and your TSN. For example, I might run
target1 on egg1.raw using the command line:
$ ./driver target1 egg1.raw 200001337
Using GDB
You can arrange to debug the target when launched in exactly this way, so that you can use GDB to read addresses, as follows.
First, run GDB on the driver program: gdb driver. Next, before
telling GDB to run the program, ask to get control back when on
execve: catch exec. Now have GDB launch the driver, specifying
command-line arguments as above: run target1 egg1.raw 200001337.
If all goes well you should get control on exec. Now is the time to
set breakpoints in the target program (if you tried to do it earlier,
you’d have gotten breakpoints in the driver program instead): break
foo. Then continue.
Note that the length of the argument to the target program will affect the placement of stack frames (because, as you know from your operating systems course, the kernel places arguments at the very top of the stack). Try to figure out how long your egg should be early on, then fine-tune its contents once stack layout is stable.
As we mentioned in class, GDB is a powerful tool that will help you in this class and beyond. Time spent reading the GDB manual is time well spent.
The assignment
Your goal is to craft a binary payload, in egg1.raw, that will cause
target1 to execute shellcode when run as
./driver target1 egg1.raw <your-tsn>
Replace <your-tsn> with your actual TSN (this is your new Triton
Student Number, starting with a 2, that replaced the SIS ID that
started with an A.)
Likewise, your goal is to craft a base64-encoded payload in egg4.b64
that will cause target4 to execute shellcode when run as
./driver target4 egg4.b64 <your-tsn>
to craft a base64-encoded payload in egg5easy.b64 that will cause
target5easy to print a success message when run as
./driver target5easy egg5easy.b64 <your-tsn>
to craft a base64-encoded payload in egg5.b64 that will cause
target5 to execute shellcode when run as
./driver target5 egg5.b64 <your-tsn>
and to craft a base64-encoded payload in egg6.b64 that will cause
target6 to execute shellcode when run as
./driver target6 egg6.b64 <your-tsn>
The reason that egg4, egg5easy, egg5, and egg6 are
base64-encoded is so that you can include 0 bytes in the decoded
version; by contrast, you should have no 0 bytes in egg1.
Note that, in target5easy and target5, snprintf will still stop
processing its format string argument at the first 0 byte in
encounters. The base64 encoding will let you get 0 bytes onto the
stack (in the rawegg buffer) but not into the format string
itself.
We’ve also included a target5hard that resembles target5 but does
accepts a raw rather than base64-encoded input. Exploiting this
target is possible but much harder. You may try it for an extra
challenge, but you do not need to exploit it for full credit on this
project.
The make-egg scripts
In principle, you could create the exploit payloads egg1.raw, etc.,
by hand using a hex editor, or write programs in any programming
language to create them. For concreteness, we ask you to use Python
to create the exploit payloads. The script make-egg1.py should
write the egg1.raw file; the script make-egg4.py should write the
egg4.b64 file; the script make-egg5easy.py should write the file
egg5easy.py; the script make-egg5.py should write the egg5.b64
file; and the script make-egg6.py should write the egg6.b64 file.
You will submit the make-egg scripts along with the actual payloads; we will check that running the scripts regenerates the payloads files.
Rather than hardcoding a specific shellcode, your scripts should
incorporate the binary string shellcode.SHELLCODE (which is defined
for you in shellcode.py). The particular shellcode we’ve provided
you is 170 bytes long, but your scripts should work with other
reasonably sized shellcode, up to 200 bytes, which means you should
adapt your egg according to len(shellcode.SHELLCODE). Our
autograder will test your exploits using different shellcode than what
is included in the project tarball, so make sure that you avoid
hardcoding either the shellcode itself or its length in your solution.
(You can easily generate additional shellcode using the tooling from
project 1.)
As shown in the starter files, Python has convenient facilities for
base64 encoding. Feel free to use any other Python features you find
handy, like bytearrays and struct.pack (for the latter you’ll need
to add an import struct), but don’t rely on any Python library that
isn’t part of the base system.
Logistics
You will submit using Gradescope. You should submit exactly the following files:
-
egg1.raw,egg4.b64,egg5easy.b64,egg5.b64, andegg6.b64: These are your exploit payloads. -
make-egg1.py,make-egg4.py,make-egg5easy.py,make-egg5.py, andmake-egg6.py: These scripts should regenerate the submitted egg files exactly. -
TSN: a text file with exactly one line, containing only your TSN, exactly as you supplied it to the driver program.
Grading
TBD.