International Association for Cryptologic Research

International Association
for Cryptologic Research

Transactions on Cryptographic Hardware and Embedded Systems, Volume 2024

JustSTART: How to Find an RSA Authentication Bypass on Xilinx UltraScale(+) with Fuzzing


README

ConFuzz FPGA Configuration Engine Fuzzing Framework

ConFuzz is an advanced FPGA configuration engine fuzzing and rapid prototyping framework based on boofuzz and OpenOCD.

The /JustSTART_paper folder contains the complete results related to our paper about ConFuzz and the JustSTART attack (CVE-2023-20570).
Further details about reproducing the results are in that folder's README.md.

Tested with

Hardware

Xilinx 7-Series:
https://www.xilinx.com/products/boards-and-kits/1-54wqge.html (basys3)

Xilinx UltraScale:
https://www.xilinx.com/products/boards-and-kits/kcu105.html (kcu105)

Xilinx UltraScale+:
https://www.xilinx.com/products/boards-and-kits/ek-u1-kcu116-g.html (kcu116)
https://opalkelly.com/products/xem8320/ (xem8320)

Software

Python 3.12.1
OpenOCD 0.12.0

Getting started

Installation

Install Python and OpenOCD, clone this repository and execute the following commands in the ConFuzz directory.

# Initialize our boofuzz fork as a submodule.
git submodule update --init

# Create and activate a virtual environment.
python -m venv env
. env/bin/activate

# Install boofuzz.
pip install -U pip setuptools
pip install ./boofuzz

# Install framework requirements.
pip install -r requirements.txt

Quickstart

Assuming the respective board is connected to the host, a fuzzer can be started by calling the main_fuzzer.py with the --board and --fuzzer-name arguments.

python main_fuzzer.py -b BOARD -fn FUZZER_NAME

All fuzzers are found in the /fuzzers directory, distinguished and named by their function name, which is always prefixed with fuzz_.
For example, the playground fuzzer can be a good starting point to explore:

python main_fuzzer.py -b basys3 -fn playground

Usage & Documentation

ConFuzz Overview

The idea behind ConFuzz is the following: The user selects a fuzzing strategy and a target FPGA to implement a concrete fuzzer ❶.
The implemented fuzzer uses a structure-aware mutator ❷ to generate modified test bitstreams automatically (see /fuzzers folder).
Then, each modified bitstream is handled by the hardware abstraction layer ❸ in order to program the FPGA and obtain information of the hardware state after configuration (/src/connections.py & /src/monitors.py).
Finally, the state is post-processed in an evaluator ❹, i.e., analyzed for crashes or deviated behavior (also in /src/monitors.py), and optionally displayed in a user interface ❺ for manual inspection (the link to the boofuzz web interface is shown after execution).

Section 3 of our paper describes this in depth, including several key challenges for FPGA configuration engine fuzzing and the system architecture, workflow, and fundamental components of ConFuzz.
Further, Section 5 gives three examples of how to use the framework in different ways.
These sections give a good introduction to working with ConFuzz.

The boofuzz documentation helps understanding the underlying boofuzz framework.
Additionally, our source code contains detailed comments and function descriptions explaining all fuzzers and ConFuzz functionality.

We reference official Xilinx documentation at certain points in our source code because knowledge of the FPGA architecture and configuration process is relevant to utilize the full potential of Confuzz.
References in the comments to UG470 refer to v1.16 (February 1, 2023).
References in the comments to UG570 refer to v1.17 (April 20, 2023).

Project structure

In the following, we briefly describe the high-level project structure.

/bitstreams folder

This folder contains .bit files for each board.
For example, original bitstreams generated with Vivado or bitsteams generated with the generate_bitstreams.py script.
These bitstreams can be used as seed for certain fuzzers (cf. FuzzedBitstream primitive in /src/primitives.py).
A README.md in the respective folders contains more details about the included example bitstreams.

/fuzzers folder

This folder contains the fuzzer implementations that can be loaded with the main_fuzzer.py (cf. Quickstart).

/register_settings folder

This folder contains configuration scripts containing the default crash settings (also called register settings) applied to each register.
Custom register settings .ini files can be placed in this folder and loaded with the --register-settings command line argument.
Note that this custom .ini file can overwrite the default crash settings while itself can be overwritten by register settings directly defined as a function argument in the fuzzer.

/src folder

This folder is the core of the framework.
- callbacks.py contains custom functions that can be hooked into the fuzzing process at certain points using the *_callbacks arguments of the boofuzz Session object.
- connections.py implements the connection with the target device (via OpenOCD).
- constants.py contains configuration values that are unlikely to change to be used across ConFuzz. Some of these values are board-dependent. Within this file, the configuration options are documented as comments.
- helpers.py contains helper functions for various parts of the framework.
- models.py contains all data classes defined for ConFuzz.
- monitors.py uses the established connection to the target from /src/connections.py to manage the target device. For example, the state of the FPGA is probed and checked for crashes.
- primitives.py contains bitstream building blocks to construct the bitstreams sent to the FPGA while fuzzing.

/static folder

This folder contains static files like test keys and OpenOCD configuration files.

Executables

While the main_fuzzer.py is the most important executable starting the fuzzing process, we implemented several supporting scripts to extend the functionality of ConFuzz.

main_fuzzer.py

This is the main script to start the fuzzing process.
Several command line options can be added.
Use -h or --help to get an explanation for all of them.

analyze_logs.py

This script can be used in combination with the log_transmitted_if_crashed crash setting to analyze the log files and search for bits that are identical in all transmitted values that led to a crash.

generate_bitstreams.py

