VolatileMinds

Application Security and Software Consulting



Advanced AFL usage with real-world examples -- preeny and dictionaries

Sometimes, you want to fuzz an application which takes input from a network socket, rather than as an argument or via stdin. For instance, the key-value store Redis listens and accepts commands to store and retrieve data on a given port. If we want to fuzz Redis with AFL, then we need to redirect the network input to stdin, and we can accomplish this with a small open source project called Preeny and LD_PRELOAD.

LD_PRELOAD is an environment variable that, when set correctly, allows a user to override certain functions in the program with a given dynamic library. This is generally a useful feature for systems administrators who must support software that requires multiple versions of the same library on the same machine.

Preeny offers a small set of pre-written functions that are useful for overriding during security research, such as specific networking functions like connect() and accept(). These functions have some “smarts” written in that can detect file vs network stream and can transparently pass file stream access while modifying the behaviour of network stream access.

A good thing about Redis is that the protocol it uses is very simple, with clear tokens that we can point AFL at for use during the fuzzing process to help aid in new path discovery.

With some elbow grease, we can get all of the above working together (Redis, LD_PRELOAD, Preeny, AFL) and fuzzing Redis locally. However, the fuzzing will be relatively slow without breaking Redis up and fuzzing disparate aspects of it as server initialization is a bit expensive. Persistent mode is also a difficult thing to use in this case due to the amount of work needed to be done to initiate a server, and how that work is done. All in all, this article won’t show you how to quickly fuzz Redis, but the general idea behind fuzzing a network application by redirecting network inputs to stdin. An introduction to the dictionary feature of AFL will also be covered to show how AFL can generate interesting new testcases based on a given token set.

As with previous articles regarding AFL, an assumption is made that a chroot has already been set up and AFL has been compiled (including afl-clang-fast!) and installed in the chroot.

Getting and modifying Redis for fuzzing

We will fuzz the Redis master branch on Github.

git clone https://github.com/antirez/redis.git

After downloading the Redis code from Github, we need to modify it to make it easier to fuzz. We want Redis to really just read in a single command, process that command, then exit. As it stands, Redis will continue to keep reading data until the server shuts down or is sent the SHUTDOWN command. To get around this, in src/networking.c, we add a line to the readQueryFromClient function to simply exit after processing a command from a client.

diff --git a/src/networking.c b/src/networking.c
index 336561e..afdf555 100644
--- a/src/networking.c
+++ b/src/networking.c
@@ -1217,6 +1217,7 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
        return;
    }
    processInputBuffer(c);
+   exit(1);
}

