We suggest you create a fork of this repo so that you can begin to develop
your solution to the eCTF. To do this, you must fork the repo, change your
fork to the origin
, and then add the example repo as another remote.
Follow these steps below.
- Clone the eCTF repository using ssh or https
git clone https://github.com/mitre-cyber-academy/2022-ectf-insecure-example --recursive
- Change the current origin remote to another name
git remote rename origin example
-
Fork the example repo on github or create a repository on another hosting service. You probably want to make the repo private for now so that other teams cannot borrow your development ideas
-
Add the new repository as the new origin
git remote add origin <url>
You can now fetch and push as you normally would using git fetch origin
and
git push origin
. If we push out updated code, you can fetch this new code
using git fetch example
.
The development servers provided for you have been set up with all the necessary packages for building and running the SAFFIRe system with Docker. When running this code for the first time, you must pull the newest version of the eCTF custom QEMU (emulator) for use in the challenge. Run the following to get the appropriate emulator image:
docker pull ectf/ectf-qemu:tiva
To run the system on your local machine for either the physical or emulated
device, you must install Docker and Python. Since the Docker image containing
the Tiva C emulator is only built for the arm64
architecture, we do not
guarantee that you can run the emulated system on your own local machine for VM.
Windows users will install Docker Desktop on Windows following the instructions on this page. We recommend using the WSL2 backend. Mac users will install Docker Desktop on Mac following the instructions on this page.
Once you download and run the installer, you should start Docker Desktop if it does not start automatically and agree to the terms. Note: Docker Desktop requires that anyone using Docker Desktop as a part of their work in a large organization needs to buy a license. This should not apply to any participants since you are using it for educational and personal purposes.
Once you have Docker Desktop installed, you will have to run it in the
background when running SAFFIRE. To be able to use Docker when running the
system, make sure that you have a command line environment that can run Docker
commands. This should be automatically set up in your PATH
environment
variable, so running the SAFFIRe commands should work in Windows Command Prompt
and Powershell.
Linux uses will install Docker Engine following the instructions on
this page. Click on the link for your
Linux distribution and you will be brought to the specific instructions for
installing docker on your computer. Be sure to follow the
post-installation instructions
to set up your user with privileges to run Docker containers. This page also
contains instructions on how to start Docker engine automatically at startup,
but you can start it per-session with sudo service docker start
.
There are a number of ways to install Python 3 on your computer. Mac and Windows users can install it directly from [https://www.python.org/downloads/], and Linux users can install it through their system package manager. There are lots of tutorials online for how to install Python 3. Another option is to use https://www.anaconda.com/products/individual or https://docs.conda.io/en/latest/miniconda.html, which are Python distribution manager thats include multiple versions of python and an easy way to set up different environments and install compatiable packages together.
To debug the physical microcontroller you need to install Texas Instruments Code Composer Studio (CCS). Download the installer from this page (scroll down to "Downloads", click "Download options" for CCSTUDIO, then click the "single file installer" for your OS). This will download as a compressed archive, which you must extract and navigate into to find the installer.
When installing, you must accept the license agreement and then go through a system check. If you have recently installed other programs that require a restart, the CCS installer will suggest that you restart your computer. Additionally, CCS may have package dependencies on Linux, which you can find on this page. Then, confirm the installation folder. We recommend that you keep the default. Then, proceed with "Custom Installation".
You only need to enable support for the TM4C12x ARM Cortex-M4F core-based MCUs,
and click "next". Make sure the Segger J-Link debug probe is selected in the the
debug probes list and click "next". Then click "next" until the installation
starts. At the end of installation on Linux: you may have to run the USB cable
drivers with sudo sh <path-to-ccs1110/ccs/install_scripts/install_drivers.sh
.
After installing Python 3 you will need to install the pyserial
package. See
the documentation for the specific Python 3 installation you set up on how to
install this package.
To launch the example SAFFIRe system (and earn the Boot Reference flag),
follow these steps. Note: you can change the values of these arguments.
Since all team members log in on the same user, you may end up interfering with
each other's containers. To avoid that, use different --sysname
and
--uart-sock
values. We recommend each team member choosing a unique socket for
--uart-sock
for the duration of the design phase.
These steps should work on the development servers we provide out of the box.
If you would like to use the physical hardware, you must follow the environment
set-up instructions and use the --physical
flag in place of --emulated
(NOTE: the physical hardware is not supported in the initial code release).
Please refer to the technical specifications document for explanations of the commands and arguments.
A deployment (host-tools + bootloader) is created using:
python3 tools/run_saffire.py build-system --emulated \
--sysname saffire-test \
--oldest-allowed-version 1
This will create two Docker images based on the host-tools and bootloader in the repo:
- saffire-test/host_tools
- saffire-test/bootloader
The bootloader must first be loaded:
python3 tools/run_saffire.py load-device --emulated --sysname saffire-test
Then, a directory should be made for the sockets, after which the bootloader can be launched:
mkdir socks
python3 tools/run_saffire.py launch-bootloader --emulated \
--sysname saffire-test \
--sock-root socks/ \
--uart-sock 1337
There are two alternative variants to this step, controlled by replacing
the launch-bootloader
argument.
To connect GDB to the bootloader, use launch-bootloader-gdb
. This will
start QEMU up, pausing at the first instruction until GDB is connected. The command
will print the GDB command to use to connect to the system. NOTE: The bootloader
will not run in this mode until GDB has been attached and starts the bootloader.
If you run with GDB, you will have to open another terminal to run the host tools.
Alternatively, to see the output of the Docker container running QEMU, use
launch-bootloader-interractive
. One case where this is helpful is if you are
unsure whether the host tools or GDB is connecting to the bootloader. Info messages
are printed in the bootloader container when processes connect and disconnect.
If you run in interactive mode, you will have to open another terminal to run
the host tools.
We now need to protect the firmware and configuration files. This can be done with the following commands:
python3 tools/run_saffire.py fw-protect \
--sysname saffire-test \
--fw-root firmware/ \
--raw-fw-file example_fw.bin \
--protected-fw-file example_fw.prot \
--fw-version 2 \
--fw-message 'hello world'
python3 tools/run_saffire.py cfg-protect \
--sysname saffire-test \
--cfg-root configuration/ \
--raw-cfg-file example_cfg.bin \
--protected-cfg-file example_cfg.prot
The release message passed to fw_protect
must be formatted as shown above, as
the quotation marks are required for passing the full string into the protect
tool as one argument. The escaped quotation marks '' are there for that purpose.
Now that we have protected firmware and configuration images, we can load them onto the device with the following commands:
python3 tools/run_saffire.py fw-update \
--sysname saffire-test \
--fw-root firmware/ \
--uart-sock 1337 \
--protected-fw-file example_fw.prot
python3 tools/run_saffire.py cfg-load \
--sysname saffire-test \
--cfg-root configuration/ \
--uart-sock 1337 \
--protected-cfg-file example_cfg.prot
After this, the bootloader should now be ready to handle readback and boot commands.
With firmware and configurations loaded onto the bootloader, we can now use the readback functionality with the following commands:
python3 tools/run_saffire.py fw-readback \
--sysname saffire-test \
--uart-sock 1337 \
--rb-len 100
python3 tools/run_saffire.py cfg-readback \
--sysname saffire-test \
--uart-sock 1337 \
--rb-len 100
You may use a tool like xxd to verify that the output of the readback tool matches the unprotected firmware.
With firmware and configurations loaded onto the bootloader, we can now boot the device:
python3 tools/run_saffire.py boot \
--sysname saffire-test \
--uart-sock 1337 \
--boot-msg-file boot.txt
python3 tools/run_saffire.py monitor \
--sysname saffire-test \
--uart-sock 1337 \
--boot-msg-file boot.txt
This first command tells the SAFFIRe bootloader to launch the firmware, printing
the boot message to the file boot.txt
(which remains in the Docker container).
If this is successful and a release message was printed, the second command will
launch and monitor the output of the rest of the airplane, simulating a flight.
IF EVERYTHING WORKS PROPERLY, CHECK THE OUTPUT OF THE MONITOR COMMAND FOR A FLAG
Any time you want to perform a soft reset on the microcontroller to restart the bootloader, you can do so with:
python3 tools/emulator_reset.py --restart-sock socks/restart.sock
If you specified a different socket root with --sock-root
when running
launch-bootloader
, make sure to replace socks/restart.sock
with the correct
path when running the reset tool.
When you're done with the bootloader or would just like to rebuild, you can do so with:
python3 tools/run_saffire.py kill-system --emulated --sysname saffire-test
Running SAFFIRe on the physical device uses the same run_saffire.py
commands
as the emulated system, with a few additional options. Follow these steps to
build the system, load the device, and start the bootloader. Note: If you are
using an Anaconda or Miniconda-based Python 3 installation, you may have to use
python
instead of python3
while running these commands.
Create the system using:
python3 tools/run_saffire.py build-system --physical \
--sysname saffire-test \
--oldest-allowed-version 1
This will create two Docker images based on the host-tools and bootloader in the repo:
- saffire-test/host_tools
- saffire-test/bootloader
In order to automatically load the bootloader on to the physical device, you
must first install the MITRE bootstrapper provided in the platform
folder.
Start Code Composer Studio, which will ask you for a folder to set up a
workspace in. You can use the default option or provide your own; the workspace
will only be used to store debug and program load configuration files.
Once the workspace opens, go to File -> New -> Target Configuration
to set up
a new board configuration. Rename it to TivaConfiguration.ccxml
, and store it in your
workspace folder (uncheck the shared location box and enter your workspace
folder). After creating the file, an editor will open with a few device options.
Select "Stellaris In-Circuit Debug Interface" for the connection type, and "Tiva
TM4C123GH6PM" as the device. Then click "Save".
Next, go to Run -> Debug Configurations
. Select "Code Composer Studio - Device
Debugging" on the left panel, and then click the "New Launch Configuration"
button above the left panel. In the main panel that opens, change the "Name" to
bootstrapper_load
, and choose the TivaConfiguration.ccxml
file as the
"Target Configuration"; you will have to navigate to it through the "File
System" button. Then, in the "Program" tab, edit the "Program" field to point to
<ectf-repo-root/platform/bootstrapper.elf
, and make sure the "Loading
options" are set to "Load program".
In the "Target" tab, under the "Auto Run and Launch Options" category, check the boxes for "Halt the target before any debugger access" and "Connect to the target on debugger startup". Click the "Apply" button to save all these settings. Finally, plug in the board, turn it on with the top switch, and click "Debug". This will open up a debugger window and install the bootstrapper on the board. You can then click the red square "Stop" button near the top of the screen to exit the debug window.
If you ever need to re-install the bootstrapper for some reason (maybe you
corrupt the Flash memory containing the bootstrapper), you can plug in the
board, and go to Run -> Load -> bootstrapper_load
. This will briefly open a
debug window, install the bootstrapper, and then close the debug view. Remember,
this step does not have to be run every time. As long as you have the
bootstrapper installed on the board, you can proceed directly from
build-system
to load-device
.
To load your SAFFIRe bootloader into the device, plug in the microcontroller, turn it OFF, and run the load step:
python3 tools/run_saffire.py load-device --physical --sysname saffire-test
This will start the bootstrap loader which looks for a serial port to open. When
the tool prints a message to turn on the device, turn it on and wait for the
load process to complete. While loading, the bootstrap loader will print out the
name of the serial port it connected to, which you will need for the
launch-bootloader
step. Take note of this and keep it for future use, since
the same device should have the same serial port name each time you plug it in.
If you shutdown and restart the device, you do not need to load the bootloader
again; the load-device
step is only necessary when building a new bootlaoder.
At power-up, the bootstrapper will check if an update is requested and then
start the SAFFIRe bootloader, which is indicated by a blue LED turning on.
This step will also automatically copy SAFFIRe bootloader ELF to your local
files so you can load debug symbols into the CCS debugger. With the above
invocation it will be called saffire-test-bootloader.elf.deleteme
.
You can use CCS to debug the bootloader running on the physical device; this debugger is particularly useful because of its graphical interface and easy access into all the system registers. The first time you debug on the physical device, you need to create another debug configuration that loads the bootloader binary symbols, but does not load the program itself (that is accomplished by the bootstrapper).
Once again, go to Run -> Debug Configurations
, click on "Code Composer
Studio - Device Debugging" on the left panel and click "New Launch
Configuration". Call this one saffire_debug
and set TivaConfiguration.ccxml
as the target configuration. In the "Program" tab, set the "Program" to point
to the saffire-test-bootloader.elf.deleteme
; Note: You will have to make all
file types visible as the default file selector window will filter out the file
due to the .deleteme
. Then, set "Load symbols only" as the "Loading options".
In the "Target" tab, under the "Auto Run and Launch Options" category, check the "Halt the target before any debugger access" and "Connect to the target on debugger startup" boxes. You may set other options on this tab as you wish.
Finally, in the "Source" tab click the "Add..." button, select "File System
Directory" as the type, click "OK", set the target directory to
<ectf-repo-root>/bootloader
, and check the "Search subfolders" box. This will
allow the debugger to find all the source files referenced in the bootloader ELF
file. Click "OK" to exit the window, click "Apply" in the main configuration
window, and click "Debug" to start a debug session. During this first time setup
you can instantly close the debug view with the square red "stop" button.
In the future, when the device is running, you can start the debugger by
clicking the dropdown arrow next to the bug icon at the top of the CCS window
and select saffire_debug
to start the debugger.
Since the bootstrapper launches the SAFFIRe bootloader automatically, the
launch-bootloader
step for the physical device flow starts a bridge between
the device serial port and the UART socket that the host tools connect to. This
bridge will take up a terminal, so make sure to open another one to run any host
tools. Using the serial port printed out by the bootstrap loader, run this
command:
mkdir socks
python3 tools/run_saffire.py launch-bootloader --physical \
--sysname saffire-test \
--uart-sock 1337 \
--serial-port <device_serial_port_name>
The rest of the SAFFIRe steps are run using the exact same commands as shown in
the earlier emulated device flow. You can run them in a new terminal and they
will interact with the device over the bootloader bridge started in the previous
step. Note: tools/emulator_reset.py
will not work on the physical device. To
soft reset the bootloader, press button SW2 on the development board.
We recommend creating scripts to run multiple SAFFIRe commands in a row. Two
useful scripts to create could be called run_setup.sh
that runs the
kill-system
, build-system
, load-device
, and launch-bootloader
steps, and
run_tools.sh
that runs the rest of the steps. This will make it easy to run
the host tools alongside a bootloader that has been launched in GDB or interactive
mode.
tools/run_saffire.py
is set up to optionally read arguments from a file. The
arguments used in the above example commands are replicated in the example
arguments file saffire.cfg
. For example, to run the same launch-bootloader
shown above using the argument file, run the following:
python3 tools/run_saffire.py launch-bootloader @saffire.cfg --emulated
Using an argument file makes it easy to re-use the same arguments for multiple commands to reduce typing and typos. You can create multiple argument files and use them for different configurations and testing.
Finally, if you want to override an argument specified in an argument file you can specify the argument at the end of the command. For example, to run the following example but with a different socket number, use the following:
python3 tools/run_saffire.py launch-bootloader @saffire.cfg --uart-sock=1338
By using the launch-bootloader-gdb
command, you can easily attach GDB
to the bootloader. To see the output of the host tools in real time, you will
have to run the debugger and host tools in separate windows.
Note: By default GDB will not find the bootloader source files. Once inside a GDB session, run the following to point GDB to the sources:
(gdb) directory bootloader
Now, when you break or inspect the code, you will be able to view the C source corresponding to the disassembly
Quick GDB Reference:
- Set a function breakpoint:
b <function_name>
- Set a breakpoint at an address:
b *0x<memory_address>
- View memory location:
x 0x<memory address>
- Continue execution:
c
orcontinue
- Step one instruction:
si
- View CPU registers:
info registers
ori r
- Disassemble current context:
disas
- Disassemble arbitrary location:
disas 0x<memory_address>, +0x<number_of_locations_to_show>
- Switch the layout to view the assembly:
layout asm
- Quit GDB:
quit
-- you may have to hity
and press enter if it asks you confirm the exit- If the debugger is running and it is not hitting breakpoints Hit
CTRL-C
first to pause
- If the debugger is running and it is not hitting breakpoints Hit
There are plenty of resources online for how to use GDB, and your teammates and advisors can likely give you tips as well.
To view all running Docker containers:
docker ps
To kill the Docker container with process ID 12345:
docker kill 12345
To kill all Docker containers (be aware not to kill the containers of others on the server):
docker kill $(docker ps -q)
You can streamline this by adding alias dockerka='docker kill $(docker ps -q)'
to your .bashrc
.
To run a command in the Docker container test:deployment
:
docker run test:deployment echo "this echo command will be run in the container"
Docker can chew up disk space, so if you need to reclaim some, first clean up unused containers and volumes
docker container prune
docker volume prune
If that isn't enough, you can clean up all containers and volumes:
docker container prune -a
docker volume prune -a
NOTE: these will remove all of the cached containers, so the next builds may take a longer time
The example code is set up to clean up the system state when running kill-system
.
But, when you are developing and things break, these are some helpful commands
to have handy.
- Kill all docker containers:
docker kill $(docker ps -q)
- Remove all sockets:
rm -rf socks/*
- Kill the process in your window:
CTRL-C
- Suspend the process in your window:
CTRL-Z
- Note: Make sure to kill the process after!