Because of its potential for finding software errors quickly, smart fuzzing has become increasingly prevalent in software development and testing in order to secure the programs, libraries, and operating systems that we rely upon. This lab steps through exercises that will walk you through how to use such tools to identify and correct some of the most common and devastating software errors.
You will deploy a Compute Engine instance, install Docker, build a Docker container image that has AFL installed, download the AFL exercises (based on Thales Security's excellent tutorial), and use AFL to find vulnerabilities in them including the Heartbleed bug.
us-west1-b
ssh
into instancesudo apt update sudo apt install -y docker.io sudo usermod -a -G docker $(whoami)
sudo su -c "echo kernel.core_pattern=core >> /etc/sysctl.conf" echo core | sudo tee /proc/sys/kernel/core_pattern
Note: Derived from Thales E-security AFL training
git clone https://github.com/wu4f/cs492-src cd cs492-src/afl
Read the Docker file to see what is included, then set the password for the fuzzer account in the container (to be used when you sudo
)
FROM ubuntu:22.04
# Originally from Michael Macnair
LABEL maintainer="cs492"
# Users
RUN useradd --create-home --shell /bin/bash fuzzer
# AFL + Deps
USER root
RUN apt update && apt upgrade -y
RUN DEBIAN_FRONTEND=noninteractive apt install -y clang llvm-dev git build-essential curl vim nano libssl-dev screen cgroup-tools sudo gcc-multilib gcc gdb tmux afl++
# For sudo for ASAN:
RUN usermod -aG sudo fuzzer
USER fuzzer
WORKDIR /home/fuzzer
COPY . cs492-afl
# See the README - this password is visible to anyone with access to the image
USER root
RUN echo "fuzzer:`cat cs492-afl/password.txt`" | chpasswd && rm cs492-afl/password.txt
RUN chown -R fuzzer:fuzzer cs492-afl
USER fuzzer
Local container named cs492-afl
echo "cs492592" > password.txt docker build . -t cs492-afl
Done in privileged mode via the use of the "-di
" flag to detach the container once running it (to keep it up). Name the container afl
for use in subsequent commands.
docker run --privileged --network host --name afl -di cs492-afl
Done via
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3d09abf5ec45 afl "/bin/bash" 6 minutes ago Up 2 seconds afl
Done via
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE cs492-afl latest 309344bef881 4 months ago 919MB
afl
", the command isdocker stop afl
docker ps
" with the "-a
" flag to list stopped containersdocker ps -a
docker rm afl
". Note that while this removes the container, it does not remove the local container image it was derived from (i.e. cs492-afl
). docker rmi cs492-afl
" after stopping the containerDone via its name, the command is:
docker start afl
docker exec -it afl /bin/bash
tmux
Used to create multiple shells
tmux
Ctrl-b
> c
to create a new screen*
in the session information at the bottom denoting the active screenCtrl-b
> n
to go to next screenCtrl-b
> p
to go to previous screenIn this lab, you will use the address sanitization feature in the compiler to generate accurate information about any memory corruption bug that your program has. For this program, a trivial buffer overflow bug is in the program. View the output when sanitization is off and when it is on.
View the output of the program when run with a benign input:
cd ~/cs492-afl/01_asan afl-gcc -m32 -o vulnerable vulnerable.c echo hi | ./vulnerable
View the output using a longer input
echo hihihi | ./vulnerable
View the output of the program when run with a benign input:
AFL_USE_ASAN=1 afl-gcc -m32 -o vulnerable vulnerable.c echo hi | ./vulnerable
echo hi | ./vulnerable 2> asan_out.txt
)Answer the following questions by reading the output file.
vulnerable.c
where the error occurs.With the AFL compiler performing the instrumentation, the AFL fuzzer can be used to automatically find memory corruption errors that lead to crashes. Note that the AFL fuzzer requires that your window be resized to at least 80x25. In this level, you will run the fuzzer on a similar program that has a buffer overflow vulnerability.
Build the binary:
cd ~/cs492-afl/02_scanf_fuzz afl-gcc -m32 -o vulnerable vulnerable.c
Then, create a directory for seeding initial input and a directory for output and run the fuzzer on the binary.
mkdir inputs outputs afl-fuzz -i inputs -o outputs -- ./vulnerable
As soon as a single crash is found, <Ctrl-C
> the fuzzer to exit.
outputs/default/crashes/id*
using xxd
Save the output, then repeat the fuzzing step again
outputs/default/crashes/id*
using xxd
In this level, you will use the fuzzer to find another vulnerability. The level includes a Makefile.
Build program via make
cd ~/cs492-afl/03_fmt_fuzz make
Then, run the fuzzer:
afl-fuzz -i inputs -o outputs -- ./vulnerable
As soon as a single crash is found, <Ctrl-C
> the fuzzer to exit.
outputs/default/crashes/id*
using xxd
Fuzzers instrument paths in a program in order to zero in on problematic input. In this level, there are two programs that will both crash on the same input.
Both programs (path_based.c
and strcmp_based.c
) will crash on the same input. One uses a library call that is opaque to the fuzzer while the other implements the logic within its source code. Fuzzing will find the crashing input quickly in one because of the number of code paths it splits itself across, but not in the other. Given what you know about the AFL fuzzer, which one might the fuzzer have difficulty with?
path_based
programBuild both programs via the Makefile
:
cd ~/cs492-afl/04_path_fuzz make
Fuzz the path_based
program to identify the crashing input:
afl-fuzz -i inputs -o outputs -- ./path_based
As soon as a single crash is found, show the following (before exiting the fuzzer):
total execs
" and the total number of paths (indicated by the "corpus count
")outputs/default/crashes/id*
using xxd
corpus count
" based on examining the source code in path_based.c
strcmp_based
programNow, run the fuzzer on the other binary for at most twice the number of total executions as the first. Terminate the fuzzer at this point regardless of output
afl-fuzz -i inputs -o outputs -- ./strcmp_based
corpus count
" that have been foundcorpus count"
is lower between the two programs based on the source code (knowing that the standard C library call is opaque to the fuzzer)With an understanding of how AFL works and how to apply it to find vulnerabilities, we will now examine a more complex program.
vulnerable.c
to locate and describe the three vulnerabilities in the file (one for each command)cd ~/cs492-afl/05_multi CC=afl-clang-fast AFL_HARDEN=1 make
echo hi | ./vulnerable
ec , head, c
)Run the program on the inputs provided in the inputs
directory to understand what each command does
cat inputs/ec ; ./vulnerable < inputs/ec cat inputs/head ; ./vulnerable < inputs/head cat inputs/c ; ./vulnerable < inputs/c
Run the fuzzer using the inputs
directory to seed the search space. Wait until you have found a crashing input for each of the 3 commands. Crashes are located in the outputs directory. A tmux
session will be helpful to monitor the output as the fuzzer runs in another session.
afl-fuzz -i inputs -o outputs ./vulnerable
tmux
session and re-attach at a later timeShow the xxd
output for each of the three crashing inputs found in the outputs
directory.
Now, we will apply AFL to automatically find the Heartbleed vulnerability
Clone using the tag containing the vulnerability
cd ~/cs492-afl/06_heartbleed git clone https://github.com/openssl/openssl.git cd openssl ; git checkout tags/OpenSSL_1_0_1f
Configure and build OpenSSL with ASAN
CC=afl-clang-fast CXX=afl-clang-fast++ ./config -d AFL_USE_ASAN=1 make
Examine handshake.cc
top-level directory. The code that connects the fuzzer to the SSL connection that is made via stdin
is included. Uncomment the appropriate lines in the file after analysis
After editing the file, compile the handshake
program.
AFL_USE_ASAN=1 afl-clang++ -g handshake.cc openssl/libssl.a \ openssl/libcrypto.a -o handshake -I openssl/include -ldl
We will now analyze the bug by examining the SSL record and the heartbeat message embedded into the record (as its record data).
handshake
program, an SSL3 record is constructed that contains a HeartbeatMessage. The input includes an SSL3 record type, an SSL3 major version number, an SSL3 minor version number, the record length, a HeartbeatMessageType (request or response), a payload length for the HeartbeatMessage, and finally the payload for the HeartbeatMessage.Run the fuzzer
afl-fuzz -i inputs -o outputs -m none ./handshake
xxd
, dump each one.Examine OpenSSL source code
ssl/ssl3.h
, find the protocol type in decimal of the TLS HeartBeat message. Calculate its hexadecimal equivalent and locate it in the crashing inputssl3_get_record()
on line 275 of ssl/s3_pkt.c
. This is what the handshake
program has been programmed to send in from a file.rr
).n2s()
is defined in line 249 in ssl/ssl_locl.h
322 /* Pull apart the header into the SSL3_RECORD */ 323 rr->type= *(p++); 324 ssl_major= *(p++); 325 ssl_minor= *(p++); 326 version=(ssl_major<<8)|ssl_minor; 327 n2s(p,rr->length);
Using the code above and the structure definition given for the HeartbeatMessage, for each of your inputs
xxd
listing of the crashing input, the values for rr->type
, ssl_major
, ssl_minor
, and rr->length
that are used.xxd
listing of the crashing input the HeartbeatMessageType
(a single byte), the HeartbeatMessage payload_length
, and the Heartbeat Message payload
(if any).ssl/t1_lib.c
Celebrate! (Or not). Be sure to stop the VM to save $.