This script generates a .bit bitstream file based on the defined bitstream within this script.
To define the bitstream, the bitstream building blocks implemented in /src/primitives.py and boofuzz primitives can be used.

cluster_fuzzer.py

This script splits up the test cases of a fuzzer over multiple boards.
We used this script to execute the test cases for our basys3 fuzzers on 15 boards in parallel.
However, minor modifications in this script are necessary depending on the hardware used (e.g., boards and USB hubs).

cleanup.sh

This is a small helper script to clean up the code base and keep the dependencies up to date.

Adding and configuring fuzzers

Section 3, 5, and especially Section 5.3 of our paper, are a helpful introduction to the fuzzing process based on ConFuzz.
In the following, we explain the implementation and configuration of a new fuzzer.
Note that besides our paper, our fuzzer implementations and comprehensively documented source code can be reviewed as an addition to this guide.

First, a function starting with fuzz_ has to be added to the respective fuzzer file in /fuzzers.

def fuzz_example_fuzzer(self, session_kwargs: dict = {}):

As arguments self and session_kwargs are passed.
This function is always called with these arguments from the MainFuzzer class in the main_fuzzer.py.
The session_kwargs dictionary contains default parameters defined in the MainFuzzer class.
Some of these arguments are fixed in the MainFuzzer, and others are derived from command line arguments.

There exist four main sections in a fuzzer: the session, the crash settings, a target, and the bitstream definition.

Session

A session is initialized using a boofuzz Session object we forked and extended for ConFuzz.
By default, only a minimal definition is required:

session = Session(**session_kwargs)

However, all arguments from the boofuzz Session object can be passed as arguments or overwritten in the session_kwargs for an individual fuzzer.

Crash settings

Next, crash settings (also called register settings) can be defined.
The high-level functionality is described in the paper and the default_register_settings.ini contains all available register settings and their explanation.
They can be omitted entirely, but usually, it is necessary to define custom register settings for each fuzzer.

Target

This is a minimal example of adding a target and optionally defined custom register settings to the previously defined session.

session.add_target(
    self._get_target(
  session._fuzz_data_logger,
  custom_register_settings=custom_register_settings,
    )
)

If necessary, additional arguments to configure the target can be looked up in the main_fuzzer.py.
These arguments configure the OpenOCDConnection in /src/connections.py and OpenOCDMonitor in /src/monitors.py.

Bitstream definition

The bitstream sent to the target device can be defined using our bitstream building blocks implemented in /src/primitives.py and boofuzz primitives.
Please refer to our implemented fuzzers as examples.

Finalization

Finally, the previously defined bitstream must be connected with the session, and the fuzz function of boofuzz is called.
self._test_case_name is passed as an argument to handle the --test-case-name command line argument, which executes only a single test case.

session.connect(bitstream_definition)

session.fuzz(self._test_case_name)

Once finished, the fuzzer can be started by passing example_fuzzer as --fuzzer-name to the main_fuzzer.py.

Steps to add a new development board

License

This repository is distributed under the MIT License. See LICENCE for more information.

Contact

Please feel free to contact us using GitHub's discussions feature.

BibTeX

To cite this work, please use the reference below:

@article{Ender_Hahn_Fyrbiak_Moradi_Paar_2024,
    title={JustSTART: How to Find an RSA Authentication Bypass on Xilinx UltraScale(+) with Fuzzing},
    volume={2024},
    url={https://tches.iacr.org/index.php/TCHES/article/view/11435},
    DOI={10.46586/tches.v2024.i2.426-450},
    abstractNote={<p>Fuzzing is a well-established technique in the software domain to uncover bugs and vulnerabilities. Yet, applications of fuzzing for security vulnerabilities in hardware systems are scarce, as principal reasons are requirements for design information access, i.e., HDL source code. Moreover, observation of internal hardware state during runtime is typically an ineffective information source, as its documentation is often not publicly available. In addition, such observation during runtime is also inefficient due to bandwidth-limited analysis interfaces, i.e., JTAG, and minimal introspection of hardware-internal modules.<br>In this work, we investigate fuzzing for Xilinx 7-Series and UltraScale(+) FPGA configuration engines, the control plane governing the (secure) bitstream configuration within the FPGA. Our goal is to examine the effectiveness of fuzzing to analyze and document the opaque inner workings of FPGA configuration engines, with a primary emphasis on identifying security vulnerabilities. Using only the publicly available hardware chip and dispersed documentation, we first design and implement ConFuzz, an advanced FPGA configuration engine fuzzing and rapid prototyping framework. Based on our detailed understanding of the bitstream file format, we then systematically define 3 novel key fuzzing strategies for Xilinx FPGA configuration engines. Moreover, our strategies are executed through mutational structure-aware fuzzers and incorporate various novel custom-tailored, FPGA-specific optimizations to reduce search space. Our evaluation reveals previously undocumented behavior within the configuration engine, including critical findings such as system crashes leading to unresponsive states of the whole FPGA. In addition, our investigations not only lead to the rediscovery of the recent starbleed attack but also uncover a novel unpatchable vulnerability, denoted as JustSTART (CVE-2023-20570), capable of circumventing RSA authentication for Xilinx UltraScale(+). Note that we also discuss effective countermeasures by secure FPGA settings to prevent aforementioned attacks.</p>},
    number={2},
    journal={IACR Transactions on Cryptographic Hardware and Embedded Systems},
    author={Ender, Maik and Hahn, Felix and Fyrbiak, Marc and Moradi, Amir and Paar, Christof},
    year={2024},
    month={Mar.},
    pages={426–450}
}