This is a black box fuzzing solution targeting the OPC UA protocol. The fuzzing is based on the mutational fuzzing engine boofuzz.
We contributed to boofuzz and provide a fork, that further supports json as crash format. This allows for an automated collection of fuzzing results and makes the crash triage easier.
To seamlessly support multiple target implementations and allow for scaling, the fuzzing takes place inside a container. A simple python script is used to choose a target. The script then handles building the container and fuzzing the target. The process consists of the following steps:
- Choose target implementation.
- [Automated] Build container with target implementation and fuzzing tools.
- [Automated] Run fuzzing on target implementation.
- [Automated] Collect data from fuzzing and store crash information.
- [Automated] Try to reproduce each crash.
- [Automated] Transfer crash information, including reproducability, to host.
- Review crash information, stored in json file.
So by using the container (powered by docker), most fuzzing steps are automated.
The project includes an easy to use wrapper called run_docker_fuzzing.py
.
$ python3 blackbox-opcua-fuzzing/run_docker_fuzzing.py --help
usage: run_docker_fuzzing.py [-h] [-p PATH] {node-opc,open62541,python-opcua,dotnet,java}
Fuzz OPC UA
positional arguments:
{node-opc,open62541,python-opcua,dotnet,java}
Target implementation
optional arguments:
-h, --help show this help message and exit
-p PATH, --path PATH Path for output / results (Default: ./fuzzing_results)
Requirements: The run_docker_fuzzing.py only depends on docker-py. Install with pip install docker
.
As of now, there are five supported OPC UA implementations (all are open source):
The complete result folder has a structure such as
$ tree fuzzing_results
fuzzing_results
├── boofuzz-crash-bin-2020-08-07T08-00-58
├── boofuzz-results
│ └── run-2020-08-07T08-00-58.db
└── crash_info_2020-08-07T08-00-58.json
1 directory, 3 files
where the crash_info_*.json
file holds the comprehensive results.
The fuzzing toolchain incorporates a mechanism to replay crashes.
This mechanism is also provided as a standalone script called reproduce_crashes.py
.
This script can replay or reproduce crashes based on three methods: Using the crash log from the blackbox fuzzing, given a single message as hexstring and given the id of a known crash.
Known crashes are kept in the test_cases.json
file.
It lists crashes that have been produced during development of this toolchain.
To correctly reproduce the crashes, the script needs to know at which state the crashing message occurs. E.g. a broken Hello message does not need any initialization, while a broken discovery service message usually needs a successful Hello/Acknowledgment and a OpenChannel handshake. Furthermore all messages sent on an opened channel need to use the correct channel parameter. To this point, the script infers the channel parameter from the previous paket and mutates the broken message to use them correctly. This can be done since the fuzzing approach does not mutate the first 6 parameter of pakets on handshake level 2 or beyond.
The script has the following interface:
python3 reproduce_crashes.py --help
usage: reproduce_crashes.py [-h] (-p PID | -t TARGET) (-m MESSAGE | -f FILE | -c CASE) [-d DEPTH]
optional arguments:
-h, --help show this help message and exit
-p PID, --pid PID PID to check if server is alive
-t TARGET, --target TARGET
Target to run messages against
-m MESSAGE, --message MESSAGE
Package to send as hexstring
-f FILE, --file FILE JSON crash log to collect messages from
-c CASE, --case CASE Replay known Testcase from test_cases.json. Options: [711, 713, 714, 844, 846, 7896]
-d DEPTH, --depth DEPTH
Set Connection Level (0 - No Connection, 1 - Hello, 2 - Open Channel, 3 - Create Session)
The depth has to be given in combination with --message
. The other two replay options (log, known case) known the correct depth from their data.
Independent of the replay method, the script does not provide a server.
The server can either be given as a file path with --target
(note that this does not allow for comman line arguments) or if running as a process id with --pid
.
The script does not have any external dependencies.
You can add more fuzzing definitions in the fuzzer/boofuzz_definition.py
file. The file currently includes definitions for:
- Hello
- OpenChannel
- CloseChannel
- FindServersRequest
- FindServersOnNetworkRequest
- GetEndpointsRequest
- RegisterServer2Request
- CreateSessionRequest
- ActivateSessionRequest
Some more helpful functions are included to e.g. parse channel parameter from previous requests and construct timestamps.
It is also possible to add more target implementations. The steps to do that are listed here.
This project is financed by German Federal Office for Information Security (BSI).
Firmware Analysis and Comparison Tool (FACT)
Copyright (C) 2020-2021 Fraunhofer FKIE
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.