________________________________________
Learning Goals
Students will learn introductory level concepts about binary exploitation. This project is designed to develop your understanding of control flow hijacking through different tasks/challenges showcasing select vulnerabilities or weaknesses in compiled binaries. By the project’s end, you should…
• …be familiar with simple C syntax and assembly operations.
• …able to identify common memory-based vulnerabilities.
• …capable of crafting basic exploits from scratch
Tools you will use
• Python – While you are welcome to craft your exploits using other tools/frameworks, in this project we teach to using Python and the pwntools library for automating/scripting our exploits.
• GDB – The GNU Debugger is a useful tool for understanding the underlying behavior of a program during runtime. We have extended its native capabilities with the pwndbg plugin for additional output and readability.
• objdump – The objdump tool will allow us to disassemble the compiled binaries, affording us an opportunity to perform static analysis of the binary’s instructions at a more granular level than what the C source code alone can.
• ropper – A useful tool for identifying so-called “gadgets” to facilitate return-oriented programming (ROP). Additional information concerning what ROP and gadgets are will be covered in the appropriate sections of the project.
Recommended Reading
To deepen your understanding of binary exploitation, consider reviewing:
• The ~/project/tutorial/README.md and the associated follow-along code.
• The project tutorial videos:
o Buffer Overflows
o ROP
• pwntools Documentation
• GDB command cheat sheet
• This Writeup explaining some rudimentary basics of Computer Architecture
• This Lecture on how the Stack and Function Calls work in C
The final deliverables:
A single JSON-formatted file will be submitted to Gradescope. This file should be named project_binexp.json. A template can be found on the Submission Details page.
Project Environment
This project runs in an OVA-formatted Virtual Machine (VM) with all the binaries/tools required, and must be used to generate the correct flags. We recommend running the VM through Oracle’s Virtualbox software. Login credentials for the VM to access the project’s materials can be found through Canvas.
________________________________________
TABLE OF CONTENTS
• FAQ
• Stage 00 – Setup/Validation — (1 flags)
• Stage 01 – Intro to Assembly — (2 flags)
• Stage 02 – Stack Smashing —– (3 flags)
• Stage 03 – ROP ———————- (2 flags)
• Stage 04 – Final Flags ————- (3 flags)
• Submission Details
Frequently Asked Question(s) (FAQ)
• Frequently Asked Question(s) (FAQ)
• Submission and GradeScope
• Logistics & Setup
o Q1) Can I SSH into the VM and perform the project?
o Q2) Can I recompile the binaries?
o Q3) May I move/relocate the files from their default location?
• General
o Q1) How can I go about submitting a question about the project?
o Q2) How do I know if I got the flag for a given binary?
o Q3) I got to the flag in GDB but I don’t see a message output with the flag; where is it?
o Q4) How does cyclic() and cyclic_find() work?
o Q5) Do I have to use e.py?
o Q6) What is “/proc/flag”?
o Q7) I’m given a different flag everytime I run my exploit; is something wrong?
o Q8) I’m making the call to read from /proc/flag but my exploit keeps seg-faulting; what’s wrong?
o Q9) My e.py file is throwing a “can only concatenate str (not bytes) to str” error; what gives?
o Q10) When I’m stepping through instructions in GDB, I enter “n” and it skips over my desired target function call; help!
o Q11) Why are the tasks prefixed with numbers like 01, 02, 03_, etc.?
o Q12) I want to run VSCode in the VM, can I?
________________________________________
Make sure you have read:
• This Writeup explaining some rudimentary basics of Computer Architecture
• Please refer to: Required Reading > VM Troubleshooting, for VM related troubleshooting questions.
Make sure you have watched:
• This Lecture I’m Developing on how the Stack and Function Calls work in C
• This CS6035 Project 1 pwntools/pndbg Tutorial video
Submission and GradeScope
Please navigate to the Submission Details menu page, left.
Logistics & Setup
Q1) Can I SSH into the VM and perform the project?
When you run your e.py exploits with GDB, a separate terminal is opened with the process hooked to GDB. Some students have reported handling this by using tmux. Once you SSH into the VM, kill existing tmux sessions:
tmux kill-session
And then start a new tmux session with:
tmux -CC
This will open up a new window with 2 separate scrollable sections for pwndbg and e.py.
Q2) Can I recompile the binaries?
If it helps your comprehension, you’re welcome to recompile the source code with whatever alterations you want; in fact, we suggest doing this in some of the binaries in section 04, for example.
However, we have fingerprinted the binaries that are shipped out to you all with the project; your final exploit(s) will need to utilize them as is. Gradescope will recognize if you attempt to submit a flag from a binary that wasn’t among the original set.
Q3) May I move/relocate the files from their default location?
We strongly discourage this. The binaries rely on files residing in particular locations, such as /home/binexp/user.txt, among others. However, you ARE welcome to make copies/backups as needed if it helps facilitate your testing.
General
Q1) How can I go about submitting a question about the project?
We respectfully request that you direct all questions to the corresponding Ed Discussion megathreads. Please do not seek to open a private post or email the TA team directly unless directed to do so. If after triaging your question we believe it necessary for you to open a private post, we’ll request you to do so.
Posting your question(s) on the public Megathreads provides the added benefit of a reviewable archive for your peers who may have similar questions/problems that they are likewise working through. We encourage you all to feel free to respond to one-another with your own insights to the project. We just ask that you err on the side of discretion when it comes to sharing overly prescriptive solutions, exploit code, assembly instruction addresses, etc.
Q2) How do I know if I got the flag for a given binary?
All of the flags in the project are read-in by the binaries through /proc/flag. For an example of what that might look like, see:
Note how our flag generator denotes both your GTID (which is derived from /home/binexp/user.txt) and the exact binary that was used to produce the flag. You can cross-reference this information in case you encounter issues with Gradescope.
Q3) I got to the flag in GDB but I don’t see a message output with the flag; where is it?
We have configured our flag generation-mechanism (/proc/flag) to deny providing any flags to users who attempt to read from it via GDB. This is deliberate, because as a debugger GDB has a multitude of functionality that would undercut the learning objectives of the tasks. This includes – but isn’t limited to – using the set and call commands, for example. You will need to naturally arrive at reading from /proc/flag either directly from directly interacting with the binary along the command line or through a non-GDB optioned invocation of your e.py file (e.g. python3 e.py).
Q4) How does cyclic() and cyclic_find() work?
We encourage you to read up on the tools you’re utilizing: https://docs.pwntools.com/en/stable/util/cyclic.html
In brief:
cyclic() produces a bytestring of length “n”, where n is some integer value between 0 and the largest possible positive integer supported by the platform (e.g. sys.maxsize). Example usages might look like:
• cyclic(10)
• cyclic(25)
• cyclic(3000)
The output of cyclic() is a bytestring with a unique character sequence in chunks of 4 bytes at a time:
In the above screenshot, you can see the pattern as:
• aaaa
• baaa
• caaa
• etc.
This pattern is the same with every invocation of cyclic() and terminates at the nth character.
cyclic_find() reverse-engineers the above. It provides an integer corresponding to a particular segment in that aforementioned bytestring; you can supply it with a variety of inputs (e.g. a bytestring, a hex representation, etc.):
Q5) Do I have to use e.py?
No. We provide you the e.py template code as an optional means of interacting with the binaries. You are welcome to exploit them through whatever other means you’d like (in fact, some of the challenges may be easier without using e.py).
Q6) What is “/proc/flag”?
The /proc/flag file is the target for all of the binary exploits in this project. Every time a user reads from it, it outputs a uniquely encoded result (which we erroneously refer to as a “hash”) that contains reversible information fingerprinted to it that the autograder uses to evaluate for correctness.
You can test if /proc/flag is running trivially by reading from it like so:
cat /proc/flag
Note: while doing the above should yield a flag hash, it will not be accepted/validated by the autograder. Valid hashes will only be returned from exploiting the binaries.
Q7) I’m given a different flag everytime I run my exploit; is something wrong?
No. This is expected behavior and nothing to worry about.
Q8) I’m making the call to read from /proc/flag but my exploit keeps seg-faulting; what’s wrong?
In most cases of this happening, your exploit is catching on a movaps instruction somewhere within system.c like in the screenshot below:
If this is the case, the issue is related to a stack-misalignment problem. Because our exploits forcibly redirect the control flow in ways the process wasn’t expecting, there can be downstream consequences as the process executes along and refers back to our maliciously overwritten stack. By-and-large, you generally want to consider jumping to a different instruction within your targeted function.
Q9) My e.py file is throwing a “can only concatenate str (not bytes) to str” error; what gives?
The error is telling you that you’re mixing your data types in your payload within e.py:
## WRONG
payload = “A” * 100 #type str
payload += p64(0xwhatever) #type bytes
## RIGHT
payload = b’A’ * 100 #type bytes
payload += p64(0xwhatever)
Q10) When I’m stepping through instructions in GDB, I enter “n” and it skips over my desired target function call; help!
We speak to this in the 01 section, step 1.2: 01_bb_steps. Both the (n)ext and next instruction (ni) GDB commands step over function calls. To enter the function you need to (s)tep inside (or step inside, immediate instruction (si)).
Q11) Why are the tasks prefixed with numbers like 01, 02, 03_, etc.?
We’ve labeled the tasks with numbers to reflect their relative perceived difficulty. You do not need to complete the tasks in any particular order. If you get stuck working on a problem, try working through some of the other challenges and come back.
As a clarifying note: Stages 00 through 04 are different from the numbered prefixes in front of the exercise names. The Stage numbers are meant to help organize the various exercises by their corresponding subject matter. Each stage may have any number of 01_ thru 03_ exercises.
Q12) I want to run VSCode in the VM, can I?
Yes! We have an installer script located at /home/InstallVSCode.sh. The binexp user has sudo permissions to run this script.
Unfortunately in order to preserve the integrity of this and other projects, we will not be providing root access to the VM. Any other software you want to install on the VM is allowed, provided you do not need those privileges.
Stage 00
• Learning Objectives
• Exercises
• Step 0.1
• Step 0.2
o 01_basic_overflow_1
Instructions
OPTIONAL: Try using e.py!
• Step 0.3
Welcome to the Binary Exploitation (BinExp) project for CS6035! We’re excited to have you with us for this effort.
Binary exploitation is a really interesting and challenging domain within cybersecurity. It rests at the intersection of many sub-disciplines, including reverse engineering, low-level programming, operating systems, code review, etc. You’ll be expected to draw upon a variety of subjects matter in approaching and working through the challenges of this project. Understandably, many students find the project challenging at some point (or many points), due to the need to perform additional research in those areas on top of working the problems themselves; as such, we encourage you to not delay in getting started with the project!
Each stage within this project presents a set of learning objectives and associated challenges. We’ve endeavored to present these challenges in a logical order in the form of “Stages”, starting here in Stage 00 as a guided introduction to the project through to Stage 04 where you’ll be crafting your own novel exploits to some unique challenges. Within each stage, there are a number of exercises affiliated with the related material ranging from “easier” content (prefixed as 01, such as 01_basic_overflow_1) to more challenging tasks (difficulty 03). While we encourage students to proceed through the challenges in-order if you’ve never done anything like this before, you are welcome to approach this projects’ challenges in any order you’d like; in fact, if you’re stuck on an exercise it may be best to move along and return back later.
Learning Objectives
The learning objectives for this section are:
• Project setup and understanding the project architecture
• High-level introduction/exposure to project materials
• Validation of project infrastructure, including:
o Environment setup
o /proc/flag
o Gradescope
Exercises
This section features 1 exercise:
• 01_basic_overflow_1
Step 0.1
Before diving in, let’s ensure that our project environment is appropriately configured.
1. This project utilizes the same virtual machine (VM) that is used for other projects within CS6035. If you’ve already got it configured, great! If not, see the respective “Course VM Download Thread” post in Ed Discussion for instructions.
2. Please follow the instructions in Canvas (navigate to “Assignments” > “Binary Exploitation”) for login credentials to the VM as well as the requisite commands for fetching the project files.
3. Navigate to /home/binexp/user.txt and set it to your nine-digit GTID. If you do not know your GTID, you should be able to discover this through https://gtid.gatech.edu. Note: if you fail to set this, no responses you submit to Gradescope will be accepted as correct.
Below is a brief summary of the project’s contents:
• project_binexp.json: This is the one (and only) file you will submit to Gradescope to have your work be evaluated. See the Submission tab along the left-hand side of this page for additional guidance concerning project submission guidelines.
• project*: This is the directory and subdirectories that make up the project. Each subdirectory reflects an individual challenge within the project and contains all of the files necessary for solving that particular challenge. We have included a projecttutorial subdirectory that has amplifying guidance material to help get you oriented to the projects’ techniques. The contents of projecttutorial are not mandatory or graded – you should not include any practice work results you perform there in your project_binexp.json file.
• project*flag: For most of the challenges, this is the compiled binary you’re attempting to exploit. If it isn’t, the student instructions for that particular challenge will say so.
• project*flag.c: This is the source code for the flag binary, above. This is intended to be a useful reference to aid in identifying and crafting your exploits of flag.
• project*e.py: This is a python3 script with some templated skeleton exploit code. You are welcome to use/ignore this as you see fit.
• project*e.py.bak: This is just a copy of the initial state of e.py in case you accidentally delete it or wish to revert back to start. Simply copy from this file to get a clean-slate e.py.
In all of the projects’ binaries, your goal is to have the binary read from /proc/flag! Most of the time, this is via a system() call like:
system(“cat /proc/flag”);
However, sometimes it’s not that simple – carefully analyze your source code within each challenge to figure out how the binary is meant to read from /proc/flag.
Step 0.2
01_basic_overflow_1
INSTRUCTIONS
Now, let’s take a look at our first introductory challenge to ease us into the exploit development process at a high-level: 01_basic_overflow_1. We’re going to walk you through this one just to give you a sense of what’s to come.
In this task, we’re looking at a simple buffer overflow. A buffer overflow occurs when input exceeds the expected bounds it’s intended to write to, thereby spilling outside those bounds and overwriting other areas of memory. Generally speaking, this kind of incident leads the running process to crash. However, a crafty (and determined) malicious actor may be able to get the process to do something else altogether!
If we were to review the source code for our binary (flag.c), we could start by tracing the code execution flow starting at main(), which is the starting point for all C program code.
cat ~/binexp/01_basic_overflow_1/flag.c
1. The main() function starts by initializing the variable make_me_not_zero to 0.
2. It then declares an int buffer of size 300.
3. There’s a printf() call, which would write some instructions to the user to stdout.
4. The process then blocks for user input with scanf(), writing the input to buffer.
5. There’s then a if-conditional check to see if make_me_not_zero is still 0. If it is, the process terminates; if it isn’t, we arrive at our desired destination in the binary which reads our flag out for us.
Intuitively, we can start to build our attack chain in reverse:
• We want to get to the function call that reads out our flag.
• To get to the above, we need to have make_me_not_zero not be zero by the time the if-conditional evaluates.
• Since the program does not otherwise allow for us to set make_me_not_zero, we need to either overwrite it in memory or otherwise disrupt the control flow of the process.
• Our only input to the process is along the scanf() call, so we’ll investigate what exploit opportunities exist around here.
Now many of you may not necessarily be professional exploit developers already (in fact, some of you may not have exercised secure coding practices in C more generally); understandably, the vulnerability may not immediately be apparent. But if we look into references for scanf(), we can see that it reads in from stdin with the “s” specifier standing for…
Any number of non-whitespace characters, stopping at the first whitespace character found…
“Any number of non-whitespace characters”?! But buffer only allocates for 300 int (300 * 4 bytes)! This adds affirmation to our above-described attack-chain that a buffer overflow may be possible.
OPTIONAL: TRY USING E.PY!
Let’s test our assumptions! Open e.py and take a minute to look it over. When you’re ready, uncomment the following line:
payload = b’A’ * 1209
…and then run it with our dbg option:
python3 e.py dbg
Assuming you’re running this in the VM, you should see another terminal open running GDB with the pwndbg extension. Don’t worry too much about understanding what’s happening here for now; we have several exercises coming up in other stages that dive into all of this. In brief, you’ve launched a debugger and hooked it onto the flag process; that process has ran and is now paused at the start of the main() function. For now, enter “c” or “continue” and let the process resume running.
GDB will likely halt again, throwing a SIGSEGV segmentation fault. Examining the BACKTRACE log panel will show that our main() function successfully made the subsequent function calls necessary to read out the flag; there will also be error messaging informing you of your test’s success – though specifying you need to run your exploit outside of GDB. For us to do that, we’d want to re-run e.py without the ‘dbg’ option like so:
python3 e.py
Make sure you’ve uncommented the payload line, or it won’t work!
Again, don’t worry too much right now about understanding all of the information that GDB is showing you. We’ll go more in-depth with that in the section(s) to follow. For now, go ahead and close the GDB window (or type in “q” or “quit”).
Step 0.3
Having developed our attack chain as a thought exercise and (optionally) affirmed our assumptions through GDB, we can now move on to exploiting the binary for our flag.
Using either e.py or the command line, run flag and pass at least 1209 characters to the program and receive your flag.
Now enter that hash into your project_binexp.json file and submit it to Gradescope to confirm you’ve correctly walked-through the initial setup! Again, if you’re uncertain about the format for what project_binexp.json should look like, see the Submission tab to the left.
Common Pitfalls
“Why 1209 characters?”
Some students may be puzzled by the specificity of 1209. Recall that we need at least 1200 bytes to fill up buffer[300]; the additional 9 bytes are owed to make_me_not_zero being of type u_int64_t. Because of this, make_me_not_zero must be aligned to an 8-byte boundary on the stack which – as it turns out – buffer is not in its addressing. At compile time, several additional “padding” bytes are added in order to properly align make_me_not_zero to this boundary. Ergo, the overflow is 1200 bytes for buffer, 8 bytes to fill the padding, and then at least 1 byte to change the value of make_me_not_zero.
Keep this concept of stack alignment in mind as we progress through the challenges, as the idea of keeping the stack aligned will almost assuredly come back up later.
“I entered a large payload value; I can see the “got it” message printing but I didn’t get a flag.”
While you *could* enter a payload larger than 1209 characters and still get the flag, you do run the risk of actually submitting a payload that’s *too* large. While this does still cause the if-conditional to be evaluated properly, it causes some stack alignment issues that triggers a fault downstream within the flag generating call. Try just 1209 characters.
________________________________________
DISCLAIMER: This website and its content are provided for educational purposes only as part of CS 6035 at Georgia Tech. Students are responsible for verifying all information and should not rely solely on website content for academic decisions. Course materials, assignments, policies, and deadlines are subject to change without notice. Georgia Tech and course instructors are not liable for any damages resulting from use of this website or its content. External links are provided for convenience only and do not constitute endorsement. Students must comply with all applicable academic policies and honor codes. Virtual machine exercises and security tools are for educational use only – unauthorized use against real systems is prohibited. © 2025 Georgia Institute of Technology. All rights reserved. Unauthorized reproduction, distribution, or commercial use is strictly prohibited.
Stage 01
• Overview
o Learning Objectives
o Exercises
• Step 1.1
o Why are we interested in Assembly at all?
o Why are we interested in CPU registers?
• Step 1.2: Exercise 1
• Step 1.3: Exercise 2
Overview
In this part of the project, we’re going to focus more narrowly on some of the foundational aspects that undergird binary exploitation more generally. We’ll look at Intel x86 Assembly, using our tools like GDB to evaluate runtime statuses, and look to solidify our comprehension with the project environment before launching into the more exploit-centric material to come.
Learning Objectives
The core learning objectives for this section are:
• A baseline familiarity with Intel x86 Assembly.
• Utilizing GDB.
• C programming language comprehension.
Exercises
This section features 2 exercises:
• 01_bb_steps
• 02_assemble_the_assembly
Step 1.1
Why are we interested in Assembly at all?
When you compile a source code file (such as flag.c), the compiler (like gcc) translates the high-level human-readable code into machine code that the computer’s processor can execute. A number of operations are performed at compile time (such as optimizing and linking), which obfuscates the binary’s original source code. However, we can still use a disassembler (like objdump) to translate machine code back into lower-level assembly instructions.
In practice, exploit developers generally do not possess the original source code of the binaries they research. But they can utilize tools to pour over and examine the assembly instructions – which can be just as good (provided you know how to read/contextualize assembly). Understanding what these assembly instructions are doing – both individually and collectively – is a fundamental baseline for reverse engineering (and by extension, exploit development).
NOTE: there are also tools that can “de-compile” binaries; these take the translation a step further by attempting to recreate the source code from the disassembled instructions. However, this is often incomplete and – in some cases – inaccurate. We do not supply you with a decompiler tool in this project because we provide you with the original source code. We’ve also compiled all our binaries in gcc with the -g flag, which produces debugging info in the OS’ native format that GDB can use to rebuild the source.
Being able to read and comprehend assembly is often a labor-intensive process, especially if you’ve primarily been exposed to only higher-level languages before. We encourage you to lean into this challenge, however. Without fostering this aptitude, you’ll often be left in a position of brute-forcing/guesswork (being unsure what a process is doing or why your exploit is behaving a certain way).
If this is your first time seeing/engaging x86 Assembly be forewarned that you’ll need to be a quick study for this project. This section’s exercises are meant to help orient you more generally, but the sections to follow will require a firm understanding if you want to avoid getting lost.
In the table below, we’ve listed some of the common instructions you’ll encounter in the course of this project. At a high-level, assembly operations (e.g. mov, xor, ret, etc.) may have 0, 1, or 2 “arguments” to them depending on the particular operation – these arguments are referred to as “operands”. Depending on the instruction, the operand may be a value, something referential to the stack/heap, or a register. We encourage you to consult other reference material as needed to foster your comprehension.
Instruction Description
mov Moves the contents of one memory location into another (as specified by operands).
xor XORs the values of 2 locations in memory against one another, storing the result in the primary operand.
lea “Load effective address”: computes an address of the source operand and stores it in the general register specified by the second.
call Saves procedure linking information on the stack and branches to the called procedure; in layman’s terms: it initiates a function call.
jmp This is an unconditional jump, redirecting the control flow to elsewhere in the binary’s instruction set. Examples of conditional jumps may apppear as jne, jnz, etc., which make the jump only if particular conditions are met (common at branches, such as if-else blocks).
ret Returns transfer of the program control to a return address on the top of the stack; commonly the last instruction performed by most disassembled functions.
Why are we interested in CPU registers?
Registers are part of a CPU’s architecture and are used to store data and perform operations. Assembly instructions make use of registers all the time (and by extension, the stack and heap – topics for another section). In the setup exercise (basic_overflow_1), you may have observed some of the registers and their contents at runtime within the GDB debugger like so:
In the above screenshot, the various R* values (RAX, RBX, RCX, etc.) in red along the left-hand side denote the CPU registers. The values immediately adjacent to them reflect what is presently stored in them. You’ll see that sometimes the register can hold referential addresses which point to other locations in memory (see RAX, RBX, RDX, RSI, etc.) whereas others contain the value itself (e.g. RDI, R8, R11, etc.).
You can always query the current value of a register in GDB. For example, let’s say we wanted to view the contents of RDI:
pwndbg> x $rdi
Throughout this course, you’re going to be working with 64-bit registers. Besides being different in size from 32-bit registers, there’s actually some important architectural differences that you’ll need to know as they relate to binary exploitation. More to-the-point, not all registers are used in the same way by the CPU. Function calls – for example – look at specific registers for things like function arguments. For now, we encourage you to perform independent research into RBP, RSP, and RIP as these will be very important in the sections/exercises to follow.
Step 1.2: Exercise 1
01_bb_steps
Resources
• See our buffer overflow tutorial video
• https://www.cs.uaf.edu/2017/fall/cs301/lecture/09_11_registers.html
Challenge Instructions
This challenge is meant to be a soft introduction to using GDB; however, you are also welcome to calculate the values by hand in reading the source code (flag.c) if you so choose. We recommend using GDB if you have never done so before because of how extensively the remaining project exercises engage the tool.
We can begin by manually starting GDB and hooking it to the flag binary process like so:
cd ~/project/01_bb_steps/
gdb flag
We have extended the default vanilla GDB tool with pwndbg in order to help with things like readability and utility. If you were instead to invoke the binary into GDB with e.py (python3 e.py dbg), you’d observe GDB open as a separate window (see FAQ for folks opting to SSH into the VM). Either way, the pwndbg prompt will wait for you to enter a command; let’s start by setting a breakpoint for the debugger to catch on:
pwndbg> b main
The above sets a breakpoint at the start of the main() function (Note: as a courtesy, all of your e.py files have this configured by default when you invoke the dbg option). Recall that all C-based programs start execution at main(), so we can reliably expect such a function to be present in all of our binaries for this project. Let’s now start the flag binary by running it:
pwndbg> r
Within the GDB interface window, you’ll likely see a flurry of text/blocks showing various things like stack traces, register printouts, code prints, and more. GDB will pause the process’ execution at the start of main() (where we set our breakpoint) and await for the next command.
If you look at the DISASM readout, you’ll note that we’re not quite yet where we want to be. Let’s go to the next instruction, call bb_steps:
pwndbg> ni
The call instruction makes a function call, in this case to the bb_steps() method. If we were to use n or ni now, we’d step over this to the mov instruction at 0x401295. We want to follow the control flow into bb_steps() instead, so we’ll step inside using:
pwndbg> si
After that, you will notice we are now in the bb_steps() function. We can now make larger “next” (n) steps (vs. the more granular “next instruction” or ni) which will traverse the code flow faster by logical instruction blocks. Go ahead and see how this progression synchronizes with the SOURCE (CODE) display readout until you hit the scanf() method.
You can (and should) also take some time to observe the changes in the registers that are taking place with each __asm() line from the source code.
pwndbg> n
At the end of the ASM instructions, you will be prompted to enter in the answers for the two registers RBX and R15. You can enter these into GDB if you want to; however, in order to get the official/valid flag for submission you will have to save your answers, exit GDB, and then enter them into a non-debug binary run, e.g.:
binexp@cs6035:~/binexp/01_bb_steps$ ./flag
What value is currently in RBX?: 0x<valid_answer_here>
Upon correctly answering the questions, you will see your flag printed out, which you can copy into the json file!
Common Pitfalls
“Is this a buffer overflow exercise?”
No.
“What kind of input is the binary expecting?”
The first two questions are expecting you to pass a hex value formatted as a string (i.e. “0xdeadbeef”). The last one will also be a string, but – as an academic exercise – we leave it up to you to determine how it should look.
Step 1.3: Exercise 2
02_assemble_the_assembly
Resources
• https://www.felixcloutier.com/x86/
• https://en.wikipedia.org/wiki/X86_instruction_listings
Challenge Instructions
This challenge will have you determine which series of assembly instructions will direct the code flow into constructing a call that will get the flag. As the challenge name implies, you get to pick the instructions from a limited menu of options that can lead to that outcome.
As the more difficult challenge, guidance for completing this challenge will be a bit more sparse and require you to lean on what you’ve learned thus far to accomplish the task. However, there are some things worth highlighting:
One of the first things you’ll want to do is figure out the target address you want to go to. Try reading the source code (flag.c) and seeing where – logically – that might be; where in the source code is the program making a call to read from /proc/flag? When you’ve identified what looks appropriate, try dumping the instruction set for the binary using objdump:
objdump -D flag > flag.asm
You can then read/search/parse through the generated flag.asm file to look for an appropriate address that lines-up with where you want to go; try looking for the function name(s) to help narrow down your search.
Alternatively, you can use GDB and query the addresses directly (much like we did with the register state up at the top of this section).
Once you find your address, you need to determine what subset of instructions would be necessary (and in what order) to get there and – ultimately – return your flag!
Common Pitfalls
“Is this a buffer overflow exercise?”
No.
“What kind of input is assemble_the_assembly looking for?”
If you’re uncertain about what the binary’s expecting, we would direct you to the `flag.c` source code. If your input does not match one of the supplied switch cases, then it lands into the default case and executes that.
“Do I need to use all of the switch case statements?”
No, you do not. If you get stuck, you might be interested in knowing that there are several different (but valid) combinations available that would result in the binary giving you the flag. Try to understand what the operations are doing (vs. blindly guessing).
Stage 02
• Overview
o Learning Objectives
o Exercises
• Step 2.1:
o What is the stack? Why do we care about it?
o What’s the danger?
• Step 2.2: Exercise 1
• Step 2.3: Exercise 2
• Step 2.4: Exercise 3
Overview
Now we get into the meat-and-potatoes of the binary exploitation project!
Recall in Stage 0 what we did in the guided exercise of 01_basic_overflow_1: we learned how C could be a memory-unsafe language. More to-the-point: we performed a buffer overflow, thereby overwriting a variable (which altered the code flow of the process). It turns out that this kind of vulnerability can extend to overwriting other areas of the execution stack as well. In this section, we’re going to have our first look at stack-based overflows and learn the building blocks that will enable us to tackle more challenging exploits.
Learning Objectives
The core learning objectives for this section are:
• Understanding the stack and stack-smashing comprehension
• Working with pwntools and basic exploit development
• Foundational considerations for code flow redirection
Exercises
This section features 3 exercises:
• 01_basic_overflow_2
• 01_mismatch
• 02_memento
Step 2.1:
What is the stack? Why do we care about it?
In computer science, the stack is a contiguous block of allocated memory. As functions get called, said function’s variables get memory allocated on the stack; as the function call is resolved, the memory for the variables are de-allocated and removed. Helping organize and control this process are the RBP and RSP registers, which store the base pointer and stack pointer values, respectively. These pointers help reference either end of the stack frame and are useful both for pushing/popping values on/off the top of the stack (RSP) or referencing local variables (RBP).
For the purposes of binary exploitation (and by extension, this project), this is useful to us in a lot of different ways. We’ve already seen how overflowing the stack can allow us to overwrite local variables contained within that particular function’s stack frame; but the real utility from this comes from writing into other stack information.
Consider what was described above: when a function call is resolved, it executes a ret assembly instruction to return the execution flow back to wherever it was originally invoked from: that destination is preserved in the stack! Since we’re already overflowing other values in the stack, we can likewise overwrite the destination that the ret instruction goes to!
What’s the danger?
Now all of the above can feel quite abstract – especially if you’ve only ever learned about buffer overflows (or similar memory-based attacks) in academic textbooks. But there’s actually substantial security risks in being able to hijack a process’ control flow at runtime.
In all of the exercises that follow, we merely direct you to exploit the binary into reading from /proc/flag. But we could – in theory – make these binaries do anything we wanted under the EUID of the process (binuser); that’s not particularly useful/threatening in our case (since binuser has similar privileges as the user you’re already logged in as, binexp), but imagine the risks that poses for a vulnerable process running under elevated privileges; if we were to exploit a process running as root (or Administrator, in Windows parlance), we could force the process to perform actions as root. This goes without even addressing the potential harms to what the software itself is responsible for (one could only imagine the potential impacts that could happen to software responsible for payroll or critical infrastructure, for example).
And before you go writing buffer overflows out as yesterday’s news – there continue to be many reported to this day.
Again however, we’re not going to be going that far in this class; these exercises are merely meant to get us acquainted with this class of vulnerability and comfortable with exploiting it at a basic level.
Step 2.2: Exercise 1
01_basic_overflow_2
Resources
• See our buffer overflow tutorial video
• e.py (see the Instructor’s Note in the code comments)
Challenge Instructions
In this task you will learn details about binaries compiled from C code (with gcc) in a Linux environment, and how some basic things can be exploited such as process redirection or control flow hijacking. We strongly encourage students consult the intro video included in the resources section above to help orient you to the task more generally.
For this task you have an executable binary named flag which is vulnerable to a buffer overflow in one of its functions. We will be using a Python exploitation library called pwntools to automate some of the overflow techniques and get the binary to call a function it otherwise wouldn’t have. This function called call_me() generates a key using your Gradescope User ID to get a valid flag that you will ultimately write to your project_binexp.json file for grading.
Now we will run the binary just to see what the program is doing:
$ cd ~/project/01_basic_overflow_2
$ ./flag
We see the binary is asking for a string. Input any text you want or just press enter and you’ll (likely) see that the program does nothing and just exits. That would align with our expectations from reading the source code (flag.c). If we look into the read() function, we can learn…
read() attempts to read up to count bytes from file descriptor fd into the buffer starting at *buf
Oh no! In this case, read() will write up to 1000 bytes into the buffer, but buffer is only sized for a lesser amount. As we learned earlier, a buffer overflow occurs when too much data is fed into an unprotected (or poorly protected) data buffer; it would appear that flag is vulnerable to a buffer overflow.
DEVELOPING THE EXPLOIT
Open e.py with your preferred text editor (the VM comes with xed by default) and analyze the content and comments. Once you understand what they do, proceed to uncomment the code in Part 1 and fill out the cyclic() size. What size do you need to make payload in order to trigger the segmentation fault from the buffer overflow?
After this, run the exploit through GDB:
python3 /home/binexp/project/01_basic_overflow_2/e.py dbg
This will open up a GDB terminal with a breakpoint set at main(). Within that terminal, pass the “continue” or “c” command to resume the process execution.
Note: the above screenshot is intended to be demonstrative; your actual values may not match what’s shown. If you’re not seeing the ret instruction overflowed with cyclic() data, you may need to increase the size of your input (perhaps considerably so!).
We see the program received an interrupt signal for a SEGMENTATION FAULT (SIGSEV, or an invalid access to memory). This happens when the program tries to access memory at a certain location that it either isn’t allowed to access, or doesn’t exist. In this case the return address for the function was overwritten by cyclic()’s data in the form of a long string of character bytes. Pay attention to the bottom of the screenshot where the instruction pointer is currently trying to ‘ret’ (return) to 0x6561……616b which is just a string of ASCII characters in hexadecimal form.
Now that we know how to break the binary, let’s figure out how to be a little more deliberate/purposeful. Using a pwntools method called cyclic_find() we enter in the bottom 32 bits (4 bytes) of the return string (in the screenshot, the example is 0x6561616b) which will give the number of characters before reaching that value. By knowing exactly how much input we need to overflow our target, we can – in theory – overwrite the target with an arbitrary alternative, thereby hijacking the control flow.
Need more insight into the above? Try checking our FAQ for more details on cyclic() and cyclic_find().
Returning back to e.py, go to “Part 2” in the code comments and update the value for offset based on what you’re seeing from above. Our goal at this step is to validate that we do – in fact – have total control over our target in memory. You might be tempted to skip this step, but you run the risk of simply assuming you’re correct when you’re not; many students have lost hours/days of project time troubleshooting exploits in other exercises because there is something amiss with their offset.
After you have done that, rerun e.py with the dbg option – ensuring to (c)ontinue again when the pwndbg terminal opens again.
If done correctly, you should see something like this screenshot. If you check the ret instruction, we are now failing on an invalid access to our dummy address.
Stepping away from the pwntools library for a moment, we now need to find something usable within the binary that will allow us to actually call a function or do something other than just crashing the program.
Now we will use a linux command objdump which takes a binary file and will output a dump of the binary’s assembly. The -D flag will output binary addresses, machine code, and assembly code of the binary into a file.
objdump -D flag > flag.asm
Then open flag.asm.
You will see a bunch of (likely) confusing information that – at a high level – translates to the code that you can see in the flag.c file. You aren’t going to have to go through this file at length (unless you want to); we are just going to focus on finding an address within the binary file that holds the machine code responsible for reading from /proc/flag. Search flag.asm for call_me in order to find corresponding assembly instructions.
The last part of this exercise is figuring out which assembly instruction is most apt to jump to. Leverage what you learned in the previous Stage, the linked materials in the “Resources” section, and try to determine where it would be best to ret to! When you’re ready, update “Part 3” in the e.py file and run your exploit:
python3 e.py
Common Pitfalls
“What’s the important takeaway?”
This is a key exercise to grasp and understand thoroughly for what’s ahead, especially if the tools/concepts are new to you. At a high-level, we’re presenting a kind of methodology to model our work against:
1. Assess/evaluate the binary and come up with a plan of attack. Where are the vulnerabilities? How can we reach them? How would we use them?
2. Construct an input – with or without e.py – so as to trigger a segmentation fault, affirming the existence of the buffer overflow.
3. Substitute your input with a uniquely sequenced string (likely with cyclic()) so as to fingerprint which areas of memory are overflowable, including the `ret` instruction.
4. Calculate the offset to those particular area(s) using cyclic_find(). Affirm control of the overflowable areas by appending a known bad value (e.g. 0xdeadbeef).
5. Build out the remainder of your exploit.
Be sure to consult the corresponding bof tutorial in /home/binexp/tutorial/bof and the video lecture listed in the resources section!
“Which instruction in call_me() am I supposed to jump to?”
This isn’t meant to be a guessing exercise, though it might feel like one if you don’t understand how to read assembly. We recommend holding up the source code against the `objdump` assembly instructions and annotate line-by-line what’s happening. This should help make it much more clear what options we might consider.
“I’m jumping into call_me() and seeing the call to the flag generator but nothing’s happening. What’s wrong?”
The most likely cause is a stack misalignment issue. See Q8 in our FAQ.
Step 2.3: Exercise 2
01_mismatch
Resources
• See our buffer overflow tutorial video
Challenge Instructions
Now it’s time for an unguided exercise for the section.
This time you need to account for adjusting more than just 1 variable in memory. Here are some suggestions for getting you started:
• Your first actions should be the same as any other binary in the project: read the source code (flag.c) and try running the binary to get an understanding of what the program does at runtime.
• Next, try performing a basic overflow of the program and see what values you can overwrite in GDB. You can view the hex values of a given variable in GDB with the command like x canary1 (or print out their string representations as p/s canary1), if it helps.
• The tricky part for this task is in keeping track of multiple offsets relative to one another.
o Since you have 2 variables (canary1 and canary2) to overflow, only one of them is going to be offset from buffer’s position. The other one will be offset from the input of the first variable.
Common Pitfalls
“I can’t get the process to SEGFAULT!”
There’s a few things to bear in mind here: do we need to overwrite RET in order to arrive at our system() call to read /proc/flag? And have we understood the source code as to why we aren’t able to overflow that operation?
Step 2.4: Exercise 3
02_memento
Resources
• See our buffer overflow tutorial video
Challenge Instructions
In theory, this task is not unlike your run-of-the-mill basic overflow. You can trivially configure an exploit to trigger the segmentation fault and see areas of memory that have been overflowed with your input.
In practice however, you’ll find that this binary does something with your input to make things trickier; because this task is so tightly-coupled to this twist, the guidance we have to offer is relatively sparse. We suggest the following to help get you going:
• Start at the beginning. As always, review the source code. Your task will be to understand what the process if doing with your input (and the implications that means).
• Once you understand the above, you should perform the steps of the basic overflow – adjusting as needed to account for the changes the code is doing to your payload.
• Understanding the structure of the stack, how your pwntools functions work, and endianness will serve you well.
Common Pitfalls
“I’m jumping into call_me() and seeing the call to the flag generator but nothing’s happening. What’s wrong?”
The most likely cause is a stack misalignment issue. See Q8 in our FAQ.
“Pwntools’ cyclic_find() isn’t giving me a correct offset. What’s happening?”
Speaking in general, the exercise’s “twist” is generally understood by students at a high-level; it is relatively trivial to see what’s happening (especially in seeing your altered input get printed back to stdout). However, the nuances of what that means – in memory, with your tools, in your payload – is where people get tripped-up.
Usually, it all comes back to understanding the twist and determining whether or not you’re appropriately accounting for it all the way from how you read the source code, what you are reading out from memory, what you are passing to your pwntools functions, and how you arrange your payload. Typically the fault is because there is some oversight at one of the above-named steps.
Stage 03
• Overview
o Learning Objectives
o Exercises
• Step 3.1:
o So what are ROP gadgets?
o So how are function calls made?
• Step 3.2: Exercise 1
• Step 3.3: Exercise 2
Overview
It’s been a few years since “Smashing the Stack For Fun And Profit” was originally published; since that time additional binary protections have been enacted to mitigate the dreaded buffer overflow. This includes things like:
• Address Space Layout Randomization (ASLR), which randomly arranges the address space positions of key data areas of a process.
• The No eXecute (NX) bit (otherwise known as Data Execution Prevention – DEP), which marks certain areas of the program as not executable (including the stack).
• …and much, much more.
But this hasn’t stopped buffer overflows from being problematic. Return-Oriented Programming (ROP) is a technique that was developed to otherwise bypass these and other controls. At its heart, ROP makes use of snippets of code that already exists within the binary – so called “gadgets” – in order to manipulate the code flow.
Learning Objectives
The core learning objectives for this section are:
• Understanding function calls within 32- and 64-bit systems
• Working with ropper and understanding gadgets
Exercises
This section features 2 exercises:
• 03_inspector_gadget
• 03_ROPscotch
Step 3.1:
So what are ROP gadgets?
At its heart, the “return” in “return oriented programming” is what defines every gadget out there. If you were to dump the assembly instructions from these binaries (e.g. using objdump), you would find any number of instruction sequences that terminate with a ret instruction. By jumping into these instructions, we allow for some atomic, register-oriented actions to take place before the ret instruction hits, thereby returning the execution flow back to the stack (which we ideally control, given our stack-based overflow techniques).
As an exercise, try dumping the assembly from our first problem in Stage 00 and CTRL+F search through the resulting flag.asm file for instances of the ret instruction:
objdump -D ~/binexp/01_basic_overflow_1/flag > flag.asm
Now – obviously – manually parsing through an objdump for a list of operations preceding ret is quite tedious; this is compounded by the fact that not all instruction sequences are necessarily useful to us. Fortunately, we have a tool available for us to quickly identify all number of gadgets on our behalf: ropper!
As a follow up, try using ropper on the same binary:
ropper –file ~/binexp/01_basic_overflow_1/flag
Do the addresses match?
In essence, we’re still performing jumps to areas in code – much like how we were in the stack smashing portion; only this time, we’re additionally leveraging these gadgets to do some setup and register manipulation in order to allow us to get some other malicious actions done.
So how are function calls made?
Thus far, you’ve seen at least one example for how functions are called through assembly instructions and registers: recall the call instruction, which we’ve looked to several times in the past several exercises.
Up until now however, you’ve probably not thought about the structure/setup that these function calls have had to observe. What happens – for example – when a function has an argument (or two+)? Here is one difference between 32- and 64-bit architectures that’s worth noting. In 32-bit architectures, these arguments are pulled from the stack; by contrast, in 64-bit architectures, these values are referenced from the registers.
So, for example, when foo(bar,baz) is invoked in a 32-bit system, we’d want a payload looking something like:
payload = cyclic(…)
payload += p32(foo)
payload += p32(pop_pop_ret_gadget)
payload += p32(bar)
payload += p32(baz)
In the above example, first the function call to foo() is crossed in the stack. When that function call is made, the very next value in the stack is considered the return address; in this case, we’ve overflowed it as being an arbitrary pop_pop_ret gadget (I say “arbitrary”, because it largely doesn’t matter which registers – save for reserved ones like EIP, ESP, and EBP – will hold the removed values in 32-bit systems). The next values in the stack fit the sequential order of arguments expected (first bar, then baz). In this case, we’ve used the particular gadget because of how it will remove bar and baz from the stack after execution by “popping” them; this sets us up for sequential function calls as needed (aka ROP chaining).
There’s a subtle difference when it comes to 64-bit systems like the VM the project is hosted on. In those kinds of systems, you want to lead with the ROP gadget first. This is because it’s necessary to stage the arguments for the function before it’s called. Moreover, we need to be quite selective about which ROP gadgets we reach for (vs. the more arbitrary choices in 32-bit systems); this is because functions will look to specific registers for their values (starting with RDI for the first argument). More generally, you’d want your payload looking like this:
Assuming that the initial address you overwrite in your buffer overflow is a “pop” gadget, the code flow will…
1. (ret)urn to the pop gadget, popping the arguments off the stack and into the respective registers.
2. At the end of the gadget, it will (ret)urn again – this time to the address of the function in question.
3. When the function ends, it will (ret)urn back to whatever’s next in the stack; if we’re chaining ROP calls, this would mean going back to step (1).
Step 3.2: Exercise 1
03_inspector_gadget
Resources
• See our ROP tutorial video
• https://www.uclibc.org/docs/psABI-x86_64.pdf (see figure 3.4: register usage)
• https://cwe.mitre.org/data/definitions/367
• https://cs.brown.edu/courses/cs033/docs/guides/x64_cheatsheet.pdf
Challenge Instructions
In this task, we’re going to build on our understanding of ROP by having you dig into more complicated gadget chains. Additionally, we’ll also need to work with yet another kind of vulnerability: race conditions! As always, evaluate the source code and try to come up with a plan of approach before you start exploiting.
On ROP
In past examples, we’ve shown how function calls in 64-bit systems have relied on arguments being placed in their appropriate registers. This is trivial when we have gadgets on-hand that simply pop the value off of the stack and into the register. However, we’re not always so fortunate as to have such options available. Instead, there’s usually a multitude of more complex gadgets present that we need to string together in a chain in order to setup our function calls. For example, instead of…
pop rdi; ret;
We might need to utilize 2 or more gadgets to achieve the same effect like…
pop rbp; ret;
mov rdi, rbp; ret;
In the above example, the first gadget pops the stack value not into RDI, but into RBP. We then use the 2nd gadget to move that popped value from RBP and into RDI.
For this exercise, we’ve provided a slew of such semi-useful gadgets in the gadgets() method of the flag.c source code. While you can solve the task using these exclusively, you’re more than welcome to identify other working gadgets using ropper on the binary as well.
RACE CONDITIONS
A race condition is a kind of concurrency problem when two or more threads/processes attempt to access shared data simultaneously. Because the execution order is unpredictable, this can lead to unexpected behavior, data corruption, or even program crashes. Because race condition vulnerabilities are unpredictable, exploiting them sometimes can require multiple iterations/attempts before catching the desired aberrant behavior.
TOCTOU
A Time-of-Check, Time-of-Use (TOCTOU) race condition occurs when there is a split between when a resource is initially accessed and when that same resource is actually utilized. If these operations are not atomically secured/linked, then a vulnerability arises where that resource can change between when it’s checked/used.
This particular exercise makes use of a kind of TOCTOU in practice, but you’ll need to identify how to construct your exploit appropriately around it. As always, make sure user.txt is set properly before reading from /proc/flag or else Gradescope will reject your hash!
Step 3.3: Exercise 2
03_ROPscotch
Resources
• See our ROP tutorial video
Challenge Instructions
In this task, we’re going to layer our working understanding of stack-based overflows in performing some ROP. Additionally, we’ll grapple with yet another vulnerability: Integer overflows! Look over the guidance below and then extend it to the source code in figuring out how to exploit flag and get your hash!
INTEGER OVERFLOWS
While numbers more abstractly can extend to values +/- infinity, classical computers cannot hold such unbound values (or rather, various representations of said numbers are capped). Let’s take a contrived example of an unsigned nibble (4-bits). In this case:
0000
Would be the smallest value that could be represented (0 decimal) and…
1111
Would be the largest value that could be represented (15 decimal).
In the above case, incrementing the max value (15) by 1 would cause the stored amount to overflow, looping the value back around to 0. Inversely, decrementing the minimum value (0) by 1 would cause the stored amount to underflow, looping the value back around to 15.
How would the above example change if we used a signed representation – where the most-significant bit controlled whether the value was positive/negative?
Failures to account for these kinds of issues (i.e. having to handle larger/smaller values than anticipated) can have knock-on consequences for systems, sometimes even fatal ones.
TYPE PROMOTION/DEMOTION
There are other ways for integer overflow/underflows to occur than just incrementing/decrementing values. One such method is via type promotion/demotion. This can occur when a value of one type (e.g. char, size 1 byte) is cast to another data type (e.g. short, size 2 bytes).
When a smaller datatype is promoted, the new bits are maximally extended (i.e. 0000 becomes 11110000). Conversely, when a larger datatype is demoted, the most significant bits are truncated (i.e. 11110000 becomes 0000).
If not properly accounted for, this can have unintended consequences. For example, if we demote a signed char value of decimal 121 (01111001) to a nibble (1001), we end up with -7 if signed or 9 if unsigned.
On ROP
• Once you’re able to perform the actual buffer overflow, you should find the necessary ROP gadget(s) you need to make your function call.
o But which function should you call? almost()? system()? Something else altogether?
• Another troubling problem is that there doesn’t appear to be a call being made to read /proc/flag anywhere. Fortunately – just like with the ROP gadgets – all of the pieces of code/data you need already exist within the binary, you just need to piece the function call(s) together with argument(s) that already exist!
Common Pitfalls
“I’m jumping into call_me() and seeing the call to the flag generator but nothing’s happening. What’s wrong?”
You might think that this is a similar stack alignment issue as discussed in our FAQ (Q8) and – in a way – you’d be right. However, for this particular problem the root cause is typically attributed not to picking the right/wrong gadget/instruction, but in having copied too much data and corrupting the stack.
To solve this, most students need to revisit the integer overflow portion of the assignment. A lot of students try to see if they can get away with passing a maximally-sized value to num, not realizing there’s more than one type promotion/demotion vulnerability to consider in the exercise. Where is the second one taking place and why is it an issue?
________________________________________
DISCLAIMER: This website and its content are provided for educational purposes only as part of CS 6035 at Georgia Tech. Students are responsible for verifying all information and should not rely solely on website content for academic decisions. Course materials, assignments, policies, and deadlines are subject to change without notice. Georgia Tech and course instructors are not liable for any damages resulting from use of this website or its content. External links are provided for convenience only and do not constitute endorsement. Students must comply with all applicable academic policies and honor codes. Virtual machine exercises and security tools are for educational use only – unauthorized use against real systems is prohibited. © 2025 Georgia Institute of Technology. All rights reserved. Unauthorized reproduction, distribution, or commercial use is strictly prohibited.
Stage 4
• Step 4.1
o Exercises
• Step 4.2: Exercise 1
• Step 4.3: Exercise 2
• Step 4.4: Exercise 3
Step 4.1
Welcome to the final section of the Binary Exploitation project! We reserve this section semester-over-semester for more advanced topics as well as binaries that we feel help extend student comprehension over the prior sections. Topically, the exercises do not necessarily relate to one-another and thematically should be approached as being distinct in their learning objectives.
Exercises
This section features 3 exercises:
• 03_ransom
• 02_looper
• 02_webbing
Step 4.2: Exercise 1
03_ransom
In this task, we’re going to (loosely) reverse engineer some faux ransomware (instructor note: this isn’t actually ransomeware; the binary is harmless to your VM and host system).
You’re presented with the following files:
• flag.enc: This is the flag binary (compiled from flag.c) but it’s been encrypted by the ransom binary! You need to figure out a way of decrypting this in order to run it and retrieve your flag.
• ransom: This is the ‘ransomware’ that was used to encrypt the flag binary into flag.enc. You need to reverse engineer and exploit this binary in order to decrypt flag.enc. It was compiled from ransom.c.
Understandably, this is a bit of a tougher task than what you might have approached previously. So here’s some suggested considerations:
• The ransom binary uses DES to encrypt/decrypt files. However, you do NOT need to understand the underlying workings of DES in order to solve this task. We’ve abstracted away the encryption/decryption code itself for this reason. Cracking/breaking DES is not the intended approach and falls outside the scope of this challenge.
• If it’s helpful, you can encrypt other files using ransom to better understand what’s happening, if you’d like. Don’t worry – ransom actually just makes an encrypted copy of the original, so you’re not at risk of genuinely harming your system/data unless you delete the original version. Using ransom on a test file and then comparing the two might be insightful. Linux has all kinds of tools available to that end:
o ls -l
o xxd
o stat
o hexdump
o od -Ax -tx1z
o chmod –help
• flag.enc was encrypted using an 8 byte key (e.g. “aaaabbbb”). One of the first things you’ll need to do is figure out what this key is; without this key, you will not be able to decrypt flag.enc. We recommend using ransom’s encrypt functionality on some test files and observing what becomes of your known keys; what does ransom do with the key after encrypting the binary? Try examining your encrypted files in some of the ways mentioned above.
• Once you’ve found the key, you need to figure out a way to decrypt flag.enc. Once again, examining the source code of ransom will likely lead you to some ideas for how you might go about this.
• When ransom decrypts flag.enc, it will be output as flag.enc.dec. If it was decrypted correctly, then you should be able to set the execution bit (see chmod, above) and run it to attain the flag hash.
• There are actually 2 flags for this challenge:
o The first flag comes from pwning the ransom binary. This is within the deadcode() function in the ransom.c source code. That flag belongs in the 03_ransom_binary spot in your project_binexp.json deliverable.
o The second flag comes from successfully running the decrypted version of flag.enc; that flag belongs in the 03_ransom_flag spot in your project_binexp.json deliverable.
o For full credit on this task, you need to submit both hashes in your JSON submission.
Common Pitfalls
I keep getting “Error setting DES encryption key: success” when I try to encrypt files; what gives?
There’s some quirks about DES keys that don’t lend themselves to just any arbitrary 8 characters; that’s not really important to this challenge, but hopefully explains why you get the error message for some of your test keys. If you’re wanting a known “good” test key to try encrypting files with, use the suggested one above (i.e. “aaaabbbb”).
Why does my exploit in GDB show an error like “[Inferior 1 (process 13227) exited with code 01]” when I enter deadcode()?
This is a message from GDB saying the binary it’s debugging called `exit(1)`; this coincides with the last statement of the deadcode() method. This would suggest the exploit has run until completion.
Step 4.3: Exercise 2
02_looper
Resources
• https://www.asciitable.com/
Challenge Instructions
In this task, you are performing another buffer overflow (or maybe several!) in order to break out of a while() loop.
Here’s some suggested guidance:
• There’s a couple of things at work here, so – as always – carefully read the source code to understand what is going on.
• The ideal way to exit the while() loop is to have the flag and breaker variables match. Since they do not align by default, this means either one or the other (or both) will need to be forcefully overwritten/set. But how?
• The developer of this binary thought they could protect against buffer overflows by constraining user input in a struct; this isolates flag and breaker from being directly overflowed (at least insofar as what has been seen in similar challenges). Furthermore, they added a kind of “canary” within the struct (checkme) to help assure data integrity within the struct. Does this prevent the buffer overflow from happening?
• Even if the struct were to become compromised, breaker has a couple of other tricks in place to mitigate a buffer overflow. But can these also be accounted for?
Step 4.4: Exercise 3
02_webbing
Resources
• https://askubuntu.com/questions/334994/which-one-is-better-using-or-to-execute-multiple-commands-in-one-line
• https://en.wikipedia.org/wiki/Input_Field_Separators
Challenge Instructions
In this challenge, you are tasked with exploiting a binary (flag) that acts as a basic webserver. When run, flag hosts a bare-bones app on http://localhost:8080 that echoes back whatever input the user provides it. Looking through the binary’s source code, you may observe that it is not immediately obvious where/how you are meant to exploit the binary. Note the total absence of a system call to /proc/flag! So what are we meant to do?
Let’s start by examining how the Linux commandline interface behaves. Feel free to copy the commands as we step through this orientation to see for yourself how they work. If we wanted to run multiple commands (such as whoami and id) we could enter them in as separate, distinct commands like so:
binexp@CS6035:~$ whoami
binexp
binexp@CS6035:~$ id
uid=1002(binexp) gid=1002(binexp) groups=1002(binexp),100(users),986(vboxsf),1003(student)
However, Linux also permits the execution of multiple commands in a single line – allowing them to be executed sequentially. Functionally, this feature exists to enhance flexibility and efficiency. As such, we could do the exact same as above along a single line using a semicolon separator like so:
binexp@CS6035:~$ whoami; id
binexp
uid=1002(binexp) gid=1002(binexp) groups=1002(binexp),100(users),986(vboxsf),1003(student)
NOTE: there are a number of different separators/terminators besides a semi-colon that exist and not all of them behave the same. Some may output only one of the commands, some may predicate on the successful execution of the commands in sequence, etc.
Looking into flag.c, we can observe an interesting vulnerability within execute_echo() that exploits this condition. Specifically, these lines here:
snprintf(command, sizeof(command), “echo %s”, ip);
snprintf(shell_command, sizeof(shell_command), “/bin/bash -c ‘%s’”, command);
FILE *fp = popen(shell_command, “r”);
In the first line, user input (ip) is appended to the string echo %s, replacing %s. This string – in turn – replaces the %s in the command string /bin/bash -c ‘%s’, which gets ran as: /bin/bash -c ‘echo <whatever_you_type>’
In the above case, if the user input (ip) isn’t sanitized/screened, we could inject arbitrary commands that get passed back to bash to be executed. For example, in the case of ; whoami as the payload, this gets formatted as:
/bin/bash -c ‘echo ; whoami’
The above is akin to:
binexp@CS6035:~$ echo ; whoami
binexp
In the above example, the injected payload terminates the echo command, then runs the whoami command (where we can see the output as “binexp”). We can expand this vulnerability and get creative about what other kinds of commands we could run instead of simply whoami in order to read out from /proc/flag.
Now unfortunately, execute_echo() does some sanitization of user input, so we cannot trivially do the above. That said, the protections are far from exhaustive and we bet you can still figure out how to perform a command injection and retrieve the flag!
Our suggested guidance:
• This is NOT a buffer overflow exploit. You are wasting your time if you try that approach.
• A good starting point is to try getting a basic command injection payload working. See if you can inject the id command, for example. This is easier because of how succinct it is (and the lack of arguments you need to provide it).
• There are multiple ways to go about crafting a working payload that will bypass the protections and read from /proc/flag. If you’re getting stuck thinking one way should work, try looking into alternative approaches.
• Chances are that passing payloads directly from the web interface is bound to have issues. When you submit payloads this way, certain characters in the form data are automatically URL-encoded (i.e. a semi-colon gets converted to %3B). Again, there’s a number of ways around this, if desired; we suggest either using your e.py script or manually submitting requests via cURL.
• This challenge more generally is about the nuances of CLI. If you’re stuck, it’s worth looking into how Linux interprets/ignores certain commands/characters. You might also look into character substitution methods (i.e. how else can we represent the blacklisted characters?).
Common Pitfalls
“Gradescope keeps rejecting my flag!”
Bear in mind that the binary has to be what calls on the flag to be made (vs. some other shell/terminal, for example). As a courtesy, our flag generator notes what calling process invoked the read to /proc/flag, so we encourage you to reference that to ensure your exploit is working properly.
Submission Details
• Submission Details
o FAQ
Q1) What does a flag look like?
Q2) Do I need to submit to both Gradescope and Canvas?
Q3) How many attempts can we submit to Gradescope?
Q4) Once I submit to Gradescope, when will I get my grade?
Q4) Do I get partial credit for passing some tasks?
Q5) How late can I submit to Gradescope?
Q6) Can I get an extension on my project deadline?
Q7) Gradescope isn’t recognizing my flag as valid; what do I do?
Q8) Gradescope timed-out processing my submission; what do I do?
Your grade for this project will be handled through Gradescope. You will submit just one file: project_binexp.json, which you should receive as an unfilled template on your VM. If you did not – or cannot otherwise locate it – below is the template that you can copy:
{
“01_bb_steps”: “<<Copy your flag here>>”,
“01_mismatch”: “<<Copy your flag here>>”,
“01_basic_overflow_1”: “<<Copy your flag here>>”,
“01_basic_overflow_2”: “<<Copy your flag here>>”,
“02_assemble_the_assembly”: “<<Copy your flag here>>”,
“02_memento”: “<<Copy your flag here>>”,
“02_looper”: “<<Copy your flag here>>”,
“02_webbing”: “<<Copy your flag here>>”,
“03_ransom_flag”: “<<Copy your flag here>>”,
“03_ransom_binary”: “<<Copy your flag here>>”,
“03_ROPscotch”: “<<Copy your flag here>>”,
“03_inspector_gadget”: “<<Copy your flag here>>”
}
FAQ
Q1) What does a flag look like?
Flags are produced in reading out from /proc/flag on the VM. They are a long sequence of hex characters. An example of a flag is shown in the screenshot below:
In the above example, you would copy the portion between the “Start FLAG” and “End FLAG” delimiters to the appropriate <copy flag here> spot in your project_binexp.json file.
Q2) Do I need to submit to both Gradescope and Canvas?
No. For this project, you will only submit your work to Gradescope.
Q3) How many attempts can we submit to Gradescope?
There is no limit to the number of times you can submit to Gradescope. By default, Gradescope retains your latest submission (not your highest scoring one); you can manually change which submission attempt should be considered for your final grade within the Gradescope submission interface. We will not be changing this for you.
Q4) Once I submit to Gradescope, when will I get my grade?
Grades will be released after the due date + any extensions have expired. However, Gradescope will give immediate feedback to you on whether or not your flag(s) pass our autograder checks. There are no hidden tests, so you can be confident that your score in gradescope will reflect your final grade for the project (barring extra credit – if offered – and/or any issues with academic integrity).
Q4) Do I get partial credit for passing some tasks?
You get credit for each correct flag successfully submitted to Gradescope. If you do not get a correct flag, you are not awarded points for that portion of the project.
Q5) How late can I submit to Gradescope?
This project has a firm deadline with no late submission policy; please consult the syllabus schedule. Once opened, Gradescope will show when the deadline is along with how much remaining time you have.
Because there are no limits on the number of submission attempts you have, we encourage you to submit early and often. Waiting until the project deadline introduces risks (i.e. Gradescope being flooded by hundreds of student submissions at the same time, Gradescope timing out, etc.); generally speaking, we do not extend the project deadline for such service outages.
Q6) Can I get an extension on my project deadline?
In most cases, no. This includes things like vacations, busy work weeks, general fatigue, etc.
Project extensions will be considered on a case-by-case basis for instances like natural disasters, military deployments/mobilizations, and medical hospitalizations. However, we will require documentation as appropriate (e.g. doctor’s note, military orders, etc.).
ODS accommodation letters do not automatically grant you extensions; such instances are determined on a case-by-case basis as to how best implement the accommodation – not all assignments are subject to extension. If you have such an accommodation letter, please reach out to the TA team as early as possible so we can be made aware.
As much as can be helped, please notify the TA team as early as possible of your request for an extension.
Q7) Gradescope isn’t recognizing my flag as valid; what do I do?
There’s a variety of things that could be contributing to this. Here’s some troubleshooting steps:
• Double-check that /home/binexp/user.txt is set to your GTID; do not add other characters, such as single- or double-quotes.
• Make sure you’ve copy/pasted the flag in its entirety; occassionally we encounter user error where students miss 1 or 2 characters.
• Your choice of text editor may have introduced invalid characters into the project_binexp.json file. This is common with software like LibreOffice and MS Word Document editor. We recommend editing the file either from the command line with vim or through the VM’s TextEdit utility (xed).
• Your JSON formatting may be incorrect; ensure that all of your flag hashes are in-line, book-ended on both sides with quotes, and – with the exception of the last entry – terminated with a comma.
• Make sure you’ve copied the flags to the correct position within the project_binexp.json file; occasionally we see students accidentally swapping the position of key:value pairs.
• Make sure that the flag is being produced by the original binary fetched by the binexp.sh setup script. Occasionally we see students try to recompile their own version of the flag binary and/or patch the original binaries with different assembly instructions; these different versions of the binaries fail to match the fingerprints of the project binaries we have on file.
• One or more of your JSON keys are incorrect; this happens most often when students choose not to use the provided template and make a spelling mistake (or mislabel a key as “01” vs. “02” or “03”, for example).
• Submitting flags generated through GDB won’t work; see the related FAQ on the subject via the FAQ menu.
• Ensure that the correct binary is yielding the flag; occasionally we see students mixing up which e.py exploit corresponds to which flag binary, producing a flag made by a different binary.
If all of the above fails, reach out to the TA team for assistance on the appropriate megathread.
Q8) Gradescope timed-out processing my submission; what do I do?
For this particular project, this usually happens due to a high volume of traffic to Gradescope. Wait a few minutes and try again.
From Canvas:
Welcome to the “Binary Exploitation” assignment! This project will be a “Capture the Flag” style project, wherein you will solve some exploits on binary programs. A correct solution will output a ‘flag’ or ‘key’.
There are 11 tasks to complete for 12 total flags. (The first flag is needed for setup, it does not carry any points).
You will submit these flags in json format to Gradescope for grading.
You’ll use the virtual machine that can be downloaded HERELinks to an external site..
Or you can copy and paste the following link to your browser: “https://cs6035.s3.us-east-1.amazonaws.com/CS6035-Fall-2025-RC-03.ova”
The VM username and password is
• Username: binexp
• Password: Colossus_292
Step 0: IMPORTANT!!
All of the requisite tools necessary for completing the project are installed and accessible as the binexp user on the VM; the user account does not have sudo privileges for installing additional tools.
Both the Main Binary Exploitation project and the Extra Credit challenges are already installed into the user Binexp account.
Go here for project details on the course Github Pages site:
https://github.gatech.edu/pages/cs6035-tools/cs6035-tools.github.io/Projects/BinExp/
Head over to Ed Discussion and feel free to discuss the project within the respective Binary Exploitation megathreads.
Good luck!
Canvas Extra Credit
Welcome to the “Binary Exploitation Extra Credit” assignment! This project will be a “Capture the Flag” style project, wherein you will solve some exploits on binary programs. A correct solution will output a ‘flag’ or ‘key’.
There are 4 tasks to complete for 4 total flags.
You will submit these flags in json format to Gradescope for grading.
You’ll use the same virtual machine you’ve been using for Binary Exploitation project with the same VM username and password as the main project.
Step 0: IMPORTANT!!
All of the requisite tools necessary for completing the project are installed and accessible as the binexp user on the VM; the user account does not have sudo privileges for installing additional tools.
Go here for details/guidance concerning the extra credit for the project:
https://docs.google.com/document/d/1GdzXL-bqgU9YYTuLp9JUf-_qzT38UMXMz6OtvrYyAMc/edit?usp=sharingLinks to an external site.
Links to an external site.
Head over to Ed Discussion and feel free to discuss the project within the respective Binary Exploitation Extra Credit megathreads.
Good luck!

![[SOLVED] Cs6035 project binary exploitation fall2025 scripts](https://assignmentchef.com/wp-content/uploads/2022/08/downloadzip.jpg)

![[SOLVED] Algorithms and Data Structures I Project 5 The Stock Market](https://assignmentchef.com/wp-content/uploads/2022/08/downloadzip-1200x1200.jpg)
Reviews
There are no reviews yet.