Introduction to Exploit Development
Last updated
Last updated
Shameless plug
This course is given to you for free by The Perkins Cybersecurity Educational Fund: https://perkinsfund.org/ in collaboration with the Malcore team: https://m4lc.io/courses/register
Please consider donating to The Perkins Cybersecurity Educational Fund and registering for Malcore. You can also join the Malcore Discord server here: https://m4lc.io/courses/discord
Malcore offers free threat intel in our Discord via their custom designed Discord bot. Join the Discord to discuss this course in further detail or to ask questions.
You can also support The Perkins Cybersecurity Educational Fund by buying them a coffee
Sponsor
Special thanks to the sponsor of this course: Backyard Bandwidth!
Enterprise-grade performance at a price that makes sense. No hidden fees, no catch—just reliable, affordable services with a commitment to privacy. Janky But Reliable.
This course assumes that you understand the basics of ASM, shellcode, Python, and C. You can find the relevant courses here:
Exploit development is the process of identifying, understanding and writing code to take advantage of software vulnerabilities. This allows the developer to execute unintended behavior on the target. In edgier terms: exploit development is turning bugs into weapons.
Below is a simple process breakdown on exploit development:
Finding possible vulnerabilities
Identify a possible issue/weakness in software.
Understand how the bug can be exploited
Once you have found the bug you can determine how to control the execution of the bug.
Writing and testing the exploit
Your goal is to develop reliable repeatable code with a high success rate.
Bypassing security protections
Modern systems have security features to protect against exploits most of these will not be covered in depth in this course.
Debuggers
Tool
Description
GDB
The GNU Debugger, used for debugging binaries on Linux. Essential for analyzing crashes and reverse engineering memory corruption bugs.
GDB-PEDA
An enhanced GDB with exploit development tools built-in, making it easier to analyze memory and registers.
GEF
Another powerful GDB plugin, lightweight and packed with heap analysis and ROP tools.
WinDbg
The Windows Debugger, used for analyzing Windows binaries and kernel debugging.
LLDB
Apple's alternative to GDB, useful for debugging on macOS.
x64dbg
GUI debugger useful for debugging and dumping Windows binaries.
Binary Analysis Tools
Tool
Description
IDA Pro
The industry standard for reverse engineering. It provides a graphical disassembler and decompiler.
Ghidra
An open-source alternative to IDA Pro, developed by the NSA. Useful for static analysis and reverse engineering.
radare2
A lightweight disassembler and debugger, packed with scripting capabilities.
Binwalk
Extracts and analyzes firmware and binary blobs to find embedded code and vulnerabilities.
Checksec
Displays the security protections enabled in a binary (e.g., ASLR, NX, RELRO).
Exploitation Frameworks
Tool
Description
Pwntools
A powerful Python library for exploit development, useful for building payloads and automating exploits.
ROPgadget
Finds useful ROP gadgets in a binary for Return-Oriented Programming (ROP) exploits.
OneGadget
Identifies one-shot RCE gadgets in binaries with libc
.
angr
A binary analysis framework that can symbolically execute programs to find exploitable conditions.
Metasploit
A penetration testing framework that includes prebuilt exploits for various vulnerabilities.
Shellcode Generation
Tool
Description
MSFVenom
Generates custom shellcode payloads for various architectures.
nasm
Assembler used for writing custom x86 and x86_64 shellcode.
objdump
Disassembles binaries and extracts shellcode from compiled programs.
strace
Traces system calls made by a binary to identify possible vulnerabilities.
Fuzzing
Tool
Description
AFL++
A fast fuzzing tool that automatically finds crashes and exploits.
Honggfuzz
A modern fuzzer with built-in sanitizer integration.
Radamsa
A mutation-based fuzzer that generates random test cases.
zzuf
Corrupts input to test how a program reacts to unexpected data.
Networking & Exploit Testing
Tool
Description
Wireshark
Captures and analyzes network traffic to inspect exploit behavior.
Tcpdump
A command-line alternative to Wireshark for packet analysis.
Netcat
A simple tool for sending and receiving data over TCP/UDP.
Burp Suite
A web security tool for testing web-based exploits and injections.
Virtual Machines & Sandboxes
Tool
Description
Malcore
QEMU
Lightweight emulator for testing kernel and system-level exploits.
VMware
Used for running Windows and Linux virtual machines for testing exploits.
VirtualBox
A free alternative to VMware for sandboxed environments.
Docker
Runs isolated containers, useful for quick testing environments.
Fuzzing is an automated technique that is used to find vulnerabilities in software. It is used to feed random, unexpected, or malformed inputs into a target platform. It is useful for helping to identify things like buffer overflows, memory corruption, or format string vulnerabilities (all of these will be talked about later in this course). Its goal is to observe the response to the data sent to the target program and see how it reacts.
Types of fuzzing
Dumb fuzzing
Completely random inputs.
Does not consider the application structure.
Useful for simple programs.
Has a low hit rate for complex applications
Smart (structure aware) fuzzing
Uses knowledge of input structures.
Generates mutated test cases that follow expected structure.
Useful for binaries that are expecting certain input types.
Coverage guided fuzzing
Uses code instrumentation to track execution paths.
Prioritizes inputs to increase code coverage.
Useful for finding bugs deep in applications.
Grammar based fuzzing
Generates syntactically correct inputs.
Often used for web applications, interpreters, and scripting engines.
Mutation based fuzzing
Starts with valid inputs and mutates them slightly.
Useful for programs who require structured input.
Creating patterns for fuzzing
When fuzzing programs, it is essential to have a unique pattern that is easily identifiable especially when it comes to buffer overflows or memory corruptions. Things like cyclic patterns can be generated by a multitude of tools, for this course we will be focusing on using pwntools
to do so:
We can use this pattern as the input in order to test crashes in the EIP or RIP value. After we have found the value, we can find the exact offset of the value in the pattern:
This allows us to determine exactly which part of our cyclic pattern produced the crash!
Fuzzing is an essential technique for exploit development. It automates the task of bug discovery, finds memory corruption issues, and provides valuable insights into software security.
Fuzzing mitigations
Use safe memory functions.
Enable compiler protections.
Implement input validation.
Monitor logs.
Fuzz your own software before release.
A buffer overflow occurs when software writes more data into a buffer than it can hold, this leads to memory corruption. This allows an attacker to overwrite memory and can potentially lead to code execution. The following code can be downloaded as a compiled binary directly from here and seen in depth on Malcore here, or you can see below for the exact code:
What is the issue here?
strcpy
blindly copies input into the buffer
variable
If the input is longer than the established size (16 bytes) it overwrites adjacent memory
This can allow us to overwrite the functions return address allowing us to control execution
To trigger the overflow all we would need to do is send more data to the buffer than it is expecting. In order to trigger the overflow we will need to compile this program without any protections, we can do so like this:
And you can trigger the buffer overflow like this:
As you can see above we get a segmentation fault. This means that the program attempted to access memory that it is not allowed to. To prevent buffer overflows the easiest way is to compile the code with protection as well as use safe functions like strncpy
over strcpy
. We have taken the liberty to make a comprehensive list of compiler safety flags in gcc
:
Flag
Purpose
-fstack-protector
Adds stack canaries.
-fstack-protector-all
Adds stack protection to all functions.
-fstack-clash-protection
Detects allocations that could overwrite adjacent memory.
-D_FORTIFY_SOURCE=2
Enhances functions like strcpy()
, memcpy()
, and sprintf()
to detect buffer overflows at runtime.
-Wstack-protector
Enables warnings if stack protection is ineffective.
-Wl,-z,relro
Marks ELF sections as read-only after initialization, preventing memory corruption.
-Wl,-z,now
Forces immediate symbol resolution, reducing attack surfaces like lazy binding exploits.
-Wl,-z,noexecstack
Marks the stack as non-executable, preventing shellcode execution.
-Wl,-z,nodlopen
Disables dynamic library loading, reducing exploit vectors.
-fsanitize=address
Enables AddressSanitizer (ASan) to detect buffer overflows, use-after-free, and memory leaks.
-fsanitize=undefined
Enables Undefined Behavior Sanitizer (UBSan) to detect out-of-bounds memory access.
-fsanitize=leak
Enables LeakSanitizer to detect memory leaks.
-fsanitize=bounds
Detects out-of-bounds array accesses.
-fsanitize=thread
Detects race conditions in multi-threaded programs.
-g
Adds debug symbols for better debugging in tools like gdb
.
-ggdb
Provides more detailed debugging info for gdb
.
NOTE: There are probably more that will protect you, these are just what we could think of at the time
Return oriented programming (ROP) is an advanced exploitation technique that is used to execute arbitrary code in a program that has security measures. This method allows bypassing of traditional buffer overflow protections by reusing preexisting code called "gadgets". ROP is needed because modern operating systems enforce NX stacks (non-executable) meaning even if you overflow the buffer and write shellcode into the memory it cannot be executed. By using ROP you can bypass this protection using gadgets that already exist within the executable parts of the program instead of trying to inject shellcode into an NX stack.
Core ROP idea
Overwrite return addresses on the stack with the address of a useful instruction sequence.
Each gadget must end with a ret
instruction allowing chaining of multiple.
Control flow is hijacked to perform arbitrary operations.
ROP gadgets
A ROP gadget is a small sequence of assembly instructions that ends with a ret
instruction. By chaining these together to form a "ROP chain" you can perform complex tasks. As an example in a binary file, you might have:
This gadget will pop a value from the stack into eax
and return the address. Another example may be:
This gadget writes the value in eax
to the memory address stored in ecx
. By choosing the correct gadgets you can manipulate an entire programs execution flow using preexisting code.
Building a ROP chain
To build a ROP chain we will create a vulnerable program that you can download as a compiled binary here, you can also see information about this program on Malcore here.
This functions gets(buffer)
call allows us to overflow the stack and overwrite return addresses. Now we can build the theoretical ROP chain!
Assuming the win()
function is at location 0x080484b6
instead of injecting shellcode we want to overwrite the return address with the win()
function address. Assuming we want to execute /bin/sh
using gadgets in libc
we need to do the following:
Find a gadget to setup the execve
system call
Find a gadget to control the registers
Find the return address to libc
's system()
function
NOTE: It is worth noting that in more complex situations you may not have direct access to useful functions like this.
We can do this using theoretical gadgets like this:
The full payload for the exploit might look something like this:
While a visual representation of this looks like the following:
The stack "unwinds" through ret
instructions executing each gadget in sequence.
Defending against ROP
Modern systems have defenses to prevent ROP such as:
ASLR (address space layout randomization): randomizes the memory addresses to prevent finding gadgets
Stack canaries: detect stack overflows
CFI (control flow integrity): ensures valid execution flow
CET/Shadow Stacks: or ROP mitigation techniques to block ROP by tracking return addresses
TL;DR
ROP chaining allows attacker to bypass DEP/NX by chaining already preexisting instruction sequences to exploit and manipulate stack behavior. There are plenty of ways to prevent ROP from happening by just being a decent developer.
Heap exploitation is a class of attacks that targets vulnerabilities in dynamic memory management. You can exploit
weaknesses in a memory allocator such as malloc
, free
, new
, delete
in C and C++. Heaps are different from
stacks which are used for function management. Common heap exploitation techniques include:
Heap overflow
Use-After-Free
Double free
NOTE: This is not an extensive list, but for this course we will not get into all of them.
Heap Spraying
Before we can get into these common techniques, we should touch on heap spraying. Heap spraying is a technique where you allocate many predictable objects in memory, this allows you to increase the likelihood of placing your shellcode at a desired location. For example, it is used in browser exploits a lot to overwrite memory in engines like so:
The above code fills the heap with the spray buffers by allocating a large number of identical heap objects. This predictably places the shellcode within memory so that it can be accessed easier at a later time.
Heap overflows
A heap overflow occurs when a program writes more data into a heap-allocated buffer than its allocated size. This leads to memory corruption. You can download this example as a compiled binary here and see more information about the binary on Malcore here.
Here, the buffer
variable is allocated to 16 bytes. As you can see, we attempt to add more data to it than is allocated. This can overwrite
adjacent heap metadata and potentially allow an attack to control execution flow. Assuming this overwritten memory includes functions, pointers or metadata used by the allocator, we can manipulate them to
possibly gain code execution.
Use-After-Free
A UAF (use-after-free) occurs when a program continues to use memory after it has been freed. This allows us to exploit things like dangling pointers. You can find a downloadable compiled binary here and see more information about the binary on Malcore here.
This program uses memory after we have freed. This can lead to exploitation by allowing attackers to control memory in an allocation they normally wouldn't have control over.
Double free
Double free's happen when the same memory is freed twice. This can lead to heap corruption. You can download a copy of the double free example as a compiled binary here and see more information about the binary on Malcore here.
As you can see in the above code free
is called twice on the same buffer right after one another. This can lead to corruption of the "freelist". We can manipulate the heap allocator into reusing the corrupted memory. Leading to an arbitrary write or
the ability to hijack function pointers.
Defense Against Heap Exploitation
Canaries or Heap Cookies: random values placed before chunks to detect corruption
ASLR (address space layout randomization): randomizes heap locations to prevent predictable exploits
DEP (data execution prevention): prevents the execution of heap memory
tcache and hardened allocators: modern allocators like tcmalloc
and jemalloc
introduce security mechanisms.
Exploitation is an ever-evolving landscape. It is constantly growing as attackers discover new and more creative exploit techniques. While buffer overflows and ROP chains remain fundamental, modern exploits are more sophisticated and require more depth to gain control. This section will cover advanced exploitation practices and topics that are beyond the traditional memory corruptions.
Format String Vulnerabilities
Format string exploits are a powerful yet underrated technique that can provide informational leaks, arbitrary memory
writes, and even code execution. C functions like printf
, fprintf
, and snprintf
allow developers to use string
format specifiers to display certain data types such as: %s
, %d
, %p
. If developers forget to provide the proper
format specifier the string can be manipulated by an attacker. This code can be
downloaded as a compiled binary here, you can see more information about the binary on
Malcore here.
The above code can cause information disclosures or even arbitrary reads. Exploiting this is fairly simple and can be done like so:
By combing the leaks and arbitrary writes it is entirely possible to get a full takeover of this program.
Mitigating this attack
Always use printf("%s", variable);
to explicitly specify a format.
Enable FORTIFY_SOURCE
, ASLR
, and Stack Canaries
.
Arbitrary Read/Write Primitives
Exploiting is not always about direct code execution. Sometimes you need some help along the way or places to walk through to act as your steppingstones. An arbitrary read lets an attacker read a memory location; this is often used to leak things such as ASLR protected addresses. An arbitrary write allows modifying memory which can lead to overwriting function pointers, hijacking control flow, or privilege escalation. Take the following pseudocode:
Using a buffer overflow on the gets()
call we are able to overflow the name field to write the number 1 possibly granting
us unauthorized access, the above code provides a good explanation of an arbitrary write. The following pseudocode is a good demonstration of an arbitrary read:
By the pointer not checking bounds we can get a leaked value from it.
Userland is fun but what happens when we need to dive a little deeper? Eventually you get to kernel exploits! By breaking into the
kernel, you officially own that system. In a nutshell: the kernel controls everything including memory,
processes, and hardware. Exploiting the kernel means bypassing Ring 0
protections and executing code with root
privileges, thus giving you full control over everything.
Common Kernel Exploits
NULL pointer dereference: if the kernel dereferences a NULL pointer, attackers are able to map user memory at 0x0
and trick it into executing code.
Race conditions: a bug where multiple processes access shared memory in an unsafe way that can lead to privilege escalation.
Use-After-Free (UAF): reallocating freed kernel objects with malicious data that you control.
NOTE: This is not an extensive list, we will not be going over all of them in this course.
We can demonstrate a UAF by providing the following simple example:
By performing heap spraying techniques it is possible for attackers to replace freed objects with controlled data and hijack the execution. We would fill the heap with our controlled data, when the kernel attempts to free the object, it will unknowingly access our controlled data, if the original object contains something like a function pointer, structure address, or other critical fields they can be overwritten by our controlled data. This can possibly lead to code execution, privilege escalation, or arbitrary memory access.
Mitigations
KASLR (Kernel Address Space Layout Randomization): randomizes the kernel memory locations.
SMEP (Supervisor Mode Execution Prevention): prevents execution of userland code within the kernel.
Memory tagging: detects UAF and buffer overflows dynamically
You're done! You have completed this introductory course! We have covered a broad spectrum of exploit development, from fundamental memory corruption to advanced ROP techniques. Understanding this topic is essential for offensive and defensive security. As you have seen, this field blends art and science and requires creativity, patience, and a deep understanding of system internals. We hope you received as much out of this course as we gained from writing it!
Support the Bible
Once again, this course is offered for free by The Perkins Cybersecurity Educational Fund in collaboration with Malcore! If you found this information valuable and want to support the continued development of the Malware Bible please consider:
Donating to the Malware Bible Fund → Donate Here
Registering for Malcore → Sign Up
Joining the Malcore Discord → Join Today
Exploring our incredible sponsor Backyard Bandwidth for all your cloud hosting needs!
Become a sponsor
These courses reach thousands of cybersecurity professionals, researchers, students, and teachers worldwide who actively engage in learning and advancing the field. Sponsoring our educational initiative not only supports free cybersecurity education but also places your brand in front of a highly technical and security-conscious audience.
Interested in partnering? Let's talk about how your organization can be featured in our future courses: Contact us today! Please view our Sponsorship Packages for more details!
Online reverse engineering