Fuzzing NetHack
After NetHack 3.6.4, I looked at other games including Freeciv and Dungeon Crawl Stone Soup. Freeciv uses a client/server model, and I really wanted to find a vulnerability in the server protocol. I discovered AFL and setup the fuzzer to run against the Freeciv server. Days later, AFL had found nothing and I was bored, so I decided to run AFL against NetHack. That ended up being a very good idea.
If you want to use AFL to fuzz NetHack, start by familiarizing yourself with AFL first here. I used the AFL++ fork as it is still actively developed and taking bug fixes. Then clone my git repot to use the patches and scripts I describe below.
After recompiling NetHack with AFL_HARDEN=1 afl-clang-fast as described in the documentation, I was able to start the fuzzer using the afl-go script.
To perform argv fuzzing, apply NetHack-AFL/argv/argv.patch on top of afl.patch applied previously. This patch makes the following changes:
If you want to use AFL to fuzz NetHack, start by familiarizing yourself with AFL first here. I used the AFL++ fork as it is still actively developed and taking bug fixes. Then clone my git repot to use the patches and scripts I describe below.
.nethackrc fuzzing
Start by applying NetHack-AFL/nethackrc/afl.patch to your NetHack-3.7 tree. This patch makes the following changes:- AFL requires that the input under test be provided as a filename on the program’s command line. In this case, we want the input to be the .nethackrc file, and I could not find a way to provide this as a command line option. I added a new -z option which overrides the default .nethackrc location when it is present. For this to work, I had to move the call to process_options(argc, argv) ahead of initoptions. Another oddity is that the user must specify an absolute path for the .nethackrc file, as nethack will change to NETHACKDIR upon launch.
- Moving process_options earlier can result in a call to askname in some cases. This would block waiting for user input, so just remove this code.
- Because we are only interested in the parsing of .nethackrc, it is not necessary to run any additional code after that point, so add a call to exit(0) after initoptions.
- We don’t want NetHack to hang when .nethackrc has an error (as it inevitably will for most of the fuzzed input files). This can be changed by removing the call to xwaitforspace in getret.
Next, AFL requires an initial input file to start the fuzzing process, the shorter the better. I made a simple OPTIONS=!number_pad file and placed it in NetHack-AFL/nethackrc/in/nethackrc.
Because .nethackrc uses many pre-defined keys and values, AFL also benefits from a user dictionary. To generate this dictionary, I ran strip(1) and then strings(1) on options.o and files.o, removed obvious non-tokens, and consolidated the result into NetHack-AFL/nethackrc/nethack.str. I then ran todict against nethack.str to generate nethack.dict.
After recompiling NetHack with AFL_HARDEN=1 afl-clang-fast as described in the documentation, I was able to start the fuzzer using the afl-go script.
argv fuzzing
In addition to .nethackrc files which are potentially remotely exploitable on public NetHack servers, NetHack accepts a variety of command line options which are potentially locally exploitable for setuid/setgid privilege escalation. NetHack is commonly installed setgid to group games for save and bones files. argv fuzzing attacks the nethack command line to look for vulnerabilities. Note that this includes argv[0], which can be controlled by symlinking nethack.To perform argv fuzzing, apply NetHack-AFL/argv/argv.patch on top of afl.patch applied previously. This patch makes the following changes:
- Use the AFL argv-fuzz-inl.h header file to copy stdin to argv.
- Disable the -z option from .nethackrc parsing as this leads to false positives.
- Avoid a false positive tty crash I encountered.
The rest of the files in NetHack-AFL/argv work similarly to their /nethackrc counterparts.
Results
AFL fuzzing was highly effective at finding security vulnerabilities and other bugs in NetHack. Many of these were subsequently fixed in NetHack 3.6.5.
Comments
Post a Comment