offensive-fuzzing-course — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited offensive-fuzzing-course (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Week 2 of the exploit development curriculum. Covers fuzzing methodology: target selection, corpus generation, coverage-guided fuzzing with AFL++/libFuzzer, structured fuzzing, and triage/deduplication. Use when setting up fuzz campaigns, selecting harness strategies, or triaging fuzzer output.
Use this skill when the conversation involves any of: fuzzing curriculum, AFL++, libFuzzer, coverage-guided fuzzing, corpus generation, harness, fuzz target, mutation, triage, crash dedup, week 2, exploit dev course
When this skill is active:
_created by AnotherOne from @Pwn3rzs Telegram channel_.
This document is Week 2 of a multi‑week exploit development course, focusing on discovering vulnerabilities through fuzzing techniques and analyzing the crashes to determine exploitability.
Last week we studied vulnerability classes through real-world examples. This week we'll learn to find these vulnerabilities ourselves using fuzzing - the automated technique that has discovered thousands of critical security bugs in production software.
Fuzzing can feel a bit front‑loaded: you may spend time wiring harnesses and running campaigns without immediately finding exciting new bugs, especially on hardened or well‑tested targets. That’s normal, and it's one reason the next week on patch diffing often feels more directly "practical" — many companies already run large fuzzing setups and need people who can understand and exploit the bugs those systems uncover. Still, working through this week is important: it teaches you how fuzzers actually discover real vulnerabilities, so when you later triage crashes or study patches, you'll have a solid intuition for how those bugs were found and how to reproduce them.
Before starting this week, ensure you have:
AFL++.Ari Takanen(From 1.3.2 to 1.3.8 and 2.4.1 to 2.7.5).Andreas Zeller - Read "Introduction" and "Fuzzing Basics."AFL++ Documentation - Follow the quick start guide.AFL++ on a C program# Setting up AFL++
# Install build dependencies
sudo apt update
sudo apt install -y build-essential gcc-13-plugin-dev cpio python3-dev libcapstone-dev \
pkg-config libglib2.0-dev libpixman-1-dev automake autoconf python3-pip \
ninja-build cmake git wget python3.12-venv meson
# Install LLVM (check latest version at https://apt.llvm.org/)
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 19 all
# Verify LLVM installation
clang-19 --version
llvm-config-19 --version
# Install Rust (required for some AFL++ components)
curl --proto '=https' --tlsv1.2 -sSf "https://sh.rustup.rs" | sh
source ~/.cargo/env
# Build and install AFL++
mkdir -p ~/soft && cd ~/soft
git clone --depth 1 https://github.com/AFLplusplus/AFLplusplus.git
cd AFLplusplus
# NOTE: unicorn support might fail(you need to add the env or run ./build_unicorn_support.py and fix issues yourself)
make distrib
sudo make install
# Verify installation
which afl-fuzz
afl-fuzz --version
# Phase 1: Simple crash example
cd ~/ && mkdir -p tuts && cd tuts
git clone --branch main --depth 1 https://github.com/alex-maleno/Fuzzing-Module.git
cd Fuzzing-Module/exercise1 && mkdir -p build && cd build
# Compile with AFL++ instrumentation
CC=/usr/local/bin/afl-clang-fast CXX=/usr/local/bin/afl-clang-fast++ cmake ..
make
# Create seed inputs
cd .. && mkdir -p seeds && cd seeds
for i in {0..4}; do
dd if=/dev/urandom of=seed_$i bs=64 count=10 2>/dev/null
done
# Run AFL++ fuzzer
cd ../build
echo core | sudo tee /proc/sys/kernel/core_pattern
afl-fuzz -i ../seeds/ -o out -m none -d -- ./simple_crash
# Expected output: AFL++ interface showing coverage, crashes, etc.
# Look for crashes in out/crashes/ directory
# Phase 2: Medium complexity example
cd ~/tuts/Fuzzing-Module/exercise2 && mkdir -p build && cd build
CC=/usr/local/bin/afl-clang-lto CXX=/usr/local/bin/afl-clang-lto++ cmake ..
make
cd .. && mkdir -p seeds && cd seeds
for i in {0..4}; do
dd if=/dev/urandom of=seed_$i bs=64 count=10 2>/dev/null
done
cd ../build
afl-fuzz -i ../seeds/ -o out -m none -d -- ./mediumSuccess Criteria:
out/crashes/ directory for any discovered crashesTroubleshooting:
afl-clang-fast not found: Check /usr/local/bin/ is in PATHclang-19 --version)echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor)Background: AFL++ and similar fuzzers are actively used to find vulnerabilities in production software. Let's examine a real case from Week 1.
Case Study - CVE-2024-47606 (GStreamer Signed-to-Unsigned Integer Underflow):
qtdemux_parse_theora_extension had a signed integer underflow that became massive unsigned valueqtdemux)Why Fuzzing Found It:
The Discovery Process:
# 1) Generate a structured MP4 seed corpus (GitHub Security Lab generator)
cd ~/tuts && git clone --depth 1 https://github.com/github/securitylab.git
cd ~/tuts/securitylab/Fuzzing/GStreamer
make
mkdir -p corpus/mp4
./generator -o corpus/mp4
# 2) Build a vulnerable GStreamer (< 1.24.10) with AFL++ + ASan
cd ~/tuts
git clone --branch 1.24.9 --depth 1 https://gitlab.freedesktop.org/gstreamer/gstreamer.git
cd gstreamer
export CC=afl-clang-fast
export CXX=afl-clang-fast++
export CFLAGS="-O1 -g"
export CXXFLAGS="-O1 -g"
sudo apt-get install -y flex bison
# NOTE: this might take a while so you can just build parts of it, not all
meson setup build-afl --buildtype=debug -Db_sanitize=address
ninja -C build-afl -j"$(nproc)"
# 3) Fuzz the QuickTime demuxer pipeline with AFL++
mkdir -p findings
# NOTE: you can fuzz other binaries as well to find bugs
echo core | sudo tee /proc/sys/kernel/core_pattern
afl-fuzz -i ~/tuts/securitylab/Fuzzing/GStreamer/corpus/mp4 \
-o findings -m none -- \
./build-afl/subprojects/gstreamer/tools/gst-launch-1.0 \
filesrc location=@@ ! qtdemux ! fakesink
# Typical outcome after hours of fuzzing:
# - ASan crash inside qtdemux_parse_theora_extension()
# - heap-buffer-overflow in gst_buffer_fill() when copying attacker-controlled data
# Root cause (CVE-2024-47606 / GHSL-2024-166, fixed in 1.24.10):
# - 32-bit signed 'size' underflows → huge unsigned value
# - _sysmem_new_block() overflows when adding alignment/header → tiny (0x89-byte) allocation
# - memcpy() writes the huge size, corrupting GstMapInfo and allocator function pointersKey Insight: Fuzzing excels at finding edge cases in complex parsers that humans would never manually test. The combination of:
...makes it more effective than manual testing for this vulnerability class.
CVE-2024-47606 when code review and unit testing didn't?AFL++AFL++ options (for example, dictionary-based fuzzing, persistent mode).AFL++ with a real-world application like a file format parser to mimic real-world scenarios.# Fuzzing a image parser (dlib imglab)
# NOTE: you can pull older versions to guarantee vulnerable code paths
cd ~/tuts && git clone --depth 1 --branch v19.24.6 https://github.com/davisking/dlib.git
cd dlib/tools/imglab && mkdir -p build && cd build
# Configure sanitizers for better crash detection
export AFL_USE_UBSAN=1
export AFL_USE_ASAN=1
export ASAN_OPTIONS="detect_leaks=1:abort_on_error=1:allow_user_segv_handler=0:handle_abort=1:symbolize=0"
# Install dependencies
sudo apt install -y libx11-dev libavdevice-dev libavfilter-dev libavformat-dev libavcodec-dev \
libswresample-dev libswscale-dev libavutil-dev libjxl-dev libjxl-tools
# Compile with AFL++ and sanitizers
cmake -DCMAKE_C_COMPILER=afl-clang-fast \
-DDLIB_NO_GUI_SUPPORT=0 \
-DCMAKE_CXX_COMPILER=afl-clang-fast++ \
-DCMAKE_CXX_FLAGS="-fsanitize=address,leak,undefined -g" \
-DCMAKE_C_FLAGS="-fsanitize=address,leak,undefined -g" ..
make -j$(nproc)
# Prepare seed corpus
mkdir -p fuzz/image/in
cp ../../../examples/faces/testing.xml fuzz/image/in/
# TODO: try to improve the fuzzing speed using https://aflplus.plus/docs/fuzzing_in_depth/#i-improve-the-speed
# Run AFL++ in parallel mode (Master + Slave instances)
# Terminal 1: Master instance
echo core | sudo tee /proc/sys/kernel/core_pattern
afl-fuzz -i fuzz/image/in -o fuzz/image/out -M Master -- ./imglab --stats @@
# Terminal 2: Slave instance (for parallel fuzzing)
afl-fuzz -i fuzz/image/in -o fuzz/image/out -S Slave1 -- ./imglab --stats @@
# Install crash analysis tools
sudo apt install -y gdb python3-pip valgrind
wget -O ~/.gdbinit-gef.py -q https://gef.blah.cat/py
echo "source ~/.gdbinit-gef.py" >> ~/.gdbinit
# Minimize a crashing input while preserving the crashing behavior (afl-tmin)
# NOTE: there might be no crashes, either fuzz longer or go back to an older tag
CRASH=$(ls ~/tuts/dlib/tools/imglab/build/fuzz/image/out/Master/crashes/id* 2>/dev/null | head -n1)
afl-tmin -i "$CRASH" -o ~/tuts/dlib/tools/imglab/build/fuzz/image/out/Master/crashes/minimized_crash -- ./imglab --stats @@
# Cluster and triage crashes with casr-afl (from CASR tools)
# NOTE: there might be no crashes, either fuzz longer or go back to an older tag
CASR_URL="https://github.com/ispras/casr/releases/latest/download/casr-x86_64-unknown-linux-gnu.tar.xz"
INSTALL_DIR="$HOME/.local"
mkdir -p "$INSTALL_DIR"
wget -O "$INSTALL_DIR/casr-x86_64-unknown-linux-gnu.tar.xz" "$CASR_URL"
tar -xJf "$INSTALL_DIR/casr-x86_64-unknown-linux-gnu.tar.xz" -C "$INSTALL_DIR"
export PATH="$INSTALL_DIR/casr-x86_64-unknown-linux-gnu/bin:$PATH" # provides casr-afl
# Now run casr-afl on the AFL++ output directory
casr-afl -i ~/tuts/dlib/tools/imglab/build/fuzz/image/out/Master -o ~/tuts/dlib/tools/imglab/build/fuzz/image/out/Master_casr_reportsExpected Outputs:
fuzz/image/out/Master/crashes/ or fuzz/image/out/Slave1/crashes/What to Look For:
SIGSEGV or SIGABRT signalsTroubleshooting:
Case Study - CVE-2023-4863 (libWebP Heap Buffer Overflow):
From Week 1, you learned about this critical vulnerability. Let's understand how fuzzing could have (and did) discover similar bugs.
Fuzzing Campaign Strategy:
# Real-world fuzzing setup for image parsers
cd ~/tuts && git clone --depth 1 --branch 1.0.0 https://chromium.googlesource.com/webm/libwebp
cd libwebp && sudo apt-get -y install gcc make autoconf automake libtool
# Compile with AFL++ and all sanitizers
export CC=afl-clang-fast
export CXX=afl-clang-fast++
export AFL_USE_ASAN=1
export AFL_USE_UBSAN=1
export CFLAGS="-fsanitize=address,undefined -g"
export CXXFLAGS="-fsanitize=address,undefined -g"
./autogen.sh
./configure
make -j$(nproc)
# Create fuzzing harness
cat > fuzz_webp.c << 'EOF'
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <webp/decode.h>
#include <webp/types.h>
int main(int argc, char **argv) {
if (argc < 2) return 1;
FILE *f = fopen(argv[1], "rb");
if (!f) return 1;
fseek(f, 0, SEEK_END);
size_t size = ftell(f);
fseek(f, 0, SEEK_SET);
uint8_t *data = malloc(size);
fread(data, 1, size, f);
fclose(f);
// Fuzz target: decode WebP image
int width, height;
uint8_t *output = WebPDecodeRGBA(data, size, &width, &height);
if (output) free(output);
free(data);
return 0;
}
EOF
# Compile fuzzing harness
afl-clang-fast -I./src -o fuzz_webp fuzz_webp.c \
-L./src/.libs -lwebp -fsanitize=address,undefined -g
# Collect seed corpus (valid WebP images)
mkdir -p ~/tuts/libwebp/seeds
# Download some WebP test images
wget -q -O ~/tuts/libwebp/seeds/test1.webp https://www.gstatic.com/webp/gallery/1.webp
wget -q -O ~/tuts/libwebp/O seeds/test2.webp https://www.gstatic.com/webp/gallery/2.webp
wget -q -O ~/tuts/libwebp/O seeds/test3.webp https://www.gstatic.com/webp/gallery/3.webp
# Run AFL++ fuzzer
export LD_LIBRARY_PATH=./src/.libs:$LD_LIBRARY_PATH
afl-fuzz -i seeds/ -o findings/ -m none -d -- ./fuzz_webp @@
# Real campaigns run for weeks. OSS-Fuzz runs 24/7.
# Expected: Crashes in findings/crashes/ directory
# Analysis: ASAN reports showing heap buffer overflowsWhat Fuzzing Discovered:
In the real CVE-2023-4863 case:
BuildHuffmanTable()Why This Bug Survived Testing:
Parallel Fuzzing for Speed:
# Real campaigns use multiple CPU cores
# Master instance
afl-fuzz -i seeds/ -o findings/ -M master -m none -- ./fuzz_webp @@
# Slave instances (in separate terminals or tmux)
for i in {1..5}; do
afl-fuzz -i seeds/ -o findings/ -S slave$i -m none -- ./fuzz_webp @@ &
done
# Check status
afl-whatsup findings/
# Expected output:
# Master: 1234 paths, 5 crashes
# Slave1: 987 paths, 2 crashes
# Slave2: 1056 paths, 3 crashes
# ... (instances share corpus and findings)Why Seed Quality Matters:
# Bad seed corpus: random bytes
dd if=/dev/urandom of=bad_seed.webp bs=1024 count=10
# Result: AFL++ spends time on invalid inputs that fail early parsing
# Coverage: Only reaches format validation code
# Good seed corpus: valid WebP files
# Result: AFL++ mutates valid structure, reaches deep parsing logic
# Coverage: Explores Huffman decoding, color space conversion, filtersBuilding Effective Seed Corpus:
# 1. Collect diverse valid inputs
mkdir -p corpus
# - Different sizes (small, medium, large)
# - Different features (lossy, lossless, animated)
# - Different color spaces (RGB, YUV, alpha channel)
wget -r -l1 -A webp https://www.gstatic.com/webp/gallery/ -P corpus/
# 2. Minimize corpus (remove redundant files)
afl-cmin -i corpus/ -o corpus_min/ -- ./fuzz_webp @@
# 3. Minimize individual files (shrink while preserving coverage)
mkdir -p corpus_tmin
for f in corpus_min/*; do
afl-tmin -i "$f" -o "corpus_tmin/$(basename $f)" -- ./fuzz_webp @@
done
# Result: Smaller corpus = faster fuzzing iterations
# Original: 50 files, 5MB total
# Minimized: 15 files, 500KB total (same coverage)FuzzTest is a unit-test-style, in-process fuzzing framework from Google that:
TEST and FUZZ_TEST side by side in the same file.Where AFL++/Honggfuzz are great for whole programs and black-box binaries, FuzzTest shines when you have source code and want to fuzz individual C++ functions directly.
mkdir -p ~/tuts/first_fuzz_project && cd ~/tuts/first_fuzz_project
git clone --branch main --depth 1 https://github.com/google/fuzztest.git
cat <<EOT > CMakeLists.txt
# GoogleTest requires at least C++17
set(CMAKE_CXX_STANDARD 17)
add_subdirectory(fuzztest)
enable_testing()
include(GoogleTest)
fuzztest_setup_fuzzing_flags()
add_executable(
first_fuzz_test
first_fuzz_test.cc
)
link_fuzztest(first_fuzz_test)
gtest_discover_tests(first_fuzz_test)
EOT
cat <<EOT > first_fuzz_test.cc
#include "fuzztest/fuzztest.h"
#include "gtest/gtest.h"
TEST(MyTestSuite, OnePlusTwoIsTwoPlusOne) {
EXPECT_EQ(1 + 2, 2 + 1);
}
void IntegerAdditionCommutes(int a, int b) {
EXPECT_EQ(a + b, b + a);
}
FUZZ_TEST(MyTestSuite, IntegerAdditionCommutes);
EOT
mkdir -p build && cd build
# configure with fuzztest
cc=clang-19 cxx=clang++-19 cmake -dcmake_build_type=relwithdebug -dfuzztest_fuzzing_mode=on ..
# Build the project
cmake --build . --parallel $(nproc)
# Run the fuzz test (short sanity run)
./first_fuzz_test --fuzz=MyTestSuite.IntegerAdditionCommutes --max_total_time=10You should see FuzzTest/libFuzzer-style statistics (executions per second, coverage, etc.). For a correct property like integer commutativity, the fuzzer should not find crashes.
Now turn FuzzTest onto a deliberately vulnerable function that mimics a classic stack / heap buffer overflow from Week 1.
cd ~/tuts/first_fuzz_project
cat <<'EOT' > first_fuzz_test.cc
#include <cstring>
#include <string>
#include "fuzztest/fuzztest.h"
#include "gtest/gtest.h"
TEST(ArithmeticSuite, OnePlusTwoIsTwoPlusOne) {
EXPECT_EQ(1 + 2, 2 + 1);
}
void IntegerAdditionCommutes(int a, int b) {
EXPECT_EQ(a + b, b + a);
}
FUZZ_TEST(ArithmeticSuite, IntegerAdditionCommutes);
void VulnerableHeaderCopy(const std::string& input) {
char header[32];
// When input.size() > 32 and ASAN is enabled, this becomes a detectable overflow.
std::memcpy(header, input.data(), input.size());
}
FUZZ_TEST(OverflowSuite, VulnerableHeaderCopy);
EOT
cd build
# Build the project
cmake --build . --parallel $(nproc)
# Run only the overflow fuzz test to focus on the bug
./first_fuzz_test --fuzz=OverflowSuite.VulnerableHeaderCopy --max_total_time=20Expected result: After a short time, FuzzTest should report a crash with an AddressSanitizer message similar to:
==1066==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x76f8218731c0 at pc 0x60a5060193a2 bp 0x7ffc65c1fd10 sp 0x7ffc65c1f4d0At this point you can:
first_fuzz_test.cc and fix the bug by adding a length check (for example, only copying up to sizeof(header)).This is exactly the same pattern as our AFL++ labs: fuzzer + sanitizer → crash → root cause → fix, but now entirely inside a unit test binary.
To connect FuzzTest to the real-world parsers from Days 1–2, fuzz a tiny length-prefixed parser that can easily go wrong if you mishandle integer arithmetic.
cd ~/tuts/first_fuzz_project
cat <<'EOT' >> first_fuzz_test.cc
struct Message {
uint8_t len;
std::string payload;
};
Message ParseMessage(const std::string& input) {
Message m{0, ""};
if (input.empty()) return m;
uint8_t len = static_cast<uint8_t>(input[0]);
// BUG -> commenting this would cause fuzzer to find vuln
// - If len > input.size() - 1, this will either truncate or read garbage.
// - Here we clamp len to avoid UB, but real code often forgets this check.
if (static_cast<size_t>(len) > input.size() - 1) {
len = static_cast<uint8_t>(input.size() - 1);
}
m.len = len;
m.payload.assign(input.data() + 1, input.data() + 1 + m.len);
return m;
}
void ParseDoesNotCrash(const std::string& input) {
(void)ParseMessage(input);
}
void LengthFieldRespected(const std::string& input) {
if (input.size() < 2) return;
uint8_t claimed_len = static_cast<uint8_t>(input[0]);
if (static_cast<size_t>(claimed_len) > input.size() - 1) return;
Message m = ParseMessage(input);
EXPECT_EQ(m.len, claimed_len);
EXPECT_EQ(m.payload.size(), claimed_len);
}
FUZZ_TEST(ParserSuite, ParseDoesNotCrash);
FUZZ_TEST(ParserSuite, LengthFieldRespected);
EOT
cd build && cmake --build . --parallel $(nproc)
# Run parser fuzz tests for a short time
./first_fuzz_test --fuzz=ParserSuite.ParseDoesNotCrash --max_total_time=15
./first_fuzz_test --fuzz=ParserSuite.LengthFieldRespected --max_total_time=15What to look for:
ParseMessage (for example, remove the if (len > input.size() - 1) guard - 3 lines), FuzzTest + ASAN/UBSAN should quickly find crashes or undefined behavior.FUZZ_TEST, using the same build system and test runner.FUZZ_TEST that can find new bugs, not just regressions?LengthFieldRespected, what kinds of mistakes in the property itself might cause you to miss real bugs or report lots of false positives?Honggfuzz# Install Honggfuzz
cd ~/soft && git clone --branch master --depth 1 https://github.com/google/honggfuzz.git
#sudo apt-get install -y binutils-dev libunwind-dev libblocksruntime-dev clang libssl-dev
sudo apt-get install -y binutils-dev libunwind-dev libblocksruntime-dev libssl-dev
cd honggfuzz && make -j$(nproc) && sudo make install
# Verify installation
which honggfuzz
honggfuzz --version
# Clone OpenSSL source
cd ~/tuts && git clone --branch openssl-3.1.2 --depth=1 https://github.com/openssl/openssl.git
cd openssl
# Configure OpenSSL for fuzzing (with all legacy protocols enabled for broader attack surface)
CC=/usr/local/bin/hfuzz-clang CXX="$CC"++ ./config \
-DPEDANTIC no-shared -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -O0 \
-fno-sanitize=alignment -lm -ggdb -gdwarf-4 --debug -fno-omit-frame-pointer \
enable-asan enable-tls1_3 enable-weak-ssl-ciphers enable-rc5 enable-md2 \
enable-ssl3 enable-ssl3-method enable-nextprotoneg enable-heartbeats \
enable-aria enable-zlib enable-egd
# Build OpenSSL
make -j$(nproc)
# Build Honggfuzz fuzzers for OpenSSL
# Note: This uses Honggfuzz's example OpenSSL fuzzers
cat > build_fuzzers.sh << 'EOT'
#!/bin/bash
set -x
set -e
echo "Building honggfuzz fuzzers for OpenSSL"
for x in x509 privkey client server; do
hfuzz-clang \
-DBORINGSSL_UNSAFE_DETERMINISTIC_MODE \
-DBORINGSSL_UNSAFE_FUZZER_MODE \
-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION \
-DBN_DEBUG \
-DLIBRESSL_HAS_TLS1_3 \
-O3 -g \
-DFuzzerInitialize=LLVMFuzzerInitialize \
-DFuzzerTestOneInput=LLVMFuzzerTestOneInput \
-I$(pwd)/include \
-I"$HOME"/soft/honggfuzz/examples/openssl \
-I"$HOME"/soft/honggfuzz \
-g "$HOME/soft/honggfuzz/examples/openssl/$x.c" \
-o "libfuzzer.openssl-memory.$x" \
./libssl.a ./libcrypto.a -lpthread -lz -ldl -fsanitize=address
done
EOT
chmod +x build_fuzzers.sh
./build_fuzzers.sh
# Run Honggfuzz on OpenSSL server parser
honggfuzz --input ~/soft/honggfuzz/examples/openssl/corpus_server/ \
-- ./libfuzzer.openssl-memory.server
# Run Honggfuzz on OpenSSL private key parser
honggfuzz --input ~/soft/honggfuzz/examples/openssl/corpus_privkey/ \
-- ./libfuzzer.openssl-memory.privkeySuccess Criteria:
What to Look For:
Note: Real OpenSSL fuzzing often runs for days/weeks. For this exercise, run for at least 30 minutes to see initial results.
Case Study - Heartbleed-Class Bugs in TLS Implementations:
While Heartbleed (CVE-2014-0160) predates modern fuzzing tools, similar vulnerabilities continue to be found through continuous fuzzing campaigns.
Why TLS is Hard to Fuzz:
Honggfuzz Advantages for Network Protocols:
# Example: Fuzzing TLS 1.3 handshake
cat > fuzz_tls13_handshake.c << 'EOF'
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <stdint.h>
#include <stddef.h>
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
if (!ctx) return 0;
SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION);
SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION);
BIO *in_bio = BIO_new(BIO_s_mem());
BIO *out_bio = BIO_new(BIO_s_mem());
SSL *ssl = SSL_new(ctx);
SSL_set_bio(ssl, in_bio, out_bio);
SSL_set_accept_state(ssl);
BIO_write(in_bio, data, size);
SSL_do_handshake(ssl);
SSL_free(ssl);
SSL_CTX_free(ctx);
return 0;
}
EOF
# Compile with Honggfuzz
hfuzz-clang fuzz_tls13_handshake.c \
-I"$HOME"/tuts/openssl/include \
-L"$HOME"/tuts/openssl/lib \
-lssl -lcrypto \
-fsanitize=address,undefined \
-o fuzz_tls13
# Run with Honggfuzz
# TODO: use a better corpus to actually find the vulnerabilities in that code
honggfuzz --input ~/soft/honggfuzz/examples/openssl/corpus_server/ \
--threads 8 \
--timeout 5 \
-- ./fuzz_tls13Real Bugs Found by Protocol Fuzzing:
From OpenSSL and other TLS implementations:
Example: CVE-2022-0778 (OpenSSL Infinite Loop):
# Bug: Infinite loop in BN_mod_sqrt() when parsing elliptic curve points
# Discovery: Fuzzing certificate parsing with malformed EC parameters
# Impact: DoS via crafted certificate
# Fixed: OpenSSL 3.0.2, 1.1.1n
# Fuzzing campaign that found similar bugs:
honggfuzz --input certs/ \
--dict openssl.dict \
--threads 16 \
--timeout 10 \
--rlimit_rss 2048 \
-- ./openssl x509 -in ___FILE___ -text
# Result: Timeout on malformed EC point → DoS vulnerabilityFuzzing vs Real-World Exposure:
| Metric | Production Use (10 years) | OSS-Fuzz (1 year) |
|---|---|---|
| Total connections | Billions | 0 (pure fuzzing) |
| Unique inputs tested | ~1,000 (typical sites) | Trillions |
| Edge cases covered | <1% | >90% |
| Bugs found | ~5 (via exploits) | ~50 |
Key Insight: Fuzzing explores input space breadth that production traffic never reaches.
SyzkallerSyzkaller.Syzkaller on a Linux VM.Syzkaller DocumentationSyzkaller.# Install kernel build dependencies
sudo apt update
sudo apt install -y make gcc flex bison libncurses-dev libelf-dev libssl-dev
# Clone Linux kernel (use a recent stable version)
# Check available tags: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/refs/tags
cd ~/soft && git clone --branch v6.8 --depth 1 git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git kernel
# Verify kernel version
cd kernel && git describe --tags
# Configure kernel for syzkaller
make defconfig
make kvm_guest.config
# Edit .config to enable syzkaller requirements
# Use sed or manually edit .config
sed -i 's/# CONFIG_KCOV is not set/CONFIG_KCOV=y/' .config
sed -i 's/# CONFIG_DEBUG_INFO_DWARF4 is not set/CONFIG_DEBUG_INFO_DWARF4=y/' .config
sed -i 's/# CONFIG_KASAN is not set/CONFIG_KASAN=y/' .config
sed -i 's/# CONFIG_KASAN_INLINE is not set/CONFIG_KASAN_INLINE=y/' .config
sed -i 's/# CONFIG_CONFIGFS_FS is not set/CONFIG_CONFIGFS_FS=y/' .config
sed -i 's/# CONFIG_SECURITYFS is not set/CONFIG_SECURITYFS=y/' .config
echo 'CONFIG_CMDLINE_BOOL=y' >> .config
echo 'CONFIG_CMDLINE="net.ifnames=0"' >> .config
make olddefconfig
make -j$(nproc)
# Create VM image
sudo apt install -y debootstrap
mkdir -p ~/soft/image && cd ~/soft/image
wget https://raw.githubusercontent.com/google/syzkaller/master/tools/create-image.sh -O create-image.sh
chmod +x create-image.sh
./create-image.sh --distribution trixie --feature full
# Install QEMU
sudo apt install -y qemu-system-x86
# Test VM boot (optional - verify image works)
# NOTE: You might need to run this outside of the vm, you might need to change net to e1000
cd /tmp/
sudo qemu-system-x86_64 \
-m 2G -smp 2 \
-kernel ~/soft/kernel/arch/x86/boot/bzImage \
-append "console=ttyS0 root=/dev/sda earlyprintk=serial net.ifnames=0" \
-drive file=~/soft/image/trixie.img,format=raw \
-net user,hostfwd=tcp:127.0.0.1:10021-:22 \
-net nic,model=virtio -enable-kvm -nographic \
-pidfile vm.pid 2>&1 | tee vm.log
# In another terminal, test SSH access
ssh -i ~/soft/image/trixie.id_rsa -p 10021 -o "StrictHostKeyChecking no" root@localhost
# Install Go
cd ~/soft
GO_VERSION="1.25.4"
wget "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz"
sudo tar -C /usr/local -xzf "go${GO_VERSION}.linux-amd64.tar.gz"
export PATH=$PATH:/usr/local/go/bin
# Add to ~/.bashrc for persistence
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
# Verify Go installation
go version
# Clone and build syzkaller
cd ~/soft && git clone --branch master --depth 1 https://github.com/google/syzkaller.git
cd syzkaller && make -j$(nproc)
# Create syzkaller configuration
# NOTE: If you are in the vm with e1000 then you don't need network_device line
cat > my.cfg << 'EOT'
{
"target": "linux/amd64",
"http": "127.0.0.1:56741",
"workdir": "/home/USER/soft/syzkaller/workdir",
"kernel_src": "/home/USER/soft/kernel",
"image": "/home/USER/soft/image/trixie.img",
"sshkey": "/home/USER/soft/image/trixie.id_rsa",
"ssh_user": "root",
"syzkaller": "/home/USER/soft/syzkaller",
"procs": 8,
"type": "qemu",
"vm": {
"count": 3,
"kernel": "/home/USER/soft/kernel/arch/x86/boot/bzImage",
"cmdline": "net.ifnames=0",
"cpu": 2,
"mem": 2048,
"network_device": "virtio-net-pci"
}
}
EOT
# Replace $USER with actual username
sed -i "s/USER/$USER/g" my.cfg
# Create workdir and start syzkaller
mkdir -p workdir
# NOTE: this might take a while, run with -debug to to identify issues
sudo ./bin/syz-manager -config=my.cfg
# Access web interface
# Install text-based browser or use your regular browser
sudo apt install -y w3m w3m-img
w3m http://127.0.0.1:56741
# Or open in regular browser: http://127.0.0.1:56741Success Criteria:
Expected Outputs:
workdir/crashes/ directoryTroubleshooting:
lsmod | grep kvm)chmod 600 trixie.id_rsa)Case Study - CVE-2022-32250 (Linux Netfilter Use-After-Free):
From Week 1, you learned about this vulnerability. Here's how syzkaller discovered it:
net/netfilter/nf_tables_api.c - Linux firewall subsystemThe Discovery Process:
# Syzkaller's approach (simplified)
# 1. System call description for netfilter operations
{
"nfnetlink_create": {
"protocol": "NETLINK_NETFILTER",
"operations": ["NFT_MSG_NEWTABLE", "NFT_MSG_NEWCHAIN", "NFT_MSG_NEWRULE"]
}
}
# 2. Syscall sequence that triggered the bug
socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER)
sendmsg(fd, {
type: NFT_MSG_NEWTABLE,
flags: NLM_F_CREATE | NLM_F_EXCL,
data: [table_attrs]
})
sendmsg(fd, {
type: NFT_MSG_NEWCHAIN,
data: [chain_with_stateful_expr]
})
# Trigger: Modify stateful expression in specific sequence
sendmsg(fd, {
type: NFT_MSG_NEWRULE,
data: [rule_update_that_frees_expr]
})
# Use freed expression -> UAF crash
# 3. KASAN detected use-after-free
# BUG: KASAN: use-after-free in nf_tables_expr_destroy+0x12/0x20
# Read of size 8 at addr ffff888012345678 by task syz-executor/1234Why Syzkaller Found It:
The Reproducer (simplified):
// Generated by syzkaller - minimal reproducer
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/netfilter/nf_tables.h>
int main(void) {
int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER);
// Create table
send_nft_msg(fd, NFT_MSG_NEWTABLE, ...);
// Create chain with stateful expression
send_nft_msg(fd, NFT_MSG_NEWCHAIN, ...);
// Trigger UAF through rule update
send_nft_msg(fd, NFT_MSG_NEWRULE, ...);
return 0;
}Impact: Local privilege escalation from any user to root on systems with unprivileged user namespaces (default on Ubuntu, Debian). Public exploit available within weeks.
Case Study - CVE-2023-32629 (Linux Netfilter Race Condition):
net/netfilter/nf_tables_api.c - Same subsystem, different bugHow Syzkaller Finds Race Conditions:
# Syzkaller executes syscalls in parallel across multiple threads
# VM 1, Thread 1:
socket(AF_NETLINK, ...) → fd1
sendmsg(fd1, NFT_MSG_NEWTABLE, ...)
# VM 1, Thread 2 (simultaneous):
socket(AF_NETLINK, ...) → fd2
sendmsg(fd2, NFT_MSG_NEWTABLE, ...) # Race on same table
# Result: Concurrent access to nf_tables objects without proper locking
# KASAN detects: use-after-free or memory corruptionSyzkaller's Advantages for Kernel Fuzzing:
Analyzing a Syzkaller Bug:
# Download reproducer from dashboard
mkdir -p ~/tuts/syz-repro && cd ~/tuts/syz-repro
wget "https://syzkaller.appspot.com/text?tag=ReproC&x=15ad9542580000" -O repro.c
# Compile and test on vulnerable kernel
gcc -pthread -o repro repro.c
./repro
# Expected: Segmentation Fault
# Kernel log shows UBSAN: array-index-out-of-bounds
# Analyze how it got identified:
https://syzkaller.appspot.com/bug?extid=77026564530dbc29b854Key Insight: Kernel attack surface is massive. Syzkaller's systematic approach finds bugs that would take years of manual testing.
# Install crash analysis toolkit
sudo apt update
sudo apt install -y gdb python3-pip valgrind binutils
# Install GEF (GDB Enhanced Features) - if you haven't done so already
#wget -O ~/.gdbinit-gef.py -q https://gef.blah.cat/py
#echo "source ~/.gdbinit-gef.py" >> ~/.gdbinit
# Install CASR (Crash Analysis and Severity Report) and rust if you haven't already
#curl --proto '=https' --tlsv1.2 -sSf "https://sh.rustup.rs" | sh
#source ~/.cargo/env
cargo install casr
# Verify installations
casr-san --help | head -5Scenario: Fuzzing discovered a crash in an image parser. Let's perform complete analysis.
# Create sample vulnerable image parser
mkdir -p ~/crash_analysis/case1_heap_overflow && cd ~/crash_analysis/case1_heap_overflow
cat > vuln_parser.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
void build_huffman_table(uint8_t *input, size_t size) {
if (size < 8) return;
uint32_t table_size = *(uint32_t*)input;
uint8_t *codes = input + 4;
uint8_t *table = malloc(256);
// VULNERABILITY: No bounds check on table_size
// Can write beyond 256-byte buffer
memcpy(table, codes, table_size); // Heap buffer overflow!
printf("Built Huffman table with %u codes\n", table_size);
free(table);
}
int main(int argc, char **argv) {
if (argc < 2) return 1;
FILE *f = fopen(argv[1], "rb");
if (!f) return 1;
fseek(f, 0, SEEK_END);
size_t size = ftell(f);
fseek(f, 0, SEEK_SET);
uint8_t *data = malloc(size);
fread(data, 1, size, f);
fclose(f);
build_huffman_table(data, size);
free(data);
return 0;
}
EOF
# Compile with ASAN for detailed crash reports
clang-19 -g -O0 -fsanitize=address -o vuln_parser_asan vuln_parser.c
# Create crashing input (table_size = 512, overflows 256-byte buffer)
python3 << 'EOF'
import struct
# table_size = 512 (causes 256-byte overflow)
payload = struct.pack('<I', 512)
payload += b'A' * 512
with open('crash_heap_overflow.bin', 'wb') as f:
f.write(payload)
EOF
# Run and capture ASAN output
./vuln_parser_asan crash_heap_overflow.bin 2>&1 | tee asan_crash.logASAN Output Analysis:
==37160==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x511000000140 at pc 0x56d6a37d0f62 bp 0x7ffd9f024440 sp 0x7ffd9f023c00
WRITE of size 512 at 0x511000000140 thread T0
#0 0x56d6a37d0f61 in __asan_memcpy
#1 0x56d6a38147f5 in build_huffman_table vuln_parser.c:16:5
#2 0x56d6a38148fe in main vuln_parser.c:37:5
0x511000000140 is located 0 bytes after 256-byte region [0x511000000040,0x511000000140)
allocated by thread T0 here:
#0 0x56d6a37d3193 in malloc (vuln_parser_asan+0xcc193) (BuildId: e524ec295f274ddf6e407b3941080060bdfc9d1c)
#1 0x56d6a38147df in build_huffman_table vuln_parser.c:12:22
#2 0x56d6a38148fe in main vuln_parser.c:37:5
SUMMARY: AddressSanitizer: heap-buffer-overflow (vuln_parser_asan+0xc9f61) (BuildId: e524ec295f274ddf6e407b3941080060bdfc9d1c) in __asan_memcpy
Shadow bytes around the buggy address:
0x510ffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x510fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x510fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x511000000000: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
0x511000000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x511000000100: 00 00 00 00 00 00 00 00[fa]fa fa fa fa fa fa faInterpreting the ASAN Report:
heap-buffer-overflowvuln_parser.c:16 in build_huffman_table()Root Cause Analysis:
# View the vulnerable code with context
cat -n vuln_parser.c | sed -n '6,16p'
# 6 void build_huffman_table(uint8_t *input, size_t size) {
# 7 if (size < 8) return;
# 8
# 9 uint32_t table_size = *(uint32_t*)input; // ATTACKER CONTROLLED
# 10 uint8_t *codes = input + 4;
# 11
# 12 uint8_t *table = malloc(256); // Fixed 256 bytes
# 13
# 14 // VULNERABILITY: No bounds check on table_size
# 15 // Can write beyond 256-byte buffer
# 16 memcpy(table, codes, table_size); // Copies attacker-controlled amount!Exploitability Assessment:
# Classify crash automatically with CASR (using the ASAN run you captured above)
#casr-san -o heap_overflow.casrep -- ./vuln_parser_asan crash_heap_overflow.bin 2>&1 | tee casr_heap_overflow.log
casr-san --stdout -- ./vuln_parser_asan crash_heap_overflow.bin | tee heap_overflow.casrep
# The .casrep and log will contain fields like:
# "Type": "EXPLOITABLE",
# "ShortDescription": "heap-buffer-overflow(write)",
# You can still use GDB for manual inspection of the corrupted heap if you want:
gdb ./vuln_parser_asan
(gdb) run crash_heap_overflow.binExploitability Classification: EXPLOITABLE
Reasoning:
table_size from inputcodes array contentReal-World Example: Similar to CVE-2023-4863 (libWebP Heap Buffer Overflow) from Week 1.
mkdir -p ~/crash_analysis/case2_uaf && cd ~/crash_analysis/case2_uaf
cat > vuln_uaf.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char *name;
void (*process)(void);
} Handler;
void default_handler(void) {
printf("Default handler\n");
}
void evil_handler(void) {
printf("Evil handler executed! Code execution via UAF.\n");
}
Handler *handler = NULL;
void register_handler(char *name) {
handler = malloc(sizeof(Handler));
handler->name = strdup(name);
handler->process = default_handler;
}
void unregister_handler(void) {
if (handler) {
free(handler->name);
free(handler);
// BUG: Should set handler = NULL here!
}
}
void attacker_groom_heap(void) {
for (int i = 0; i < 1000; i++) {
Handler *fake = malloc(sizeof(Handler));
fake->name = "pwned";
fake->process = evil_handler;
}
}
void call_handler(void) {
if (handler) {
handler->process();
}
}
int main(int argc, char **argv) {
register_handler("test");
unregister_handler();
attacker_groom_heap();
call_handler();
return 0;
}
EOF
# Compile with ASAN
clang-19 -g -O0 -fsanitize=address -o vuln_uaf_asan vuln_uaf.c
# Run and capture output
./vuln_uaf_asan 2>&1 | tee uaf_crash.logASAN Output:
=================================================================
==38664==ERROR: AddressSanitizer: heap-use-after-free on address 0x502000000010 at pc 0x617b2245a953 bp 0x7ffe92f7c160 sp 0x7ffe92f7c158
READ of size 8 at 0x502000000010 thread T0
#0 0x617b2245a952 in call_handler vuln_uaf.c:44:50
#1 0x617b2245a9d0 in main vuln_uaf.c:53:5
0x502000000010 is located 0 bytes inside of 16-byte region [0x502000000010,0x502000000020)
freed by thread T0 here:
#1 0x617b2245a86a in unregister_handler vuln_uaf.c:29:9
#2 0x617b2245a9c6 in main vuln_uaf.c:51:5
previously allocated by thread T0 here:
#1 0x617b2245a7a5 in register_handler vuln_uaf.c:21:15
#2 0x617b2245a9c1 in main vuln_uaf.c:50:5
SUMMARY: AddressSanitizer: heap-use-after-free vuln_uaf.c:44:50 in call_handler
Shadow bytes around the buggy address:
0x501ffffffd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x501ffffffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x501ffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x501fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x501fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x502000000000: fa fa[fd]fd fa fa fd fa fa fa 00 00 fa fa 00 00Exploitability Assessment:
# Classify crash with CASR again (now for a UAF instead of overflow)
casr-san --stdout -- ./vuln_uaf_asan 2>&1 | tee uaf.casrep
# The report will highlight:
# - Bug class: heap-use-after-free
# - Severity: "NOT_EXPLOITABLE"
# - Reason: ASan instruments the READ of the function pointer *before* the call.
# Since it's a read from freed memory, CASR defaults to "Not Exploitable".
#
# This is a critical lesson: Automated tools are heuristics.
# A human analyst sees "Read of function pointer from freed memory" -> Critical.
# To prove exploitability, we would need to:
# 1. Bypass ASan quarantine (so memory is reallocated/valid)
# 2. Overwrite with a bad pointer
# 3. Trigger the crash on the JUMP (SEGV), not the UAF read.
#
# For now, trust your manual analysis: Controlling a function pointer is exploitable.
# For deeper debugging, verify the exploit manually in GDB:
# NOTE: You must disable ASan quarantine to allow the freed chunk to be reused!
# Otherwise, malloc() will return a new address, and handler->process will still be old/garbage.
export ASAN_OPTIONS=detect_leaks=0:quarantine_size_mb=0
gdb ./vuln_uaf_asan
(gdb) break vuln_uaf.c:44 # Break at call_handler (before crash)
(gdb) run
(gdb) p handler
# $1 = (Handler *) 0x...
(gdb) p *handler
# With quarantine=0, you should see:
# name = "pwned"
# process = <evil_handler>
(gdb) p handler->process
# Should point to evil_handler
(gdb) continue
# Execution should flow to evil_handler() (or crash if ASan still catches the shadow marker)
Exploitability Classification: EXPLOITABLE (Verified via manual analysis)
[!NOTE]: Automated tools like CASR may label this NOT_EXPLOITABLE because ASan instruments the function pointer _read_ before the call. Manual verification (as shown above) proves control flow hijack is possible.Exploitation Strategy:
call_handler()handler->process points to attacker-controlled addressReal-World Example: Similar to CVE-2024-2883 (Chrome ANGLE UAF) from Week 1.
mkdir -p ~/crash_analysis/case3_integer_overflow && cd ~/crash_analysis/case3_integer_overflow
cat > vuln_intoverflow.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
void process_image(uint32_t width, uint32_t height, uint8_t *data) {
size_t pixel_count = width * height;
size_t buffer_size = pixel_count * 4;
printf("Allocating %zu bytes for %ux%u image\n", buffer_size, width, height);
uint8_t *buffer = malloc(buffer_size);
for (size_t i = 0; i < (size_t)width * height; i++) {
// WRITES out of bounds immediately
// Use modulo to avoid reading out of bounds of 'data'
buffer[i * 4] = data[i % 1024];
}
free(buffer);
}
int main(int argc, char **argv) {
// Attacker-controlled dimensions
uint32_t width = 0x10000; // 65536
uint32_t height = 0x10000; // 65536
uint8_t fake_data[1024];
memset(fake_data, 'A', sizeof(fake_data));
process_image(width, height, fake_data);
return 0;
}
EOF
# Compile with ASAN and UBSAN
clang-19 -g -O0 -fsanitize=address,unsigned-integer-overflow -o vuln_int_asan vuln_intoverflow.c
# Run
./vuln_int_asan 2>&1 | tee intoverflow_crash.logAnalysis:
vuln_intoverflow.c:7:32: runtime error: unsigned integer overflow: 65536 * 65536 cannot be represented in type 'uint32_t' (aka 'unsigned int')
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior vuln_intoverflow.c:7:32
=================================================================
==39011==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000000014 at pc 0x5fa5104bd933 bp 0x7ffd7d3885a0 sp 0x7ffd7d388598
WRITE of size 1 at 0x502000000014 thread T0
#0 0x5fa5104bd932 in process_image vuln_intoverflow.c:17:23
#1 0x5fa5104bdad0 in main vuln_intoverflow.c:31:5
0x502000000014 is located 3 bytes after 1-byte region [0x502000000010,0x502000000011)
allocated by thread T0 here:
#1 0x5fa5104bd7fc in process_image vuln_intoverflow.c:12:23
SUMMARY: AddressSanitizer: heap-buffer-overflow vuln_intoverflow.c:17:23 in process_image
Shadow bytes around the buggy address:
0x501ffffffd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x501ffffffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x501ffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x501fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x501fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x502000000000: fa fa[01]fa fa fa fa fa fa fa fa fa fa fa fa fa
Root Cause:
width * height overflows 32-bit integer range, wrapping to 0.malloc(0) allocates a tiny chunk.Exploitability Assessment:
# Classify crash with CASR
# Note: UBSAN might print an error first, but ASAN catches the memory corruption
casr-san --stdout -- ./vuln_int_asan 2>&1 | tee intoverflow.casrep
# The report will highlight:
# "Type": "EXPLOITABLE",
# "ShortDescription": "heap-buffer-overflow(write)",
# "Description": "Heap buffer overflow",
# Verify manually in GDB:
gdb ./vuln_int_asan
(gdb) break vuln_intoverflow.c:17 # Break inside the loop
(gdb) run
(gdb) p buffer_size
# $1 = 0
(gdb) p buffer
# $2 = (uint8_t *) 0x... (Valid small chunk)
(gdb) x/4gx buffer
# Check adjacent memory (likely metadata or other chunks)
(gdb) step
# Watch the write to buffer[0] corrupting the heap
(gdb) continueExploitability Classification: EXPLOITABLE
Exploitation Strategy:
0x10000 * 0x10000 to cause integer overflow -> malloc(0).fake_data) into the adjacent sensitive object.#### Heap Overflow Example
cd ~/crash_analysis/case1_heap_overflow
cat > buf_ov.py << 'EOF'
#!/usr/bin/env python3
# POC for heap buffer overflow in vuln_parser
import struct
def craft_overflow_input():
"""Create input that causes a massive overflow"""
# Target allocates 256 bytes. We claim size is 512.
table_size = 512
# Structure: [Size (4 bytes)] [Data (512 bytes)]
payload = struct.pack('<I', table_size)
payload += b'A' * table_size
return payload
with open('poc_exploit.bin', 'wb') as f:
f.write(craft_overflow_input())
print("[+] Created poc_exploit.bin")
print("[+] Run the ASan build for diagnostics or the no-sanitizer build for raw exploitation.")
EOF
python3 buf_ov.py
# Diagnostic run (shows detailed ASan report)
./vuln_parser_asan poc_exploit.bin
# Realistic exploit run (no sanitizers, glibc notices corrupted heap metadata)
clang-19 -g -O0 -o vuln_parser_nosan vuln_parser.c
MALLOC_CHECK_=0 ./vuln_parser_nosan poc_exploit.bin#### Use-After-Free Example
cd ~/crash_analysis/case2_uaf
# Build a runtime version without sanitizers (ensures freed chunks are reused immediately)
clang-19 -g -O0 -o vuln_uaf_nosan vuln_uaf.c
# Optional: keep the ASan build for triage but disable qua~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.