void getClientsMaxBuffers(unsigned long *longest_output_list,

Once that change is made, we need to compile the server statically.

CC=afl-clang-fast CFLAGS="-static" make clean all

Preeny

Now we can grab and build preeny from Github, which is simple.

git clone https://github.com/zardus/preeny.git

apt-get install libini-config3 libini-config-dev

cd preeny && make && cd

Running Redis

There is a very small race condition in Redis when it listens on multiple interfaces, which production machines will likely never see. In order to work around this race condition, we can tell Redis to just listen on a single interface (by default it attempts to listen on INET and INET6, if available). Creating and passing a small conf file to Redis as an argument will mitigate this.

# cat conf
bind 127.0.0.1
#

Let’s test everything now. Using afl-showmap to trace the execution path of a single input, we call our instrumented and modified server, passing a file descriptor to stdin containing “PING”. LD_PRELOADing preeny’s desock.so will take the stdin input and redirect Redis to read it instead.

# LD_PRELOAD=preeny/x86_64-linux-gnu/desock.so afl-showmap -m2048 -o/dev/null ./redis-server ~/conf < <(echo "PING");
afl-showmap 1.94b by <lcamtuf@google.com>
[*] Executing './redis-server'...

-- Program output begins --
29193:M 20 Sep 18:39:05.230 * Increased maximum number of open files to 10032 (it was originally set to 1024).
             _._                                                  
        _.-``__ ''-._                                             
    _.-``    `.  `_.  ''-._           Redis 3.1.999 (846da5b2/1) 64 bit
.-`` .-```.  ```\/    _.,_ ''-._                                   
(    '      ,       .-`  | `,    )     Running in standalone mode
|`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
|    `-._   `._    /     _.-'    |     PID: 29193
`-._    `-._  `-./  _.-'    _.-'                                   
|`-._`-._    `-.__.-'    _.-'_.-'|                                  
|    `-._`-._        _.-'_.-'    |           http://redis.io        
`-._    `-._`-.__.-'_.-'    _.-'                                   
|`-._`-._    `-.__.-'    _.-'_.-'|                                  
|    `-._`-._        _.-'_.-'    |                                  
`-._    `-._`-.__.-'_.-'    _.-'                                   
    `-._    `-.__.-'    _.-'                                       
        `-._        _.-'                                           
            `-.__.-'                                               

29193:M 20 Sep 18:39:05.232 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
29193:M 20 Sep 18:39:05.232 # Server started, Redis version 3.1.999
29193:M 20 Sep 18:39:05.232 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
29193:M 20 Sep 18:39:05.232 * DB loaded from disk: 0.000 seconds
29193:M 20 Sep 18:39:05.232 * The server is now ready to accept connections on port 6379
-- Program output ends --
[+] Captured 3388 tuples in '/dev/null'.
#

Perfect, the Redis server read the input and immediately exited. AFL reports seeing 3388 tuples, which gives you a notion of the paths taken because of the input given. To be thorough though, let’s test this again, but with another command. If things are really working well, we should get a different number of captured tuples

# LD_PRELOAD=preeny/x86_64-linux-gnu/desock.so afl-showmap -m2048 -o/dev/null ./redis-server ~/conf < <(echo "SHUTDOWN");
afl-showmap 1.94b by <lcamtuf@google.com>
[*] Executing './redis-server'...

-- Program output begins --
2146:M 20 Sep 18:39:27.278 * Increased maximum number of open files to 10032 (it was originally set to 1024).
             _._                                                  
        _.-``__ ''-._                                             
    _.-``    `.  `_.  ''-._           Redis 3.1.999 (846da5b2/1) 64 bit
.-`` .-```.  ```\/    _.,_ ''-._                                   
(    '      ,       .-`  | `,    )     Running in standalone mode
|`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
|    `-._   `._    /     _.-'    |     PID: 2146
`-._    `-._  `-./  _.-'    _.-'                                   
|`-._`-._    `-.__.-'    _.-'_.-'|                                  
|    `-._`-._        _.-'_.-'    |           http://redis.io        
`-._    `-._`-.__.-'_.-'    _.-'                                   
|`-._`-._    `-.__.-'    _.-'_.-'|                                  
|    `-._`-._        _.-'_.-'    |                                  
`-._    `-._`-.__.-'_.-'    _.-'                                   
    `-._    `-.__.-'    _.-'                                       
        `-._        _.-'                                           
            `-.__.-'                                               

2146:M 20 Sep 18:39:27.280 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
2146:M 20 Sep 18:39:27.280 # Server started, Redis version 3.1.999
2146:M 20 Sep 18:39:27.280 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
2146:M 20 Sep 18:39:27.280 * DB loaded from disk: 0.000 seconds
2146:M 20 Sep 18:39:27.280 * The server is now ready to accept connections on port 6379
2146:M 20 Sep 18:39:27.327 # User requested shutdown...
2146:M 20 Sep 18:39:27.327 # Redis is now ready to exit, bye bye...
-- Program output ends --
[+] Captured 3356 tuples in '/dev/null'.
# 

Yep, we saw less paths taken with the SHUTDOWN command than we did the PING COMMAND. Things look good, Redis is ready for fuzzing.

Creating the dictionary

Now we just need to get the dictionary of Redis commands set up for AFL to consume.

# mkdir testcases syncdir dictionary && cd dictionary
# for i in `curl https://raw.githubusercontent.com/antirez/redis/unstable/src/server.c | grep Command, | sed 's/ //g' | grep -oP '{"(.*?)"' | sort | uniq | sed -e s/\"//g -e s/{//g`; do echo $i> `uuid`; done
# cd

The above for loop will parse out the supported commands from the server.c file and write them to individual files in the dictionary/ folder. The names of the files in the dictionary/ folder don’t matter, just the contents. Each file should hold a single token, and a token could be a binary byte, or an ASCII string like PING or SHUTDOWN.

Fuzzing Redis

Let’s fuzz some key/value stores. You will not likely get more than 30 or so executions per second, but across multiple fuzzer instances, this can be bearable.

# LD_PRELOAD=~/preeny/x86_64-linux-gnu/desock.so afl-fuzz -i ~/testcases/ -o ~/syncdir/ -x ~/dictionary/  -m2048 ./redis-server ./conf