Malware Bible
  • The Indoctrination
    • The Beginning
      • Introduction to x86 Assembly
      • Introduction to Shellcode
      • Introduction to Python
      • Introduction to C
      • So You Want to Write Malware?
      • Introduction to Exploit Development
    • The Journey
      • Windows PE File Structure
      • ELF Structures
      • Android Package File Structure
      • iOS App Store File Structure
    • The Rituals
      • Start Writing Malware
      • Malware Obfuscation Techniques
      • Malware Evasion Techniques
    • The Scriptures
      • Yara Rules
      • Malcore
      • Radare2
      • IDAPro
      • Evasion Tactics
      • WinDBG
      • WinAPI
Powered by GitBook
On this page
  • What will be covered?
  • What is exploit dev?
  • Tools of the trade
  • Introduction to fuzzing
  • Buffer overflows
  • ROP (Return Oriented Programming)
  • Heap exploitation
  • Modern Exploit Techniques
  • Kernel exploits
  • That's all folks
  1. The Indoctrination
  2. The Beginning

Introduction to Exploit Development

PreviousSo You Want to Write Malware?NextThe Journey

Last updated 1 month ago

Shameless plug

This course is given to you for free by The Perkins Cybersecurity Educational Fund: in collaboration with the Malcore team:

Please consider donating to Fund and registering for Malcore. You can also join the Malcore Discord server here:

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

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.


NOTE:

This course assumes that you understand the basics of ASM, shellcode, Python, and C. You can find the relevant courses here:


What will be covered?


What is exploit dev?

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.


Tools of the trade

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.


Introduction to fuzzing

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

  1. Dumb fuzzing

    • Completely random inputs.

    • Does not consider the application structure.

    • Useful for simple programs.

    • Has a low hit rate for complex applications

  2. 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.

  3. Coverage guided fuzzing

    • Uses code instrumentation to track execution paths.

    • Prioritizes inputs to increase code coverage.

    • Useful for finding bugs deep in applications.

  4. Grammar based fuzzing

    • Generates syntactically correct inputs.

    • Often used for web applications, interpreters, and scripting engines.

  5. 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:

Python 3.8.10 (default, Nov 14 2022, 12:59:47) 
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
[*] Checking for new versions of pwntools
    To disable this functionality, set the contents of /home/salty/.cache/.pwntools-cache-3.8/update to 'never' (old way).
    Or add the following lines to ~/.pwn.conf or ~/.config/pwn.conf (or /etc/pwn.conf system-wide):
        [update]
        interval=never
[*] You have the latest version of Pwntools (4.14.0)
>>> pattern = cyclic(100)
>>> print(pattern)
b'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa'
>>>

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:

>>> # fake offset found
>>> crash = 0x61616172
>>> print(cyclic_find(crash))
68
>>>

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.


Buffer overflows

#include <stdio.h>
#include <string.h>

void vulnerable_function(char *input) {
    char buffer[16];
    strcpy(buffer, input);
    printf("You entered: %s\n", buffer);
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("Usage: %s <input>\n", argv[0]);
        return 1;
    }
    vulnerable_function(argv[1]);
    return 0;
}

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:

$ gcc -fno-stack-protector -z execstack -Wall .github/exploit_dev_files/example_c.c -o .github/exploit_dev_files/example_c

And you can trigger the buffer overflow like this:

$ ./example_c
Usage: .github/exploit_dev_files/example_c <input>
$ ./example_c AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
You entered: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault

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


ROP (Return Oriented Programming)

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

  1. Overwrite return addresses on the stack with the address of a useful instruction sequence.

  2. Each gadget must end with a ret instruction allowing chaining of multiple.

  3. 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:

pop eax
ret

This gadget will pop a value from the stack into eax and return the address. Another example may be:

mov dword prt [ecx], eax
ret

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

#include <stdio.h>
#include <string.h>

void win() {
    printf("You've won! Code execution achieved.\n");
}

void vulnerable_function() {
    char buffer[64];
    gets(buffer);
}

int main() {
    vulnerable_function();
    return 0;
}

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:

pop eax
ret         ;; load syscall

pop ebx
ret         ;; load address of /bin/sh

pop ecx
ret         ;; null pointer for argv

pop edx
ret         ;; null pointer for envp

int 0x80    ;; trigger execve("/bin/sh", NULL, NULL)

The full payload for the exploit might look something like this:

padding + gadget1 + "/bin/sh" + address + gadget2 + NULL + gadget3 + syscall

While a visual representation of this looks like the following:

# Stack before the exploit is crafted
-----------------------------------
|  Return Address (main)          |
|---------------------------------|
|  Saved EBP                      |
|---------------------------------|
|  Buffer (64 bytes)              |
|---------------------------------|
|  Overflow Starts Here           |
-----------------------------------

# Stack after the exploit is crafted
-----------------------------------
|  Address of ROP Gadget 1        | 
|---------------------------------|
|  Argument for Gadget 1          |
|---------------------------------|
|  Address of ROP Gadget 2        |
|---------------------------------|
|  Argument for Gadget 2          |
|---------------------------------|
|  Address of "system"            | 
|---------------------------------|
|  Address of "/bin/sh"           | 
-----------------------------------

The stack "unwinds" through ret instructions executing each gadget in sequence.

Defending against ROP

Modern systems have defenses to prevent ROP such as:

  1. ASLR (address space layout randomization): randomizes the memory addresses to prevent finding gadgets

  2. Stack canaries: detect stack overflows

  3. CFI (control flow integrity): ensures valid execution flow

  4. 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

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:

var shellcode = unescape("%u4141%u4141%u4141%u4141");
var spray = [];
for (var i = 0; i < 10000; i++) {
    spray[i] = shellcode;
}

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    char *buffer = (char *)malloc(16);
    strcpy(buffer, "AAAAAAAAAAAAAAAAAAAAAAAAAAAA"); 
    printf("Buffer: %s\n", buffer);
    free(buffer);
}

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    char *buffer = malloc(16);
    free(buffer); 
    strcpy(buffer, "Exploited!"); 
    printf("%s\n", buffer);
}

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

#include <stdio.h>
#include <stdlib.h>

int main() {
    char *buffer = malloc(16);
    free(buffer);
    free(buffer); 
}

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.


Modern Exploit Techniques

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

#include <stdio.h>
void vulnerable_function(char *user_input) {
    printf(user_input);
}
int main(int argc, char *argv[]) {
    vulnerable_function(argv[1]);
    return 0;
}

The above code can cause information disclosures or even arbitrary reads. Exploiting this is fairly simple and can be done like so:

$ .github/exploit_dev_files/string_vuln %p
0x7fffffffdc38  # we now have the stack value

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:

struct user {
    char name[16];
    int admin;
};

void edit_user(struct user *u) {
    printf("Enter new name: ");
    gets(u->name);
}

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:

struct user {
    char name[16];
    int admin;
};

void leak_address(struct user *u) {
    int index;
    printf("Enter an index to read: ");
    scanf("%d", &index);

    char *ptr = (char *)u + index;
    printf("Leaked value: %p\n", *ptr);
}

By the pointer not checking bounds we can get a leaked value from it.

Kernel exploits

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:

int fd = open("/dev/path/to/something", O_RDWR);

// free the object
ioctl(fd, FREE_OBJECT, NULL);

// using after it has been freed - possible to hijack
ioctl(fd, USE_OBJECT, NULL);

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


That's all folks

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:

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.

Special thanks to the sponsor of this course: !

Online reverse engineering

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 and seen in depth on Malcore , or you can see below for the exact code:

To build a ROP chain we will create a vulnerable program that you can download as a compiled binary , you can also see information about this program on Malcore .

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 and see more information about the binary on Malcore .

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 and see more information about the binary on Malcore .

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 and see more information about the binary on Malcore .

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 , you can see more information about the binary on Malcore .

Donating to the Malware Bible Fund →

Registering for Malcore →

Joining the Malcore Discord →

Exploring our incredible sponsor for all your cloud hosting needs!

Interested in partnering? Let's talk about how your organization can be featured in our future courses: Please view our for more details!

Backyard Bandwidth
ASM
Shellcode
Python
C
here
here
here
here
here
here
here
here
here
here
here
here
Donate Here
Sign Up
Join Today
Backyard Bandwidth
Contact us today!
Sponsorship Packages
What is exploit development?
Tools of the trade
Fuzzing introduction
Buffer Overflows
Return oriented programming
Heap exploitation
Modern exploit techniques
Kernel exploits
That's all folks
platform
https://perkinsfund.org/
https://m4lc.io/courses/register
The Perkins Cybersecurity Educational
https://m4lc.io/courses/discord