Skip to main content Link Menu Expand (external link) Document Search Copy Copied

CTF of Physical Attacks

Exploiting an insecure memcmp implementation on a real microcontroller to leak a secret.

esp32 picture

Image: Adafruit

Table of contents

Introduction

In this recitation, we will attack an ESP32 chip. ESP32 is a Wifi and Bluetooth enabled microcontroller unit (or called MCU) built on the Xtensa architecture. We will be using two of them today. One is the victim (a password checker from which we will leak the correct password), and the other is the attacker board (which you fully control). You will be writing an ESP32 program on the attacker board to leak the secret from the victim by exploiting a timing side channel.

To participate in the CTF, you will bring:

TA will bring:

  • 2x ESP32 boards, one programmed with the victim code, and one for the students to write attacker code on
  • 2x USB-A to USB-Micro B cables
  • A bundle of jumpers for students to use as needed

All attacks are fair game

  • You can do whatever you want to these boards.
  • Please try to leave them at least functional though for next year.
  • Our intended bug is probably the easiest way to exploit them, but feel free to do whatever and explore!

Prizes

The first 3 teams to finish will get 1 ESP32 board per group member (the same kind we used in class today).

Before Class: Software Development Environment Setup

You need to install the appropriate board programmer files to your local PC to program the attacker microcontroller with your attack. We are using this esp32 board. It is identified in Arduino as an esp32/Node32s.

Follow the installation procedure described here. A brief summary of what you need to do:

  1. Download the Arduino IDE 2.0.4
  2. Open Preferences
  3. Add https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json to Additional Boards Manager URLs
  4. Close and reopen the Arduino app
  5. Open Boards Manager from Tools > Board > Board Manager…
  6. Search esp32
  7. Install esp32 Espressif Systems
  8. Close and Reopen the Arduino app
  9. Make sure Tools > Board is set to ESP32 Arduino/Node32s

Note

For windows users, you need to also install and configure PuTTY and the FTDI Drivers as seen here.

In Class: Hardware Setup

As a physical attack recitation, you will need to interact with hardware devices physically. Read below to understand how we communicate with the ESP32 chip using UART, and follow the instructions to wire things up.

What is UART?

How does the esp32 board communicate with the external world? Using the serial port. Serial, or UART, is an asynchronous protocol commonly used for communicating data between devices. Many consumer devices ship with exposed UART pads that transmit and receive data, which can cause many physical security vulnerabilities.

Serial consists of 3 pins: RX, TX, and ground. Ground is the same for both devices (as a general rule of thumb, ground is always common amongst everything in a system). One device’s RX connects to the other’s TX, and vice versa. RX is used for receiving, and TX is used for transmitting. See the figure below.

The most important parameter for a serial port is the baud rate, i.e., the speed at which serial communication runs. Our boards are configured to use the standard baud rate of 115200.

Test the Communication with ESP32

Let’s first test the victim’s connection with our PC to make sure everything’s working. You can attach an ESP32 to your PC using a USB cable. See detailed information here.

The first step is to find the port name on your computer.

  1. Open the Arduino app with no boards connected.
  2. Go to Tools > Port and look at the available ports.
    • On macOS / Linux you will see something like /dev/cu.Bluetooth-Incoming-Port (or maybe nothing).
    • On Windows you will probably see nothing, maybe a few COMX ports (X is a number).
  3. Plug in the victim board and reload Tools > Port and look for the new port.
    • On macOS / Linux, it will be a dev file like /dev/cu.usbserial-0001/.
    • On Windows, it will be something like COM4.
  4. Make note of the port name so we can connect to it later.

Now you can see what the victim boar is doing!

  • If you’re on macOS / Linux, we can use the screen command to talk to the victim.
    • screen {name from above} 115200 will give you a terminal.
    • Press control + A, then K, and then Y when you want to exit.
  • If you’re on Windows, follow the PuTTY instructions.

At first your terminal will be blank. Press the EN button on the board to reset it. You should now see some terminal output.

                              ____    ____  _____  _________             
                             |_   \  /   _||_   _||  _   _  |            
                   ______      |   \/   |    | |  |_/ | | \_|    ______  
                  |______|     | |\  /| |    | |      | |       |______| 
                              _| |_\/_| |_  _| |_    _| |_               
                             |_____||_____||_____|  |_____|              
                                                                         
  ___                        _  _             _                       ___         _
 / __| ___ __ _  _ _ _ ___  | || |__ _ _ _ __| |_ __ ____ _ _ _ ___  |   \ ___ __(_)__ _ _ _
 \__ \/ -_) _| || | '_/ -_) | __ / _` | '_/ _` \ V  V / _` | '_/ -_) | |) / -_|_-< / _` | ' \
 |___/\___\__|\_,_|_| \___| |_||_\__,_|_| \__,_|\_/\_/\__,_|_| \___| |___/\___/__/_\__, |_||_|
                                                                                   |___/
Ravi's Super-Secure Password Checker(TM)
Enter your Password >

You can type in guessed passwords, and it will tell you if you are correct or not. You should see the light flash and a result printed to the console. Here’s an example if the correct password is MIT{this_is_a_secret}:

Enter your Password > MIT{incorrect_guess}
Try again.
Enter your Password > MIT{this_is_a_secret}
Correct!
Enter your Password >

Wire Up the Attack Setup

In our attack, we will use another ESP32 board as the attack board to interact with the victim board and monitor the victim’s timing behaviors. Here’s a diagram of the setup we will build:

We are going to attach the attacker and victim together via their secondary serial ports. Both MCUs can also attach to your laptop via USB, to power them up and to see the results of the attack. We will also attach the LED pin of the victim to the attacker so it can measure when the light turns on (more information later). The precise connnections to make depend on which type of the board you get, as follows. (Two types of board we bring label the pins differently.)

If your board has a USB-C port, connect:

  1. Attacker.G17 (G17 is TX) to Victim.G16 (G16 is RX).
  2. Attacker.G16 to Victim.G17.
  3. Attacker.G2 to Victim.G2 (G2 is the LED pin).
  4. Attacker.GND to Victim.GND.
  5. Attach the attacker’s and victim’s USB ports to your computer.

If your board has a USB-Micro port, connect:

  1. Attacker.TX2 to Victim.RX2.
  2. Attacker.RX2 to Victim.TX2.
  3. Attacker.D2 to Victim.D2 (D2 is the LED pin).
  4. Attacker.GND to Victim.GND.
  5. Attach the attacker’s and victim’s USB ports to your computer.

Warm Up: Program the Attacker Board

Now you have some rough ideas about your attack plan, let’s learn how to write and upload programs to the attacker board.

Below is an example “Hello World” program for ESP32.

void setup() { // Runs once
	Serial.begin(115200);
}

void loop() { // Runs forever
	Serial.println("Hello, 6.5950!");
}

The setup method runs once when the board is reset. The loop method runs forever after setup completes. We will post the starter code on Piazza before the class. The provided attacker code handles setting up both serial to the computer, serial to the victim, and configuring the GPIO pins.

How to Upload Programs

  1. Attach the attacker MCU to your PC using a USB cable (as shown in the hardware setup).
  2. Ensure the correct port and board (Node32s) are selected in Tools > Port and Tools > Board. Do not accidentally reflash the victim board.
    • Note that port are assigned by order of connection to the laptop, so the port name may change if you unplugged the boards and replugged them in later.
    • To be safe you can make it a rule to always flash while only the attacker board is plugged in.
  3. Press the Upload button (it is a rightward pointing arrow). Upload Button
  4. Watch the output window at the bottom.
    • If there are any errors, let the TA know.
    • You may see the output is stuck doing Connecting........_____....._____....._____. forever. If you see that, try holding down the BOOT button and trying again.

Take care to only program the attacker board. The victim board should NOT be reprogrammed ever, otherwise the secret will be lost!

Warmup Exercise

If it is the first time you program embedded systems, try upload the hello-world program to the attacker board and see the output.

With all the setup and preparation above, we can now take a close look at our victim program and start the attack!

Get to Know the Victim: the memcmp Vulnerability

The victim board reads user-supplied passwords over serial, and tells you if the password was correct or not. Your job is to determine the correct password!

