If you saw my previous blog post on the buffer overflow I found in Left4Dead 2, you know that I found that vulnerability through fuzzing.
Modern game engines usually have a very large attack surface within which vulnerabilities could exist. They handle 3D rendering, audio, game logic, file parsing, networking, and much more. While it is possible to perform manual analysis to find vulnerabilities, fuzz testing (or “fuzzing”) is usually much faster and more convenient. Fuzzing is using a piece of software (a “fuzzer”) to send randomized inputs to a program and seeing what happens. If properly set up, a fuzzer can send thousands of inputs every second – exponentially faster than a human can. Sometimes those random inputs will uncover security vulnerabilities, which makes fuzzing a popular choice among those looking for vulnerabilities. In this post, I will go through the process of using a fuzzer to uncover vulnerabilities in a video game.
Fuzzing with CERT's BFF on Binary
The fuzzer we will be using is CERT’s Basic Fuzzing Framework (BFF). BFF is a Python-based “dumb” fuzzer that supports Windows, MacOS, and Linux environments. A dumb fuzzer is one that is not guided by any sort of intelligence or input model and generates inputs entirely randomly, which aptly describes BFF’s fuzzing methodology. BFF is very straightforward to setup and provides quite a few features to aid in the fuzzing effort.
Since we are using dumb fuzzing, no reverse engineering of the target binary is necessary to start fuzzing. This makes using BFF a good choice if you aren’t skilled at reverse engineering or do not wish to spend a lot of time doing it.
Utilizing Exploitability Ranking and Test Case Minimizer
As mentioned previously, BFF has a few useful features that heavily aid in the fuzzing process.
The first useful feature is the exploitability ranking. When a crash is detected, BFF will attempt to guess how exploitable the crash is and give it a numerical ranking. Crashes with a rank of 100 are the least exploitable and 5 is the most exploitable. While this ranking is prone to false positives, it helps with knowing what to start looking into first.
The second major useful feature is the test case minimizer. BFF will attempt to automatically isolate the section of input that is specifically causing the crash. This saves quite a bit of time, especially when dealing with larger inputs.
Trade-offs of Fuzzing Game Engines with BFF
The biggest downside to BFF is that it still can be a slow process, especially with game engines. BFF waits for the entire program to start up and process the input as opposed to testing just the part of the binary that is processing the input. This means that in some cases one fuzzing iteration every 5 seconds is as good as it will get.
In addition to being slow, it mutates inputs randomly – this means that code coverage is not as in-depth as other fuzzers.
Installing the BFF Tool
The process of installing BFF is straightforward. Get the installer for your platform from the CERT website, and then download and install it. It will automatically install Python and the required Python libraries for you, so that you’re ready to begin as soon as the installer is finished.
Configuring BFF and Getting Test Cases
A look at BFF’s configuration file that is configured for fuzzing Left4Dead 2.
BFF has a few configuration options that are useful when fuzzing. The configuration file for BFF, bff.yaml, is located inside the BFF install directory in the “configs” directory. This file includes comments that describe what each setting does.
An example of seedfiles that will be mutated during fuzzing.
You’ll also need some test cases – if for example you’re going to fuzz audio parsing in your game, you’ll need to take some audio files from the game and put them into BFF’s seedfiles directory which is defined in the configuration file.
Begin Running the Fuzzer
Once BFF is configured for your particular game and you have some test cases, run the bff.py Python script and the fuzzer will automatically start. After that, all that is needed is time – the longer BFF runs, the more opportunities it will have to find vulnerabilities.
BFF actively fuzzing a video game.
A video game typically processes a lot of different input and has a large attack surface. Things such as 3D models, audio, textures, and scripts are all parsed by the game’s engine and can be fuzzed. By fuzzing different types of inputs, more opportunities for vulnerabilities become available, as different parts of the code will handle different types of input.
Improving Fuzzing by Configuring for Speed
As previously mentioned, BFF is quite slow. That being said, there are multiple ways that it can be sped up, sometimes quite significantly.
The process watcher and run timeout options in BFF’s configuration file.
One way that it can be sped up is by disabling the process watcher and setting a default run timeout. This can be useful if the game does not crash after processing your input but continues to execute. In this case, you would not want BFF to wait for the process to idle but instead go ahead and close it. By configuring it this way, the process will automatically be closed after a user-defined number of seconds.
Another way that it can be sped up is by moving the game to a faster storage medium, such as a solid state drive (SSD). While it is not recommended to put BFF on an SSD, as it may speed up degradation of the SSD due to write amplification, having the game itself on the SSD will lead to a speedup in loading times which will lower the time of each fuzzing iteration.
Configuring the Game for Speed Improvements
It is also possible to configure the game itself to load faster to increase fuzzing speed. When I was fuzzing Left4Dead 2, one of the things that I did was use the developer-provided settings to disable all 3D rendering and audio. This led to a significant speedup as the game did not have to initialize anything related to 3D rendering or audio.
This largely depends on the particular game you are fuzzing, so you will need to research into whether or not things like this are possible. A good way to find extra configuration options is to use a search engine to find the game’s command line options – you might find some very useful options that aren’t available in the game’s normal settings menu.
Triaging the Crashes
After BFF has run for a while and accumulated crashes, now they will need to be triaged to determine whether or not they are a security vulnerability. This step of the fuzzing process will take the longest and is typically the most complex step.
BFF’s Exploitability Ranking and Crash Type
BFF automatically separates crashes into the following categories:
- Probably Exploitable
- Probably Not Exploitable
Crash categories used by BFF.
It uses a heuristic to automatically determine exploitability, and it is a good starting point for triage. I recommend starting with the crashes marked as exploitable and looking at their exploitability rank. As I stated previously, lower exploitability ranks mean that BFF has determined the crash is more exploitable. While this is not perfect and is prone to false positives, it is a good starting point.
Another thing to look for would be the type of crash. Something like a Data Execution Prevention (DEP) exception is a good crash type to look for, as it means that somewhere the processor tried to execute data. If you see one of these, it is a good indicator of a buffer overflow vulnerability. More research would need to be done to see if the crashing address is coming from user input. If it is, then an exploitable buffer overflow is present. Research can then be done to write an exploit.
Fuzzing can be one of the quickest ways to find vulnerabilities. Using BFF to fuzz is very straightforward to setup and requires no reverse engineering to get started with fuzzing. I personally have used it to find multiple vulnerabilities in the past with great success.
I encourage you to set up BFF and start fuzzing a video game yourself. You might come across some very interesting vulnerabilities, and you’ll learn a lot in the process if you’ve never done it before.
As always, feel free to open issues and pull requests on GitHub if you’d like and we will try to get to them to support future usage of these tools. Follow us on Twitter for more GCP releases and blog posts: @RhinoSecurity, @hun10sta.