Chapter 15. Tool: Command-Line Fuzzer

Fuzzing is a technique that is used to identify possible vulnerabilities in executables, protocols, and systems. Fuzzing is particularly useful in identifying applications that have poor user-input validation which could result in a vulnerability such as a buffer overflow. Bash is ideal for fuzzing command-line programs that accept arguments, because running programs in the shell is the exact purpose of bash.

In this chapter, we create the tool fuzzer.sh, which fuzzes the command-line arguments of an executable. In other words, it will run a given executable over and over again, each time increasing the length of one of the arguments by one character. Here are the requirements:

  • The argument that is to be fuzzed will be identified using a question mark (?).

  • The fuzzed argument will begin with a single character, and each time the target program is executed, one additional character will be added.

  • The fuzzer will stop after the argument length is 10,000 characters.

  • If the program crashes, the fuzzer will output the exact command that caused the crash, and any output from the program, including errors.

For example, if you want to use fuzzer.sh to fuzz the second argument of fuzzme.exe, you would do so as follows:

./fuzzer.sh fuzzme.exe arg1 ?

The argument you want to fuzz is designated by the question mark (?). Fuzzer.sh will execute the fuzzme.exe program over and over, adding another character to the second argument each time. Done manually, this would look like the following:

$ fuzzme.exe arg1 a
$ fuzzme.exe arg1 aa
$ fuzzme.exe arg1 aaa
$ fuzzme.exe arg1 aaaa
$ fuzzme.exe arg1 aaaaa
.
.
.

Implementation

The program fuzzme.exe is what we will use as the target application. It takes two command-line arguments, concatenates them, and outputs the combined string to the screen. Here is an example of the program being executed:

$ ./fuzzme.exe 'this is' 'a test'

The two arguments combined is: this is a test

Example 15-1 provides the source code for fuzzme.exe, which is written in the C language.

Example 15-1. fuzzme.c
#include <stdio.h>
#include <string.h>

//Warning - This is an insecure program and is for demonstration
//purposes only