When the Super-Secure Password Checker first prototype was shown to customers, they didn’t like the fact that you cannot tell when passwords are being checked. The vendors added this light to provide feedback to the user that computation is going on internally. Specifically, a light is turned on while the password is being checked, and is turned off after the check finishes. However, this light inadvertently leaks the time the MCU takes to check the password.

Internally, the microcontroller is using a vulnerable definition of memcmp to compare your input against the password. Here is the memcmp definition and invocation:

/*
 * memcmp_unsafe
 * Compares buffers buf1 and buf2.
 * Returns true if they are exactly equal, false otherwise.
 */
bool memcmp_unsafe(uint8_t *buf1, uint8_t *buf2, size_t len) {
	for (size_t i = 0; i < len; i++) {
		if (buf1[i] != buf2[i]) {
			return false;
		}
	}
	return true;
}

/*
 * check_passwords
 * Checks the global user input buffer against the global secret.
 */
bool check_passwords(void) {
	turn_on_light(); // The users complained that there was no feedback, so we added a light
	bool result = memcmp_unsafe((uint8_t *)userbuf, (uint8_t *)SECRET, strlen(SECRET));
	turn_off_light();
	return result;
}

Notice that memcmp will quit early when it detects an incorrect character in your input. This means that passwords that contain more correct characters will take slightly longer to check than those with fewer correct characters at the beginning.

Attack Time

Suggested Attack Strategy

  1. Start by determining how to time an individual victim memcmp (How do you know when it starts? How do you know when it ends?)
    • For simplicity, you can start by only timing guesses issued manually via USB connection to the victim. (AKA type into the victim terminal yourself and have the attacker watch for the light).
    • Then move onto having the attacker itself issue guesses.
  2. Move onto parsing the reply from the victim. (Did it say Correct! or did it say Try again. ? Do you care…?)
  3. Now combine the two to leak the entire secret…

Provided Code

  • You will fill in run_attacker just like in the spectre lab. (You have started the spectre lab, haven’t you? :D)
  • The provided code will take care of guess generation, you take care of determining which guess was best.
  • You will implement do_guess which issues one guess and returns the time it took.
  • You will then handle cleanup and state synchronization so that the victim and attacker stay in sync.
    • We provide sync_victim that may help with this.
    • Be careful not to deadlock.

Measuring and Communicating with the Victim

When we hooked the boards up in the wiring part of the experiment setup, we connected the victim’s LED pin to an attacker input pin (pin D2). So, by calling digitalRead(2), we can see what the status of the victim light is. It will either be HIGH or LOW (macros defined for you to use by the Arduino toolchain).

You can use the esp_timer_get_time method to read the current timestamp counter. By timing how long the light is on, you can time how long the victim spends in memcmp.

Use Serial2 to talk to the victim. You can use our wrapper methods send_to_victim and read_byte_from_victim or just use the Serial2 device with the Serial API.

Useful methods

Here is a laundry list of useful methods you may want to use.

  • uint64_t esp_timer_get_time(): Reads from the processor high resolution timer register. Similar to the rdtsc instruction on x86.
  • Serial.println(anything): Print this thing to the serial port. Can handle numbers, char arrays, Strings, etc. Just like on Arduino.
  • digitalRead(pin number): Returns the state of a given input pin (either HIGH or LOW).
  • delay(time in ms): Delay for some number of milliseconds. Useful for syncing up between the two devices/ allowing transfers to finish.
  • Helper functions in the starter code: send_to_computer, send_to_victim, read_byte_from_computer, and read_byte_from_victim. These are wrappers around the excellent Arduino libraries.
  • Variable Serial talks to the computer, and Serial2 talks to the victim.
  • More references: Arduino String Reference, Arduino Serial Reference

Hints

  • If you are sending data too fast, the victim may not see it. Adding some delay()s while testing and then removing them later can’t hurt.
  • You can use Serial2.availableForWrite() to check if the write buffer is busy. This might be able to help you stop sending data too fast.
  • Take a look at the Arduino serial API, it helps a lot with implementing a serial protocol.
  • Valid solutions have a 100% success rate and mine takes 1 minute 30 seconds to run.

Image Credits

Apple, Adafruit, Espressif, Mouser, Bhphoto