int main(int argc, char *argv[])
{
	char combined[50] = "";
	strcat(combined, argv[1]);
	strcat(combined, " ");
	strcat(combined, argv[2]);
	printf("The two arguments combined is: %s
", combined);

	return(0);
}

The program uses the strcat() function, which is inherently insecure and vulnerable to a buffer-overflow attack. On top of that, the program performs no validation of the command-line input. These are the types of vulnerabilities that can be discovered by using a fuzzer.

In Example 15-1, the combined[] variable has a maximum length of 50 bytes. Here is what happens if the combination of the two program arguments is too large to store in the variable:

$ ./fuzzme.exe arg1 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

The two arguments combined is: arg1 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Segmentation fault (core dumped)

As you can see, the data overflowed the space allocated to the combined[] variable in memory and caused the program to crash because of a segmentation fault. The fact that this caused the program to crash means it might not be performing adequate input validation and may be vulnerable to attack.

The purpose of a fuzzer is to help automate the process of identifying the areas of a target program that crash because of invalid input.

The implementation is shown in Example 15-2.

Example 15-2. fuzzer.sh
#!/bin/bash -
#
# Cybersecurity Ops with bash
# fuzzer.sh
#
# Description:
# Fuzz a specified argument of a program
#
# Usage:
# bash fuzzer.sh <executable> <arg1> [?] <arg3> ...
#   <executable> The target executable program/script
#   <argn> The static arguments for the executable
#   '?' The argument to be fuzzed
#   example:  fuzzer.sh ./myprog -t '?' fn1 fn2
#

#
function usagexit ()                            1
{
    echo "usage: $0 executable args"
    echo "example: $0 myapp -lpt arg ?"
    exit 1
} >&2						2

if (($# < 2))					3
then
    usagexit
fi

# the app we will fuzz is the first arg
THEAPP="$1"
shift						4
# is it really there?
type -t "$THEAPP" >/dev/null  || usagexit    5

# which arg to vary?
# find the ? and note its position
declare -i i
for ((i=0; $# ; i++))				6
do
    ALIST+=( "$1" )				7
    if [[ $1 == '?' ]]
    then
	NDX=$i					8
    fi
    shift
done

# printf "Executable: %s  Arg: %d %s
" "$THEAPP" $NDX "${ALIST[$NDX]}"

# now fuzz away:
MAX=10000
FUZONE="a"
FUZARG=""
for ((i=1; i <= MAX; i++))			9
do
    FUZARG="${FUZARG}${FUZONE}"  # aka +=
    ALIST[$NDX]="$FUZARG"
    # order of >s is important
    $THEAPP "${ALIST[@]}"  2>&1 >/dev/null      10
    if (( $? )) ; then echo "Caused by: $FUZARG" >&2 ; fi  11
done
1

We define a function called usagexit to give the user an error message showing the correct way to use the script. After printing the message, the script exits because the script will be called in the case of an erroneous invocation (in our case, not enough arguments). (See 3.) The -lpt argument in the example usage message are arguments to the user’s program myapp, not to the fuzzer.sh script.

2

Because this function is printing an error message, and not printing the intended output of the program, we want the message to go to stderr. With this redirect, all output from inside the function sent to stdout is redirected to stderr.

3

If there aren’t enough arguments, we need to exit; we call this function to explain correct usage to the user (and the function will exit the script and not return).

4

Having saved the first argument in THEAPP, we shift the arguments, so that $2 becomes $1, $3 becomes $2, etc.

5

The type built-in will tell us what kind of executable (alias, keyword, function, built-in, file) the user-specified app really is. We don’t care about the output, so we throw it away by redirecting output to the bit bucket, /dev/null. What we do care about is the return value from type. If the app specified by the user is runnable (one of those types listed), it will return 0. If not, it returns 1, which will then cause the second clause on this line to be executed—that is, it will call the usagexit function—and we’re done.

6

This for loop will cycle through the number of arguments ($#) to the script, though that number will decrease with each shift. These are the arguments for the user’s program, the program we are fuzzing.

7

We save each argument by adding it to the array variable ALIST. Why don’t we just append each argument to a string, rather than keep them as elements of an array? It would work fine if none of the arguments had embedded blanks. Keeping them as array elements keeps them as separate arguments; otherwise, the shell uses whitespace (e.g., blanks) to separate the arguments.

8

As we step through the arguments, we are looking for the literal ?, which is how the user is specifying which argument to fuzz. When we find it, we save the index for later use.

9

In this loop, we are building larger and larger strings for fuzzing the application, counting up to our maximum of 10,000. Each iteration through, we add another character to FUZARG and then assign FUZARG to the argument that had been specified with the ? by the user.

10

When we invoke the user’s command, we provide the list of arguments by specifying all elements of the array; by putting this construct in quotes, we tell the shell to quote each argument, thereby preserving any spaces embedded in an argument (e.g., a filename called My File). Note, especially, the redirections here. First, we send stderr to where stdout is normally sent, but then we redirect stdout to be diverted to /dev/null. The net effect: error messages will be kept, but the normal output will be discarded. The order of those redirections is important. If the order had been reversed, redirecting stdout first, then all the output would be discarded.

11

If the command fails, as indicated by a nonzero return value ( $? ), the script will echo out what argument value caused the error. This message is directed to stderr so that it can be diverted separately from the other messages; the error messages come from the user’s program.

Summary

Using a fuzzer is a great way to automate the process of identifying areas of a program that may lack input validation. Specifically, you are looking to find input that causes the target program to crash. Note that if the fuzzer is successful in crashing the target program, that just identifies an area where further investigation is needed and does not necessarily guarantee that a vulnerability exists.

In the next chapter, we look at various ways to enable remote access to a target system.

Workshop

  1. In addition to being overly large, user input that is of the wrong type can cause an application to crash if it does not have proper validation. For example, if a program expects an argument to be a number, and instead it receives a letter, what will it do?

    Expand fuzzer.sh so that it will fuzz an argument with different random data types (numbers, letters, special characters) in addition to increasing the length. For example, it might execute something like this:

    $ fuzzme.exe arg1 a
    $ fuzzme.exe arg1 1q
    $ fuzzme.exe arg1 &e1
    $ fuzzme.exe arg1 1%dw
    $ fuzzme.exe arg1 gh#$1
    .
    .
    .
  2. Expand fuzzer.sh so that it can fuzz more than one argument at a time.

Visit Cybersecurity Ops website for additional resources and the answers to these questions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset