diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7ecefa0..cd3b44b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,10 +44,10 @@ jobs: with: release: "14.2.Rel1" - - name: Install CMake and Ninja + - name: Install building tools run: | sudo apt-get update - sudo apt-get install -y cmake ninja-build + sudo apt-get install -y cmake ninja-build gcc-avr binutils-avr avr-libc - name: Build Firmware run: | @@ -58,3 +58,4 @@ jobs: uses: antmicro/renode-test-action@v5 with: tests-to-run: firmware/tests/renode/src/*.robot + renode-revision: "v1.16.1" diff --git a/.gitignore b/.gitignore index c65c5a1..3c96bf5 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,8 @@ build_simulation/ log.html report.html robot_output.xml + +attiny.h + +*-backups/ +*.kicad_prl diff --git a/docs/images/glitch_screen_corrupted.jpg b/docs/images/glitch_screen_corrupted.jpg new file mode 100644 index 0000000..23b2c75 Binary files /dev/null and b/docs/images/glitch_screen_corrupted.jpg differ diff --git a/docs/images/glitch_screen_unstable.jpg b/docs/images/glitch_screen_unstable.jpg new file mode 100644 index 0000000..3c1888a Binary files /dev/null and b/docs/images/glitch_screen_unstable.jpg differ diff --git a/docs/images/glitch_screen_verified.jpg b/docs/images/glitch_screen_verified.jpg new file mode 100644 index 0000000..e6df961 Binary files /dev/null and b/docs/images/glitch_screen_verified.jpg differ diff --git a/docs/labs/02_i2c/instructions.md b/docs/labs/02_i2c/instructions.md index 0c3d013..f841fee 100644 --- a/docs/labs/02_i2c/instructions.md +++ b/docs/labs/02_i2c/instructions.md @@ -29,11 +29,14 @@ Below is a check list of all the tools and skills that you need in order to perf ### Skill set - +- Principles of electronics +- Basic familiarity with command line interfaces (CLIs) ## Objective +The goal of this lab is to exploit the I²C bus of the DVH board. Plain text communication with an EEPROM is performed to retrieve sensitive configuration variables such as passwords. Your task is to interact with I²C to gain root access to the UART shell to find the flags. +This lab contains 3 flags, formatted like so : `DVH{th1s_1s_4_f4k3_fl4g_385c951916}`. ## Instructions @@ -42,3 +45,19 @@ Below is a check list of all the tools and skills that you need in order to perf > This step is off-limits in the scope of the lab. Since the EEPROM is independent from the STM32, it needs to go through an initialization process to make sure the environment is properly setup, and the EEPROM is in a valid state. In order to initialize or reset the lab, press the reset button while powering on the board. The LED will blink, and if everything goes smoothly, it will turn off. If it stays on, an error has occured, meaning the reset has not been completed. Please retry until you end up with an LED turned off. + +### Initial access + +Gain access to the UART shell as practiced in the [corresponding lab](../01_uart/README.md). + +### User escalation + +Now that you are an anonymous user inside the system, find a way to authenticate as a real system user. + +> Hint : Where does the password live ? How is it transmitted ? + +### Root escalation + +With standard user privileges, find a way to gain root access to obtain the third flag. + +> Hint : Exploit eveything in the environment, the EEPROM might respond to other masters than the STM32. Completing this step requires two specific clues. diff --git a/docs/labs/03_glitch/README.md b/docs/labs/03_glitch/README.md new file mode 100644 index 0000000..0264aae --- /dev/null +++ b/docs/labs/03_glitch/README.md @@ -0,0 +1,46 @@ +# Voltage glitching + +## Overview + +Fault injection is a hardware hacking practice that aims to destabilize a device to bypass its security controls. Voltage glitching, or power glitching, is a common form of fault injection, where an attacker controls the power supply to trigger unstable behaviors. + +When dropping the power supply to 0, the chip will obviously power off. However, when exposed to extremely fast (fraction of a millisecond) voltage drops at very precise timings (down to the CPU instruction), it is possible to perform a glitch that causes the CPU to skip its current instruction and continue its execution. + +Skipping a single instruction can be particularly useful when encountering contidional branches (`if ... then ... else`). For example, if we have some sort of password check : + +```c +if (user_input == expected_password) { + welcome() +} else { + get_out() +} +``` + +If we manage to skip the password verification using a voltage glitch, we would be able to call the `welcome()` function without knowing the password. + +## Exploit + +Voltage glitching is extremely powerful (and very hard to master). Because it is targeted at the silicon itself, any chip is virtually vulnerable to such attacks. Compromising the integrity of a system with voltage glitching may result to : + +- Bypass password or security checks. +- Bypass potential Readout Protection measures. +- Obtain unexpected chip behavior, and potentially infinite possibilities. + +The difficulty of power glitching resides in the strictness of finding a relevant width (to prevent the chip from passing out) and delay (to reliably attack the right instruction). Finding those typically requires a lot of trial and error, performing side-channel analysis (footprinting the chip to reliably detect when to trigger to glitch), and a bit of luck. + +Some powerful tools specialized in glitching exist on the market, such as [ChipWhisperer](https://chipwhisperer.readthedocs.io/en/latest/getting-started.html) and the [PicoGlitcher](https://fault-injection-library.readthedocs.io/en/latest/overview/). + +## Mitigations + +There are common methods to protect a system against power glitching : + +- Decoupling capacitors stabilize the power supply, making the chip sustain even with voltage drops. It is however possible for an attacker to desolder the chip. +- Brown Out Detectors (BOD) monitor the microcontroller's power and place the core in a safe state when the voltage falls below a certain level. +- Compare the values twice to harden the check itself. +- Add random time delays before sensitive operations for unpredictability. + +Voltage glitching also suffers from the fact that it is a destructive practice that may damage, or permanently fry the chip. Depending on the target, it may also be frustrating because of its volatile nature, CPU clocks may drift, and an exploit's success rate may vary depending on the setup. + +## Practice + +You can now gain hands-on experience with Voltage Glitching by completing the [lab](./instructions.md) for this section. diff --git a/docs/labs/03_glitch/assets/attiny.bin b/docs/labs/03_glitch/assets/attiny.bin new file mode 100755 index 0000000..1f7bf1d Binary files /dev/null and b/docs/labs/03_glitch/assets/attiny.bin differ diff --git a/docs/labs/03_glitch/assets/glitcher.py b/docs/labs/03_glitch/assets/glitcher.py new file mode 100644 index 0000000..85373c8 --- /dev/null +++ b/docs/labs/03_glitch/assets/glitcher.py @@ -0,0 +1,154 @@ +import machine +import rp2 +import time +import sys + +machine.freq(250_000_000) + +# Hardware state machine (perform the actual glitch) +@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW) +def glitcher(): + pull() # Pull DELAY + mov(y, osr) + pull() # Pull WIDTH + mov(x, osr) + + # Wait for TRIGGER to go HIGH + wait(0, pin, 0) + wait(1, pin, 0) + + # Wait DELAY cycles + label("delay_loop") + jmp(y_dec, "delay_loop") + + # Pull ATTiny VCC to GND for WIDTH cycles + set(pins, 1) + label("width_loop") + jmp(x_dec, "width_loop") + + set(pins, 0) # Release MOSFET + +mosfet_pin = machine.Pin(13, machine.Pin.OUT) +trigger_pin = machine.Pin(17, machine.Pin.IN, machine.Pin.PULL_DOWN) +sm = rp2.StateMachine(0, glitcher, freq=250_000_000, in_base=trigger_pin, set_base=mosfet_pin) + +delay_cycles = 0 +width_cycles = 0 + +print("Welcome to DVH glitcher") +print("Commands: 'fire', 'test trigger', 'set delay ', 'set width '") + +while True: + cmd = sys.stdin.readline().strip().lower().split() + if not cmd: + continue + + if cmd[0] == "set" and len(cmd) == 3: + if cmd[1] == "delay": + delay_cycles = int(cmd[2]) + print(f"[+] Delay set to {delay_cycles} cycles") + elif cmd[1] == "width": + width_cycles = int(cmd[2]) + print(f"[+] Width set to {width_cycles} cycles") + + elif cmd[0] == "test" and len(cmd) == 2 and cmd[1] == "trigger": + print("[+] Listening on TRIGGER pin...") + + trigger_caught = [False] + def trigger_callback(pin): + trigger_caught[0] = True + + trigger_pin.irq(trigger=machine.Pin.IRQ_RISING, handler=trigger_callback) + while not trigger_caught[0]: + pass + + trigger_pin.irq(handler=None) + print("[!] Successfully captured TRIGGER signal") + + elif cmd[0] == "fire": + print(f"[+] Launching glitch (delay: {delay_cycles}, width: {width_cycles})") + # Start state machine + sm.active(0) + sm.put(delay_cycles) + sm.put(width_cycles) + sm.active(1) + + elif cmd[0] == "autosweep" and len(cmd) == 3: + start_delay = int(cmd[1]) + step = int(cmd[2]) + current_delay = start_delay + + print(f"[+] Starting auto-sweep (initial delay: {current_delay}, step: {step}, width: {width_cycles}. Press CTRL+C to stop.") + + try: + while True: + sm.active(0) + sm.put(current_delay) + sm.put(width_cycles) + sm.active(1) + + if current_delay % 100 == 0: + print(f"[+] Scanning delay: {current_delay}") + + timeout_start = time.ticks_ms() + triggered = False + + while time.ticks_diff(time.ticks_ms(), timeout_start) < 8000: + if trigger_pin.value() == 1: + triggered = True + break + + if not triggered: + print("[!] Timed out (TRIGGER is not rising)") + while trigger_pin.value() == 0: + pass + while trigger_pin.value() == 1: + pass + + current_delay += step + + except KeyboardInterrupt: + print("\n[-] auto-sweep stopped.") + + elif cmd[0] == "autosweep2d" and len(cmd) == 5: + # autosweep2d + start_delay = int(cmd[1]) + step = int(cmd[2]) + min_width = int(cmd[3]) + max_width = int(cmd[4]) + current_delay = start_delay + + print(f"[+] Starting auto-sweep 2D (delay: {start_delay}, step: {step}, width Range: {min_width} to {max_width}). Press CTRL+C to stop.") + + try: + while True: + # Sweep the widths for this delay + for current_width in range(min_width, max_width + 1): + sm.active(0) + sm.put(current_delay) + sm.put(current_width) + sm.active(1) + + timeout_start = time.ticks_ms() + triggered = False + while time.ticks_diff(time.ticks_ms(), timeout_start) < 8000: + if trigger_pin.value() == 1: + triggered = True + break + + if not triggered: + print("[!] Timed out (TRIGGER is not rising)") + while trigger_pin.value() == 0: + pass + while trigger_pin.value() == 1: + pass + + if current_delay % 50 == 0: + print(f"[+] Scanned delay: {current_delay} (widths {min_width}-{max_width})") + current_delay += step + + except KeyboardInterrupt: + print("\n[*] 2D AutoSweep stopped by user.") + + else: + print("[!] Unknown command.") diff --git a/docs/labs/03_glitch/instructions.md b/docs/labs/03_glitch/instructions.md new file mode 100644 index 0000000..0a5dae0 --- /dev/null +++ b/docs/labs/03_glitch/instructions.md @@ -0,0 +1,65 @@ +# Voltage Glitching lab instructions + +## Overview + +Here are the top-level instructions required to perform the Voltage Glitching lab. They are meant to give you the necessary information to get you started by yourself. If you ever feel stuck or need any help, you can read the lab's complete [walkthrough](./walkthrough.md) that will provide step-by-step instructions. + +## Requirements + +Below is a check list of all the tools and skills that you need in order to perform the lab. Everything is mandatory unless stated otherwise, except the skill set items. It is absolutely not a problem if you do not have some of the listed skills, but fundamentals of these topics is always nice to have. Anyway, you will learn more about those in the context of the lab. Feel free to do your own research whenever you lack understanding of any of the topics. + +### Materials and equipment + +- Computer +- DVH board with latest firmware (if not, you will need to [flash](../../tools/flash.md) it) +- Glitcher, we will use a Pico with [MicroPython](../../tools/micropython.md) for this lab +- Oscilloscope (optional) +- Micro-USB cable and 5-12V power supply +- Micro-USB to USB/USB-C cable (depending on available ports) +- Dupont cables (recommended) or regular wires +- 5.1K ohm resistor (tested) or any other value ranging from 1K to 10K + +### Software + +- Linux Physical or Virtual Machine (recommended) +- [MicroPython](../../tools/micropython.md) development environment + +### Skill set + +- Principles of electronics +- Some knowledge of MicroPython and the Raspberry Pi Pico +- Basic familiarity with oscilloscopes + +## Objective + +The goal of this lab is to exploit the ATTiny of the DVH board. A security check is performed on the ATTiny to verify the system's integrity, but unfortunately, it never passes. Your task is to perform a voltage glitching attack on the ATTiny to bypass the security check and obtain the flags. + +This lab contains 2 flags, formatted like so : `DVH{th1s_1s_4_f4k3_fl4g_385c951916}`. + +## Instructions + +### Reset the lab + +> This step is off-limits in the scope of the lab. + +Since the ATTiny is independent from the STM32, it needs to go through an initialization process to make sure the environment is properly setup, and the ATTiny is in a valid state. In order to initialize or reset the lab, press the reset button while powering on the board. The LED will blink, and if everything goes smoothly, it will turn off. If it stays on, an error has occured, meaning the reset has not been completed. Please retry until you end up with an LED turned off. + +### Dump the firmware + +> Because the difficulty of this lab is already higher than the others, the [firmware](./assets/attiny.bin) is provided. If you have time, it would be a good exercise to attempt the dump yourself. + +Dump a copy of the firmware running on the ATTiny to understand where the security check lives and how to detect it. + +> Hint : Is there a specific trigger signal that is transmitted right before the security check ? + +### Set up + +Find the right spots on the board to perform the glitch. You will need the trigger from last step, and some sort of method to pull the voltage down. + +> Hint : Is there a specific component near the ATTiny's power supply that can help drop the voltage easily ? + +### Perform glitch + +Find suitable parameters to bypass the security check by glitching the ATTiny. You can use this [MicroPython script](./assets/glitcher.py) to configure the parameters and drop the voltage. + +> Hint : Try to interpret the outputs. What happens when you drop the voltage too long ? What if you do not drop it long enough ? Also, the `autosweep` and `autosweep2d` functions may help with scanning ranges automatically. diff --git a/docs/labs/03_glitch/walkthrough.md b/docs/labs/03_glitch/walkthrough.md new file mode 100644 index 0000000..a555ecd --- /dev/null +++ b/docs/labs/03_glitch/walkthrough.md @@ -0,0 +1,56 @@ +# Voltage Glitching lab walkthrough + +## Overview + +> This walkthrough is still Work In Progress, and some steps need to be explained more in depth, but it is complete enough to give you a working solution. + +This is a complete walkthrough of the Voltage Glitching lab, that is meant to guide you through every step of the way. You can read through this document to perform the lab, but for educational purposes, it is strongly suggested to use the [instructions](./instructions.md) as a main roadmap, and come back here whenever you feel stuck or need a sanity check. + +> By this point, I assume that you have read through the instructions and understood the goal of this lab. + +## Walkthrough + +### Dump the firmware + +When starting this lab, the first thing we notice is the screen going crazy with "CORRUPTED" messages : + +

+ Glitch screen corrupted +

+ +By examining the [ATTiny firmware](./assets/attiny.bin), we notice that right before performing the check, it methodically raises its `PB4` pin. That is the trigger we can attach to. + +### Set up + +When looking at the PCB, we can see that the `VCC` pin is connected to a MOSFET transistor. Even better, there is a pin header that is linked to that MOSFET, which allows us to drop the ATTiny's power supply when that pin header goes high. + +We can thus place two wires going from the Pico : + +
+ +| **DVH** | **Pico** | **Note** | +|:----------------:|:--------:|:----------------------------------:| +| ATTiny_PB4 / TP3 | GP17 | / | +| J8_left | GP13 | Place a 5.1K (or similar) resistor | + +
+ +### Perform glitch + +Using the [Glitcher script](./assets/glitcher.py), we can fire the MOSFET pin whenever the TRIGGER pin goes high, after a delay that we can calculate based on the number of CPU cycles performed before the instruction we want to bypass. + +The width can be found by trial and error. The idea is to find a width that is at the edge between "too wide", which crashes the ATTiny all the time, and "too narrow", which does not induce faults. When the ATTiny crashes sometimes, but not too often, 50-50 for example, the width is a sweet spot to attempt glitching. + +The first time the ATTiny browns out, the first flag will be printed on the screen along the "UNSTABLE" mention : + +

+ Glitch screen unstable +

+ +When you finally manage to perform the glitch and skip the correct CPU instruction, the system will be marked as "VERIFIED", and the second flag will be printed : + +

+ Glitch screen verified +

+ +> The parameters that specifically worked for me are : 5.1K ohm resistor, width 328, delay 110450. A good corresponding command for the glitcher script would be `autosweep2d 105000 10 323 333`. diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index 520f5d9..ad56a94 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -14,12 +14,12 @@ set(CMAKE_C_EXTENSIONS ON) option(SIMULATION "Build for Renode simulation (Bypasses Clock Config)" OFF) if(SIMULATION) - add_compile_definitions(SIMULATION) + add_compile_definitions(SIMULATION) endif() # Define the build type if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE "Debug") + set(CMAKE_BUILD_TYPE "Debug") endif() # Set the project name @@ -35,55 +35,75 @@ message("Build type: " ${CMAKE_BUILD_TYPE}) # Enable CMake support for ASM and C languages enable_language(C ASM) +# Include ATTiny subprojects +include(ExternalProject) +set(ATTINY_GLITCH_DIR ${CMAKE_SOURCE_DIR}/dvh/labs/03_glitch/attiny) + +ExternalProject_Add( + ATTiny_Glitch + SOURCE_DIR ${ATTINY_GLITCH_DIR} + CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${ATTINY_GLITCH_DIR}/avr-toolchain.cmake + BUILD_ALWAYS 1 + INSTALL_COMMAND "" +) + # Create an executable object type add_executable(${CMAKE_PROJECT_NAME}) +# Add subproject dependency +add_dependencies(${CMAKE_PROJECT_NAME} ATTiny_Glitch) + # Add STM32CubeMX generated sources add_subdirectory(cmake/stm32cubemx) # Link directories setup target_link_directories(${CMAKE_PROJECT_NAME} PRIVATE - # Add user defined library search paths + # Add user defined library search paths ) # Add sources to executable target_sources(${CMAKE_PROJECT_NAME} PRIVATE - # Add user sources here - dvh/shared/src/fonts.c - dvh/shared/src/st7789.c - dvh/shared/src/utils_secrets.c - dvh/shared/src/utils_uart.c - dvh/shared/src/utils_shell.c - dvh/shared/src/utils_eeprom.c - dvh/shared/src/utils_screen.c - - dvh/manager/src/lab_bootstrap.c - dvh/manager/src/lab_registry.c - dvh/manager/src/lab_select.c - dvh/manager/src/lab_blink.c - - dvh/labs/00_swd/src/lab_swd.c - dvh/labs/01_uart/src/lab_uart.c - dvh/labs/01_uart/src/lab_uart_data.c - dvh/labs/01_uart/src/lab_uart_commands.c - dvh/labs/02_i2c/src/lab_i2c.c - dvh/labs/02_i2c/src/lab_i2c_data.c - dvh/labs/02_i2c/src/lab_i2c_commands.c + # Add user sources here + dvh/shared/src/fonts.c + dvh/shared/src/st7789.c + dvh/shared/src/utils_secrets.c + dvh/shared/src/utils_uart.c + dvh/shared/src/utils_shell.c + dvh/shared/src/utils_eeprom.c + dvh/shared/src/utils_screen.c + dvh/shared/src/utils_isp.c + + dvh/manager/src/lab_bootstrap.c + dvh/manager/src/lab_registry.c + dvh/manager/src/lab_select.c + dvh/manager/src/lab_blink.c + + dvh/labs/00_swd/src/lab_swd.c + dvh/labs/01_uart/src/lab_uart.c + dvh/labs/01_uart/src/lab_uart_data.c + dvh/labs/01_uart/src/lab_uart_commands.c + dvh/labs/02_i2c/src/lab_i2c.c + dvh/labs/02_i2c/src/lab_i2c_data.c + dvh/labs/02_i2c/src/lab_i2c_commands.c + dvh/labs/03_glitch/src/lab_glitch.c + dvh/labs/03_glitch/src/lab_glitch_data.c + dvh/labs/03_glitch/src/lab_glitch_commands.c ) # Add include paths target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE - # Add user defined include paths - dvh/shared/include - dvh/manager/include - dvh/labs/00_swd/include - dvh/labs/01_uart/include - dvh/labs/02_i2c/include + # Add user defined include paths + dvh/shared/include + dvh/manager/include + dvh/labs/00_swd/include + dvh/labs/01_uart/include + dvh/labs/02_i2c/include + dvh/labs/03_glitch/include ) # Add project symbols (macros) target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE - # Add user defined symbols + # Add user defined symbols ) # Remove wrong libob.a library dependency when using cpp files @@ -91,22 +111,22 @@ list(REMOVE_ITEM CMAKE_C_IMPLICIT_LINK_LIBRARIES ob) # Add linked libraries target_link_libraries(${CMAKE_PROJECT_NAME} - stm32cubemx + stm32cubemx - # Add user defined libraries + # Add user defined libraries ) # Build bin file add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD - COMMAND ${CMAKE_OBJCOPY} -O binary ${CMAKE_PROJECT_NAME}.elf ${CMAKE_PROJECT_NAME}.bin - COMMENT "Generating firmware.bin..." - BYPRODUCTS ${CMAKE_PROJECT_NAME}.bin + COMMAND ${CMAKE_OBJCOPY} -O binary ${CMAKE_PROJECT_NAME}.elf ${CMAKE_PROJECT_NAME}.bin + COMMENT "Generating firmware.bin..." + BYPRODUCTS ${CMAKE_PROJECT_NAME}.bin ) # Add flash target add_custom_target(flash - COMMAND openocd -f ${CMAKE_SOURCE_DIR}/openocd.cfg -c "program ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.elf verify reset exit" - DEPENDS ${CMAKE_PROJECT_NAME} - COMMENT "Flashing firmware via SWD..." - USES_TERMINAL + COMMAND openocd -f ${CMAKE_SOURCE_DIR}/openocd.cfg -c "program ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.elf verify reset exit" + DEPENDS ${CMAKE_PROJECT_NAME} + COMMENT "Flashing firmware via SWD..." + USES_TERMINAL ) diff --git a/firmware/Core/Src/main.c b/firmware/Core/Src/main.c index 5f711fb..5d5f769 100644 --- a/firmware/Core/Src/main.c +++ b/firmware/Core/Src/main.c @@ -263,7 +263,7 @@ static void MX_SPI2_Init(void) hspi2.Init.CLKPolarity = SPI_POLARITY_LOW; hspi2.Init.CLKPhase = SPI_PHASE_1EDGE; hspi2.Init.NSS = SPI_NSS_SOFT; - hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; + hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi2.Init.TIMode = SPI_TIMODE_DISABLE; hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; @@ -327,7 +327,7 @@ static void MX_USART2_UART_Init(void) /* USER CODE END USART2_Init 1 */ huart2.Instance = USART2; - huart2.Init.BaudRate = 115200; + huart2.Init.BaudRate = 1200; huart2.Init.WordLength = UART_WORDLENGTH_8B; huart2.Init.StopBits = UART_STOPBITS_1; huart2.Init.Parity = UART_PARITY_NONE; diff --git a/firmware/dvh/labs/03_glitch/attiny/CMakeLists.txt b/firmware/dvh/labs/03_glitch/attiny/CMakeLists.txt new file mode 100644 index 0000000..3f1a0cb --- /dev/null +++ b/firmware/dvh/labs/03_glitch/attiny/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.22) + +set(CMAKE_PROJECT_NAME attiny) +project(${CMAKE_PROJECT_NAME}) + +set(MCU attiny85) +set(F_CPU 125000UL) + +add_executable(${CMAKE_PROJECT_NAME} + src/main.c + src/attiny_hal.c +) + +set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES SUFFIX ".elf") + +target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE include) + +target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE -mmcu=${MCU} -DF_CPU=${F_CPU} -Os -g) +target_link_options(${CMAKE_PROJECT_NAME} PRIVATE -mmcu=${MCU}) + +add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_OBJCOPY} -O binary $ ${CMAKE_PROJECT_NAME}.bin + COMMAND xxd -i ${CMAKE_PROJECT_NAME}.bin > ${CMAKE_SOURCE_DIR}/../include/${CMAKE_PROJECT_NAME}.h + COMMENT "Converting ELF to binary and generating payload header..." + VERBATIM +) diff --git a/firmware/dvh/labs/03_glitch/attiny/avr-toolchain.cmake b/firmware/dvh/labs/03_glitch/attiny/avr-toolchain.cmake new file mode 100644 index 0000000..17a992c --- /dev/null +++ b/firmware/dvh/labs/03_glitch/attiny/avr-toolchain.cmake @@ -0,0 +1,10 @@ +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR avr) + +set(CMAKE_C_COMPILER avr-gcc) +set(CMAKE_CXX_COMPILER avr-g++) +set(CMAKE_ASM_COMPILER avr-gcc) +set(CMAKE_OBJCOPY avr-objcopy) + +set(CMAKE_C_COMPILER_WORKS 1) +set(CMAKE_CXX_COMPILER_WORKS 1) diff --git a/firmware/dvh/labs/03_glitch/attiny/include/attiny_hal.h b/firmware/dvh/labs/03_glitch/attiny/include/attiny_hal.h new file mode 100644 index 0000000..4d6919e --- /dev/null +++ b/firmware/dvh/labs/03_glitch/attiny/include/attiny_hal.h @@ -0,0 +1,22 @@ +#ifndef ATTINY_HAL_H +#define ATTINY_HAL_H + +#include + +void HAL_Init(void); + +uint8_t HAL_GetResetCause(void); + +void HAL_ClearResetCause(void); + +uint8_t HAL_IsExternalReset(uint8_t reset_cause); + +void HAL_TX_High(void); + +void HAL_TRIG_High(void); + +void HAL_TRIG_Low(void); + +void HAL_UART_TransmitByte(uint8_t data); + +#endif diff --git a/firmware/dvh/labs/03_glitch/attiny/src/attiny_hal.c b/firmware/dvh/labs/03_glitch/attiny/src/attiny_hal.c new file mode 100644 index 0000000..cf0b76a --- /dev/null +++ b/firmware/dvh/labs/03_glitch/attiny/src/attiny_hal.c @@ -0,0 +1,57 @@ +#include +#include + +#define TX_PIN PB3 +#define TRIG_PIN PB4 +#define BIT_DELAY 715 + +void HAL_Init(void) { + // Slow down the clock using prescaler (125 kHz) + CLKPR = (1 << CLKPCE); + CLKPR = (1 << CLKPS2) | (1 << CLKPS1); + + DDRB |= (1 << TX_PIN) | (1 << TRIG_PIN); +} + +uint8_t HAL_GetResetCause(void) { + return MCUSR; +} + +void HAL_ClearResetCause(void) { + MCUSR = 0; +} + +uint8_t HAL_IsExternalReset(uint8_t reset_cause) { + return reset_cause & (1 << EXTRF); +} + +void HAL_TX_High() { + PORTB |= (1 << TX_PIN); +} + +void HAL_TRIG_High() { + PORTB |= (1 << TRIG_PIN); +} + +void HAL_TRIG_Low() { + PORTB &= ~(1 << TRIG_PIN); +} + +void HAL_UART_TransmitByte(uint8_t data) { + PORTB &= ~(1 << TX_PIN); // Start bit (LOW) + _delay_us(BIT_DELAY); + + // Data bits (8 bits, LSB first) + for (uint8_t i = 0; i < 8; i++) { + if (data & 0x01) { + PORTB |= (1 << TX_PIN); // HIGH + } else { + PORTB &= ~(1 << TX_PIN); // LOW + } + _delay_us(BIT_DELAY); + data >>= 1; + } + + PORTB |= (1 << TX_PIN); // Stop bit (HIGH) + _delay_us(BIT_DELAY); +} diff --git a/firmware/dvh/labs/03_glitch/attiny/src/main.c b/firmware/dvh/labs/03_glitch/attiny/src/main.c new file mode 100644 index 0000000..bcfb6a9 --- /dev/null +++ b/firmware/dvh/labs/03_glitch/attiny/src/main.c @@ -0,0 +1,58 @@ +#include "attiny_hal.h" +#include + +#define CANARY_SIZE 16 +uint8_t canary[CANARY_SIZE] __attribute__ ((section (".noinit"))); + +int main(void) { + uint8_t reset_cause = HAL_GetResetCause(); + HAL_ClearResetCause(); + + HAL_Init(); + HAL_TX_High(); + HAL_TRIG_Low(); + _delay_ms(100); + + uint8_t is_glitch = 1; + // Inspect canary for expected bytes + for (uint8_t i = 0; i < CANARY_SIZE; i++) { + uint8_t expected_val = (i % 2 == 0) ? 0xAA : 0x55; + if (canary[i] != expected_val) { + is_glitch = 0; + break; + } + } + + // Refresh canary for next time + if (!is_glitch) { + for (uint8_t i = 0; i < CANARY_SIZE; i++) { + canary[i] = (i % 2 == 0) ? 0xAA : 0x55; + } + } + + if (is_glitch && !HAL_IsExternalReset(reset_cause)) { + HAL_UART_TransmitByte(0xca); // First flag / Crashed + } + + while(1) { + HAL_TRIG_High(); + _delay_us(20); + + volatile uint16_t count = 0; + for (volatile uint16_t i = 0; i < 500; i++) { + count++; + } + + HAL_TRIG_Low(); + + if (count != 500) { + _delay_ms(10); + HAL_UART_TransmitByte(0xcb); // Second flag + _delay_ms(10000); + } else { + HAL_UART_TransmitByte(0xc0); // Failed + } + + _delay_ms(2); + } +} diff --git a/firmware/dvh/labs/03_glitch/include/lab_glitch.h b/firmware/dvh/labs/03_glitch/include/lab_glitch.h new file mode 100644 index 0000000..e948f57 --- /dev/null +++ b/firmware/dvh/labs/03_glitch/include/lab_glitch.h @@ -0,0 +1,8 @@ +#ifndef LAB_GLITCH_H +#define LAB_GLITCH_H + +#include "ilab.h" + +extern ILab Lab_Glitch; + +#endif diff --git a/firmware/dvh/labs/03_glitch/include/lab_glitch_commands.h b/firmware/dvh/labs/03_glitch/include/lab_glitch_commands.h new file mode 100644 index 0000000..d888046 --- /dev/null +++ b/firmware/dvh/labs/03_glitch/include/lab_glitch_commands.h @@ -0,0 +1,20 @@ +#ifndef LAB_GLITCH_COMMANDS_H +#define LAB_GLITCH_COMMANDS_H + +#include +#include + +typedef struct { + bool ui_printed; + bool flag_one_printed; + bool flag_two_printed; + uint32_t last_dot; + uint32_t last_corrupt; + uint32_t last_crash; +} Lab_Glitch_State; + +void Lab_Glitch_Cmd_InitState(Lab_Glitch_State* state); + +void Lab_Glitch_Cmd_ProcessByte(Lab_Glitch_State* state, uint8_t c, uint32_t tick); + +#endif diff --git a/firmware/dvh/labs/03_glitch/include/lab_glitch_data.h b/firmware/dvh/labs/03_glitch/include/lab_glitch_data.h new file mode 100644 index 0000000..c729bfc --- /dev/null +++ b/firmware/dvh/labs/03_glitch/include/lab_glitch_data.h @@ -0,0 +1,10 @@ +#ifndef LAB_GLITCH_DATA_H +#define LAB_GLITCH_DATA_H + +extern const unsigned char LAB_GLITCH_FLAG_ONE[]; +extern const int LAB_GLITCH_FLAG_ONE_LEN; + +extern const unsigned char LAB_GLITCH_FLAG_TWO[]; +extern const int LAB_GLITCH_FLAG_TWO_LEN; + +#endif diff --git a/firmware/dvh/labs/03_glitch/src/lab_glitch.c b/firmware/dvh/labs/03_glitch/src/lab_glitch.c new file mode 100644 index 0000000..ffd3b3f --- /dev/null +++ b/firmware/dvh/labs/03_glitch/src/lab_glitch.c @@ -0,0 +1,91 @@ +#include "lab_glitch.h" +#include "lab_glitch_data.h" +#include "lab_glitch_commands.h" +#include "attiny.h" +#include "utils_secrets.h" +#include "utils_isp.h" +#include "utils_screen.h" +#include "main.h" +#include + +extern UART_HandleTypeDef huart2; + +Lab_StatusTypeDef Lab_Glitch_Init(void) { + HAL_GPIO_WritePin(ATTINY_RST_GPIO_Port, ATTINY_RST_Pin, GPIO_PIN_SET); + HAL_GPIO_WritePin(LDO_EN_GPIO_Port, LDO_EN_Pin, GPIO_PIN_SET); + HAL_Delay(100); + return LAB_OK; +} + +void Lab_Glitch_Loop(void) { + static Lab_Glitch_State state; + static bool is_init = false; + + if (!is_init) { + Lab_Glitch_Cmd_InitState(&state); + is_init = true; + } + + if (state.flag_two_printed && state.flag_one_printed) { + HAL_Delay(20000); + return; + } + + uint8_t c; + HAL_StatusTypeDef status = HAL_UART_Receive(&huart2, &c, 1, 100); + + if (status == HAL_OK) { + Lab_Glitch_Cmd_ProcessByte(&state, c, HAL_GetTick()); + + } else if (status == HAL_ERROR) { + Utils_Screen_Fill_Write("[CRITICAL] ATTiny UART timeout", UTILS_SCREEN_WARNING); + state.ui_printed = false; + HAL_Delay(2000); + } +} + +Lab_StatusTypeDef Lab_Glitch_Reset(void) { + // Power on ATTiny while in RESET mode + HAL_GPIO_WritePin(ATTINY_RST_GPIO_Port, ATTINY_RST_Pin, GPIO_PIN_RESET); + HAL_Delay(100); + HAL_GPIO_WritePin(LDO_EN_GPIO_Port, LDO_EN_Pin, GPIO_PIN_SET); + HAL_Delay(20); + + if (Utils_ISP_ProgrammingEnable_Retry(20) != UTILS_ISP_OK) { + HAL_GPIO_WritePin(ATTINY_RST_GPIO_Port, ATTINY_RST_Pin, GPIO_PIN_SET); + return LAB_ERROR; + } + + // Ensure chip is correct and working + uint8_t device_code[3]; + Utils_ISP_ReadDeviceCode(device_code, sizeof(device_code)); + if ((device_code[0] != 0x1e) || (device_code[1] != 0x93) || (device_code[2] != 0x0b)) { + return LAB_ERROR; + } + + if (Utils_ISP_ChipErase() != UTILS_ISP_OK) { + return LAB_ERROR; + } + HAL_Delay(20); + HAL_GPIO_WritePin(ATTINY_RST_GPIO_Port, ATTINY_RST_Pin, GPIO_PIN_SET); + HAL_Delay(5); + HAL_GPIO_WritePin(ATTINY_RST_GPIO_Port, ATTINY_RST_Pin, GPIO_PIN_RESET); + HAL_Delay(20); + + if (Utils_ISP_ProgrammingEnable_Retry(20) != UTILS_ISP_OK) { + HAL_GPIO_WritePin(ATTINY_RST_GPIO_Port, ATTINY_RST_Pin, GPIO_PIN_SET); + return LAB_ERROR; + } + + Utils_ISP_Write(attiny_bin, attiny_bin_len); + HAL_GPIO_WritePin(ATTINY_RST_GPIO_Port, ATTINY_RST_Pin, GPIO_PIN_SET); + return LAB_OK; +} + +ILab Lab_Glitch = { + .id = 3, + .name = "Voltage glitching", + .init = Lab_Glitch_Init, + .loop = Lab_Glitch_Loop, + .reset = Lab_Glitch_Reset +}; diff --git a/firmware/dvh/labs/03_glitch/src/lab_glitch_commands.c b/firmware/dvh/labs/03_glitch/src/lab_glitch_commands.c new file mode 100644 index 0000000..8f6d40b --- /dev/null +++ b/firmware/dvh/labs/03_glitch/src/lab_glitch_commands.c @@ -0,0 +1,65 @@ +#include "lab_glitch_commands.h" +#include "lab_glitch_data.h" +#include "utils_secrets.h" +#include "utils_screen.h" +#include "main.h" + +void Lab_Glitch_Cmd_InitState(Lab_Glitch_State* state) { + state->ui_printed = false; + state->flag_one_printed = false; + state->flag_two_printed = false; + state->last_dot = 0; + state->last_corrupt = 0; + state->last_crash = 0; +} + +void Lab_Glitch_Cmd_ProcessByte(Lab_Glitch_State* state, uint8_t c, uint32_t tick) { + if (!state->ui_printed) { + Utils_Screen_Fill_Write("[ATTINY] Running integrity checks", UTILS_SCREEN_STANDARD); + state->ui_printed = true; + } + + // Standard state + if (c == 0xc0) { + if (tick > (state->last_dot + 500)) { + Utils_Screen_WriteChar('.', UTILS_SCREEN_STANDARD); + state->last_dot = tick; + } + + if (tick > (state->last_corrupt + 2500)) { + Utils_Screen_Write("CORRUPTED, system will reboot now", UTILS_SCREEN_WARNING); + state->last_corrupt = tick; + state->ui_printed = false; + } + + } else if (c == 0xca) { + // Crash state + if (tick > state->last_crash + 1000) { + Utils_Screen_Write("UNSTABLE, ATTiny crashed", UTILS_SCREEN_WARNING); + state->last_crash = tick; + state->ui_printed = false; + } + + // Print flag one on first occurence + if (!state->flag_one_printed) { + char flag[64]; + Utils_Secrets_Decrypt(LAB_GLITCH_FLAG_ONE, LAB_GLITCH_FLAG_ONE_LEN, flag, sizeof(flag)); + Utils_Screen_Write(flag, UTILS_SCREEN_WARNING); + + state->flag_one_printed = true; + HAL_Delay(20000); + } + + } else if (c == 0xcb) { + // Successful glitch state + Utils_Screen_Write("VERIFIED, system is stable", UTILS_SCREEN_STANDARD); + + char flag[64]; + Utils_Secrets_Decrypt(LAB_GLITCH_FLAG_TWO, LAB_GLITCH_FLAG_TWO_LEN, flag, sizeof(flag)); + Utils_Screen_Write(flag, UTILS_SCREEN_WARNING); + state->flag_two_printed = true; + + } else { + Utils_Screen_WriteChar((char)c, UTILS_SCREEN_WARNING); + } +} diff --git a/firmware/dvh/labs/03_glitch/src/lab_glitch_data.c b/firmware/dvh/labs/03_glitch/src/lab_glitch_data.c new file mode 100644 index 0000000..6dcfd0a --- /dev/null +++ b/firmware/dvh/labs/03_glitch/src/lab_glitch_data.c @@ -0,0 +1,9 @@ +#include "lab_glitch_data.h" + +// DVH{br0wn3d_0ut_50bd763c0d} +const unsigned char LAB_GLITCH_FLAG_ONE[] = {0x2e, 0x23, 0x3b, 0x0f, 0x0b, 0x1c, 0x5a, 0x02, 0x1d, 0x47, 0x0d, 0x31, 0x5a, 0x00, 0x07, 0x2b, 0x5c, 0x5e, 0x08, 0x11, 0x44, 0x42, 0x5a, 0x0d, 0x5a, 0x11, 0x0e}; +const int LAB_GLITCH_FLAG_ONE_LEN = sizeof(LAB_GLITCH_FLAG_ONE); + +// DVH{gl1tch_m4st3r_335b04723e} +const unsigned char LAB_GLITCH_FLAG_TWO[] = {0x2e, 0x23, 0x3b, 0x0f, 0x0e, 0x02, 0x5b, 0x01, 0x10, 0x1c, 0x36, 0x03, 0x5e, 0x06, 0x07, 0x47, 0x1b, 0x31, 0x59, 0x46, 0x46, 0x16, 0x59, 0x5a, 0x5d, 0x47, 0x40, 0x11, 0x14}; +const int LAB_GLITCH_FLAG_TWO_LEN = sizeof(LAB_GLITCH_FLAG_TWO); diff --git a/firmware/dvh/manager/src/lab_registry.c b/firmware/dvh/manager/src/lab_registry.c index ad12d3d..964aec9 100644 --- a/firmware/dvh/manager/src/lab_registry.c +++ b/firmware/dvh/manager/src/lab_registry.c @@ -4,11 +4,13 @@ #include "lab_swd.h" #include "lab_uart.h" #include "lab_i2c.h" +#include "lab_glitch.h" static ILab* labs[] = { &Lab_SWD, &Lab_UART, &Lab_I2C, + &Lab_Glitch, NULL }; diff --git a/firmware/dvh/shared/include/utils_isp.h b/firmware/dvh/shared/include/utils_isp.h new file mode 100644 index 0000000..d1048dd --- /dev/null +++ b/firmware/dvh/shared/include/utils_isp.h @@ -0,0 +1,23 @@ +#ifndef UTILS_ISP_H +#define UTILS_ISP_H + +#include +#include +#include + +typedef enum { + UTILS_ISP_OK, + UTILS_ISP_ERROR +} Utils_ISP_StatusTypeDef; + +Utils_ISP_StatusTypeDef Utils_ISP_ProgrammingEnable(void); + +Utils_ISP_StatusTypeDef Utils_ISP_ProgrammingEnable_Retry(uint8_t count); + +void Utils_ISP_ReadDeviceCode(uint8_t* device_code, size_t size); + +Utils_ISP_StatusTypeDef Utils_ISP_ChipErase(void); + +void Utils_ISP_Write(unsigned char* data, unsigned int len); + +#endif diff --git a/firmware/dvh/shared/include/utils_screen.h b/firmware/dvh/shared/include/utils_screen.h index 586fd6d..b73e006 100644 --- a/firmware/dvh/shared/include/utils_screen.h +++ b/firmware/dvh/shared/include/utils_screen.h @@ -7,11 +7,13 @@ typedef enum { UTILS_SCREEN_WARNING, } Utils_Screen_State; -void Utils_Screen_Fill(Utils_Screen_State status); +void Utils_Screen_Fill(Utils_Screen_State state); -void Utils_Screen_Write(char* text, Utils_Screen_State status); +void Utils_Screen_WriteChar(char c, Utils_Screen_State state); -void Utils_Screen_Fill_Write(char* text, Utils_Screen_State status); +void Utils_Screen_Write(char* text, Utils_Screen_State state); + +void Utils_Screen_Fill_Write(char* text, Utils_Screen_State state); void Utils_Screen_Welcome(void); diff --git a/firmware/dvh/shared/src/utils_isp.c b/firmware/dvh/shared/src/utils_isp.c new file mode 100644 index 0000000..0c33443 --- /dev/null +++ b/firmware/dvh/shared/src/utils_isp.c @@ -0,0 +1,81 @@ +#include "utils_isp.h" +#include "main.h" + +extern SPI_HandleTypeDef hspi2; + +uint8_t Utils_ISP_Transmit(uint8_t b1, uint8_t b2, uint8_t b3, uint8_t b4, uint8_t ret_idx) { + uint8_t tx_data[4] = {b1, b2, b3, b4}; + uint8_t rx_data[4] = {0}; + HAL_SPI_TransmitReceive(&hspi2, tx_data, rx_data, 4, HAL_MAX_DELAY); + return rx_data[ret_idx]; +} + +Utils_ISP_StatusTypeDef Utils_ISP_ProgrammingEnable(void) { + uint8_t ret = Utils_ISP_Transmit(0xac, 0x53, 0x00, 0x00, 2); + if (ret == 0x53) return UTILS_ISP_OK; + return UTILS_ISP_ERROR; +} + +Utils_ISP_StatusTypeDef Utils_ISP_ProgrammingEnable_Retry(uint8_t count) { + uint8_t retries = 0; + + while (retries < count) { + // Pulse RESET + HAL_GPIO_WritePin(ATTINY_RST_GPIO_Port, ATTINY_RST_Pin, GPIO_PIN_SET); + HAL_Delay(5); + HAL_GPIO_WritePin(ATTINY_RST_GPIO_Port, ATTINY_RST_Pin, GPIO_PIN_RESET); + HAL_Delay(25); + + if (Utils_ISP_ProgrammingEnable() == UTILS_ISP_OK) return UTILS_ISP_OK; + retries++; + } + + return false; +} + +void Utils_ISP_ReadDeviceCode(uint8_t* device_code, size_t size) { + if ((device_code == NULL) || (size < 3)) return; + + device_code[0] = Utils_ISP_Transmit(0x30, 0x00, 0x00, 0x00, 3); // vendor + device_code[1] = Utils_ISP_Transmit(0x30, 0x00, 0x01, 0x00, 3); // family and flash size + device_code[2] = Utils_ISP_Transmit(0x30, 0x00, 0x02, 0x00, 3); // part ID +} + +Utils_ISP_StatusTypeDef Utils_ISP_ChipErase(void) { + uint8_t ret = Utils_ISP_Transmit(0xac, 0x80, 0x00, 0x00, 2); + if (ret == 0x80) return UTILS_ISP_OK; + return UTILS_ISP_ERROR; +} + +void Utils_ISP_Write(unsigned char* data, unsigned int len) { + uint32_t b_addr = 0; // byte address + + while (b_addr < len) { + uint8_t b_low = data[b_addr]; + uint8_t b_high = 0xff; // Default pad if odd payload + if ((b_addr + 1) < len) { + b_high = data[b_addr + 1]; + } + + uint16_t w_addr = b_addr / 2; // word address + uint8_t w_lsb = w_addr & 0xff; + uint8_t w_msb = (w_addr >> 8) & 0xff; + + // Load Program Memory Page + Utils_ISP_Transmit(0x40, w_msb, w_lsb, b_low, 0); + Utils_ISP_Transmit(0x48, w_msb, w_lsb, b_high, 0); + + b_addr += 2; + + // Only write when end of page or end of payload + if (((b_addr % 64) == 0) || (b_addr >= len)) { + uint16_t p_addr = (w_addr / 32) * 32; // page base address + uint8_t p_lsb = p_addr & 0xff; + uint8_t p_msb = (p_addr >> 8) & 0xff; + + // Write Program Memory Page + Utils_ISP_Transmit(0x4c, p_msb, p_lsb, 0x00, 0); + HAL_Delay(5); + } + } +} diff --git a/firmware/dvh/shared/src/utils_screen.c b/firmware/dvh/shared/src/utils_screen.c index 3778264..0342f4b 100644 --- a/firmware/dvh/shared/src/utils_screen.c +++ b/firmware/dvh/shared/src/utils_screen.c @@ -30,6 +30,19 @@ void Utils_Screen_Fill(Utils_Screen_State state) { ST7789_Fill_Color(COLOR_THEMES[state].bg); } +void Utils_Screen_WriteChar(char c, Utils_Screen_State state) { + ST7789_WriteChar(cursor_x, cursor_y, c, *font, COLOR_THEMES[state].fg, COLOR_THEMES[state].bg); + + cursor_x += font->width; + if (cursor_x >= (ST7789_WIDTH - font->width)) { + cursor_y += font->height; + cursor_x = BASE_X_OFFSET; + } + if (cursor_y >= (ST7789_HEIGHT - font->height)) { + cursor_y = BASE_Y_OFFSET; + } +} + void Utils_Screen_Write(char* text, Utils_Screen_State state) { ST7789_WriteString(cursor_x, cursor_y, text, *font, COLOR_THEMES[state].fg, COLOR_THEMES[state].bg); @@ -39,7 +52,7 @@ void Utils_Screen_Write(char* text, Utils_Screen_State state) { cursor_y += (requested_lines * font->height); // Vertical bounds check - if (cursor_y >= ST7789_HEIGHT - font->height) { + if (cursor_y >= (ST7789_HEIGHT - font->height)) { cursor_y = BASE_Y_OFFSET; } } diff --git a/firmware/firmware.ioc b/firmware/firmware.ioc index cca2c77..e8d5a20 100644 --- a/firmware/firmware.ioc +++ b/firmware/firmware.ioc @@ -246,14 +246,16 @@ SPI1.Direction=SPI_DIRECTION_2LINES SPI1.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate,BaudRatePrescaler,CLKPolarity,CLKPhase SPI1.Mode=SPI_MODE_MASTER SPI1.VirtualType=VM_MASTER -SPI2.CalculateBaudRate=18.0 MBits/s +SPI2.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_256 +SPI2.CalculateBaudRate=140.625 KBits/s SPI2.Direction=SPI_DIRECTION_2LINES -SPI2.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate +SPI2.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate,BaudRatePrescaler SPI2.Mode=SPI_MODE_MASTER SPI2.VirtualType=VM_MASTER USART1.IPParameters=VirtualMode USART1.VirtualMode=VM_ASYNC -USART2.IPParameters=VirtualMode,Mode +USART2.BaudRate=1200 +USART2.IPParameters=VirtualMode,Mode,BaudRate USART2.Mode=MODE_RX USART2.VirtualMode=VM_ASYNC VP_SYS_VS_Systick.Mode=SysTick diff --git a/firmware/tests/renode/platforms/dvh.repl b/firmware/tests/renode/platforms/dvh.repl index 8be3112..ae7da30 100644 --- a/firmware/tests/renode/platforms/dvh.repl +++ b/firmware/tests/renode/platforms/dvh.repl @@ -1 +1,4 @@ using "platforms/cpus/stm32f103.repl" + +spi1: SPI.STM32SPI @ sysbus 0x40013000 + IRQ -> nvic@35 diff --git a/firmware/tests/renode/src/test_lab_glitch.robot b/firmware/tests/renode/src/test_lab_glitch.robot new file mode 100644 index 0000000..365964c --- /dev/null +++ b/firmware/tests/renode/src/test_lab_glitch.robot @@ -0,0 +1,52 @@ +*** Settings *** +Resource ${RENODEKEYWORDS} +Library OperatingSystem + +Suite Setup Setup +Suite Teardown Teardown +Test Teardown Teardown + +*** Variables *** +${PLATFORM} ${CURDIR}/../platforms/dvh.repl +${ELF} ${CURDIR}/../../../build_simulation/firmware.elf +${UART} sysbus.usart2 +${SPI} sysbus.spi1 +${HOOKS_SCRIPT} ${CURDIR}/utils/hooks_lab_glitch.py + +${FLAG_STRING} Utils_Screen: DVH\{ + +*** Test Cases *** +Verify Lab Glitch + [Documentation] Verify glitching can be correctly performed on the ATTiny to print flags + File Should Exist ${ELF} msg=DVH ELF not found + + # Setup + Execute Command mach create "dvh_test_glitch" + Execute Command machine LoadPlatformDescription @${PLATFORM} + Execute Command sysbus LoadELF @${ELF} + + # Config (select lab 0011) + Create Log Tester 0 + Execute Command sysbus.gpioPortB OnGPIO 3 True + Execute Command sysbus.gpioPortA OnGPIO 15 True + Execute Command logLevel 3 ${SPI} + + # Run + Execute Command include @${HOOKS_SCRIPT} + Execute Command start + + # Verify + Sleep 5s + Execute Command ${UART} WriteChar 192 + Wait For Log Entry Utils_Screen: [ATTINY] Running integrity checks timeout=5 + Wait For Log Entry Utils_Screen: CORRUPTED, system will reboot now timeout=5 + + Sleep 5s + Execute Command ${UART} WriteChar 202 + Wait For Log Entry Utils_Screen: UNSTABLE, ATTiny crashed timeout=5 + Wait For Log Entry ${FLAG_STRING} timeout=5 + + Sleep 20s + Execute Command ${UART} WriteChar 203 + Wait For Log Entry Utils_Screen: VERIFIED, system is stable timeout=5 + Wait For Log Entry ${FLAG_STRING} timeout=5 diff --git a/firmware/tests/renode/src/test_lab_swd.robot b/firmware/tests/renode/src/test_lab_swd.robot index e6f2136..c4d8555 100644 --- a/firmware/tests/renode/src/test_lab_swd.robot +++ b/firmware/tests/renode/src/test_lab_swd.robot @@ -9,7 +9,7 @@ Test Teardown Teardown *** Variables *** ${PLATFORM} ${CURDIR}/../platforms/dvh.repl ${ELF} ${CURDIR}/../../../build_simulation/firmware.elf -${HOOKS_SCRIPT} ${CURDIR}/utils/hooks.py +${HOOKS_SCRIPT} ${CURDIR}/utils/hooks_lab_swd.py *** Test Cases *** Verify Lab SWD @@ -20,7 +20,7 @@ Verify Lab SWD Execute Command mach create "dvh_test_swd" Execute Command machine LoadPlatformDescription @${PLATFORM} Execute Command sysbus LoadELF @${ELF} - + # Config Create Log Tester 0 Execute Command logLevel 0 @@ -29,7 +29,7 @@ Verify Lab SWD # Run Execute Command include @${HOOKS_SCRIPT} Execute Command start - + # Verify Wait For Log Entry Success: LAB_SWD_FLAG_ONE found! timeout=5 Wait For Log Entry Success: LAB_SWD_FLAG_TWO found! timeout=5 diff --git a/firmware/tests/renode/src/utils/hooks_lab_glitch.py b/firmware/tests/renode/src/utils/hooks_lab_glitch.py new file mode 100644 index 0000000..98e996b --- /dev/null +++ b/firmware/tests/renode/src/utils/hooks_lab_glitch.py @@ -0,0 +1,17 @@ +import sys +from Antmicro.Renode.Logging import Logger, LogLevel +from Antmicro.Renode.Peripherals.CPU import ICPU + +def hook_screen_write(cpu, address): + str_ptr = cpu.GetRegister(0) + raw = self.Machine.SystemBus.ReadBytes(str_ptr, 128) + text = "".join([chr(b) for b in raw]).split('\0')[0] + Logger.Log(LogLevel.Info, "Utils_Screen: %s" % (text)) + +try: + sysbus = self.Machine.SystemBus + cpu = list(self.Machine.GetPeripheralsOfType[ICPU]())[0] + cpu.AddHook(sysbus.GetSymbolAddress("Utils_Screen_Write"), hook_screen_write) + cpu.AddHook(sysbus.GetSymbolAddress("Utils_Screen_Fill_Write"), hook_screen_write) +except Exception as e: + Logger.Log(LogLevel.Error, "Error during setup: " + str(e)) diff --git a/firmware/tests/renode/src/utils/hooks.py b/firmware/tests/renode/src/utils/hooks_lab_swd.py similarity index 94% rename from firmware/tests/renode/src/utils/hooks.py rename to firmware/tests/renode/src/utils/hooks_lab_swd.py index ff85952..5d026a2 100644 --- a/firmware/tests/renode/src/utils/hooks.py +++ b/firmware/tests/renode/src/utils/hooks_lab_swd.py @@ -11,7 +11,7 @@ def read_string(bus, address, size=64): return "".join([chr(b) for b in byte_array if 32 <= b <= 126]) def get_reg_value(cpu, offset_reg): - reg_val = cpu.GetRegisterUnsafe(offset_reg) + reg_val = cpu.GetRegister(offset_reg) try: return System.Convert.ToUInt64(reg_val) except: @@ -33,7 +33,7 @@ def check_flag(cpu, symbol, flag, ptr): match = True if match: - Logger.Log(LogLevel.Error, "Success: %s found! ('%s')" % (symbol, discovered_content)) + Logger.Log(LogLevel.Info, "Success: %s found! ('%s')" % (symbol, discovered_content)) else: Logger.Log(LogLevel.Error, "Failed: %s content mismatch" % flag) Logger.Log(LogLevel.Error, " Expected: '%s'" % flag) diff --git a/firmware/tests/unit/mocks/mock_st7789.c b/firmware/tests/unit/mocks/mock_st7789.c index 4757650..7f6c381 100644 --- a/firmware/tests/unit/mocks/mock_st7789.c +++ b/firmware/tests/unit/mocks/mock_st7789.c @@ -17,6 +17,15 @@ void ST7789_Fill_Color(uint16_t color) { SPY_ST7789_BG = color; } +void ST7789_WriteChar(uint16_t x, uint16_t y, const char c, FontDef font, uint16_t color, uint16_t bgcolor) { + (void)font; + SPY_ST7789_FG = color; + SPY_ST7789_BG = bgcolor; + SPY_ST7789_X = x; + SPY_ST7789_Y = y; + strncpy(SPY_ST7789_Buffer, &c, 1); +} + void ST7789_WriteString(uint16_t x, uint16_t y, const char *str, FontDef font, uint16_t color, uint16_t bgcolor) { (void)font; SPY_ST7789_FG = color; diff --git a/firmware/tests/unit/mocks/mock_utils_screen.c b/firmware/tests/unit/mocks/mock_utils_screen.c index 063e1ec..e3af66f 100644 --- a/firmware/tests/unit/mocks/mock_utils_screen.c +++ b/firmware/tests/unit/mocks/mock_utils_screen.c @@ -9,7 +9,9 @@ uint8_t SPY_Screen_Root_Calls = 0; char SPY_Screen_LastText[128] = {0}; Utils_Screen_State SPY_Screen_LastState = 0; +int SPY_Screen_WriteChar_CallCount = 0; int SPY_Screen_Write_CallCount = 0; +int SPY_Screen_FillWriteChar_CallCount = 0; int SPY_Screen_FillWrite_CallCount = 0; void SPY_Screen_Clear(void) { @@ -28,14 +30,32 @@ void Utils_Screen_UART_Anonymous(void) { SPY_Screen_Anonymous_Calls++; } void Utils_Screen_UART_User(void) { SPY_Screen_User_Calls++; } void Utils_Screen_UART_Root(void) { SPY_Screen_Root_Calls++; } +void Utils_Screen_WriteChar(char c, Utils_Screen_State state) { + SPY_Screen_WriteChar_CallCount++; + SPY_Screen_LastState = state; + int len = strlen(SPY_Screen_LastText); + if (len < sizeof(SPY_Screen_LastText) - 1) { + SPY_Screen_LastText[len] = c; + SPY_Screen_LastText[len+1] = '\0'; + } +} + void Utils_Screen_Write(char* text, Utils_Screen_State state) { SPY_Screen_Write_CallCount++; SPY_Screen_LastState = state; - strncpy(SPY_Screen_LastText, text, sizeof(SPY_Screen_LastText) - 1); + strncat(SPY_Screen_LastText, text, sizeof(SPY_Screen_LastText) - strlen(SPY_Screen_LastText) - 1); +} + +void Utils_Screen_Fill_WriteChar(char c, Utils_Screen_State state) { + SPY_Screen_FillWriteChar_CallCount++; + SPY_Screen_LastState = state; + memset(SPY_Screen_LastText, 0, sizeof(SPY_Screen_LastText)); // Clear screen + SPY_Screen_LastText[0] = c; } void Utils_Screen_Fill_Write(char* text, Utils_Screen_State state) { SPY_Screen_FillWrite_CallCount++; SPY_Screen_LastState = state; + memset(SPY_Screen_LastText, 0, sizeof(SPY_Screen_LastText)); // Clear screen strncpy(SPY_Screen_LastText, text, sizeof(SPY_Screen_LastText) - 1); } diff --git a/firmware/tests/unit/mocks/mock_utils_uart.c b/firmware/tests/unit/mocks/mock_utils_uart.c index 73e9e4d..be90d54 100644 --- a/firmware/tests/unit/mocks/mock_utils_uart.c +++ b/firmware/tests/unit/mocks/mock_utils_uart.c @@ -1,5 +1,6 @@ #include "../../../dvh/shared/include/utils_uart.h" #include +#include void Utils_UART_Writeline(const char* text) { strncat(SPY_UART_Buffer, text, sizeof(SPY_UART_Buffer) - strlen(SPY_UART_Buffer) - 1); diff --git a/firmware/tests/unit/run_tests.sh b/firmware/tests/unit/run_tests.sh index ebdd595..bff5931 100755 --- a/firmware/tests/unit/run_tests.sh +++ b/firmware/tests/unit/run_tests.sh @@ -13,6 +13,7 @@ INCLUDES=( "-I dvh/labs/00_swd/include" "-I dvh/labs/01_uart/include" "-I dvh/labs/02_i2c/include" + "-I dvh/labs/03_glitch/include" ) MOCK_SOURCE="$MOCK_DIR/mock_hal.c" diff --git a/firmware/tests/unit/src/test_lab_glitch_commands.c b/firmware/tests/unit/src/test_lab_glitch_commands.c new file mode 100644 index 0000000..b6d40da --- /dev/null +++ b/firmware/tests/unit/src/test_lab_glitch_commands.c @@ -0,0 +1,81 @@ +#include "../lib/unity.h" +#include "../../../dvh/labs/03_glitch/src/lab_glitch_commands.c" +#include "../../../dvh/labs/03_glitch/src/lab_glitch_data.c" +#include "../../../dvh/shared/src/utils_secrets.c" +#include "../mocks/mock_utils_uart.c" +#include "../mocks/mock_utils_screen.c" +#include + +Lab_Glitch_State state; + +void setUp(void) { + SPY_Screen_Clear(); + Lab_Glitch_Cmd_InitState(&state); +} + +void tearDown(void) {} + +void test_Lab_Glitch_Cmd_ProcessByte_should_printui(void) { + Lab_Glitch_Cmd_ProcessByte(&state, 0x00, 0); + + TEST_ASSERT_TRUE(state.ui_printed); + TEST_ASSERT_NOT_NULL(strstr(SPY_Screen_LastText, "Running integrity checks")); +} + +void test_Lab_Glitch_Cmd_ProcessByte_should_notprintui(void) { + state.ui_printed = true; + Lab_Glitch_Cmd_ProcessByte(&state, 0x00, 0); + + TEST_ASSERT_NULL(strstr(SPY_Screen_LastText, "Running integrity checks")); +} + +void test_Lab_Glitch_Cmd_ProcessByte_should_printcorrupted(void) { + Lab_Glitch_Cmd_ProcessByte(&state, 0xc0, 10000); + TEST_ASSERT_NOT_NULL(strstr(SPY_Screen_LastText, "CORRUPTED")); +} + +void test_Lab_Glitch_Cmd_ProcessByte_should_printunstable(void) { + Lab_Glitch_Cmd_ProcessByte(&state, 0xca, 10000); + TEST_ASSERT_NOT_NULL(strstr(SPY_Screen_LastText, "UNSTABLE")); + TEST_ASSERT_TRUE(state.flag_one_printed); +} + +void test_Lab_Glitch_Cmd_ProcessByte_should_printverified(void) { + Lab_Glitch_Cmd_ProcessByte(&state, 0xcb, 1000); + TEST_ASSERT_NOT_NULL(strstr(SPY_Screen_LastText, "VERIFIED")); + TEST_ASSERT_TRUE(state.flag_two_printed); +} + +void test_Lab_Glitch_Cmd_ProcessByte_should_ratelimit(void) { + Lab_Glitch_Cmd_ProcessByte(&state, 0xc0, 100); + TEST_ASSERT_EQUAL(state.last_dot, 0); + + Lab_Glitch_Cmd_ProcessByte(&state, 0xc0, 600); + TEST_ASSERT_EQUAL(state.last_dot, 600); + + Lab_Glitch_Cmd_ProcessByte(&state, 0xca, 100); + TEST_ASSERT_EQUAL(state.last_crash, 0); + + Lab_Glitch_Cmd_ProcessByte(&state, 0xca, 1500); + TEST_ASSERT_EQUAL(state.last_crash, 1500); +} + +void test_Lab_Glitch_Cmd_ProcessByte_should_printgarbage(void) { + state.ui_printed = true; + Lab_Glitch_Cmd_ProcessByte(&state, 0xbb, 0); + TEST_ASSERT_EQUAL_HEX8(SPY_Screen_LastText[0], 0xbb); +} + +int main(void) { + UNITY_BEGIN(); + + RUN_TEST(test_Lab_Glitch_Cmd_ProcessByte_should_printui); + RUN_TEST(test_Lab_Glitch_Cmd_ProcessByte_should_notprintui); + RUN_TEST(test_Lab_Glitch_Cmd_ProcessByte_should_printcorrupted); + RUN_TEST(test_Lab_Glitch_Cmd_ProcessByte_should_printunstable); + RUN_TEST(test_Lab_Glitch_Cmd_ProcessByte_should_printverified); + RUN_TEST(test_Lab_Glitch_Cmd_ProcessByte_should_ratelimit); + RUN_TEST(test_Lab_Glitch_Cmd_ProcessByte_should_printgarbage); + + return UNITY_END(); +} diff --git a/firmware/tests/unit/src/test_utils_isp.c b/firmware/tests/unit/src/test_utils_isp.c new file mode 100644 index 0000000..b7b5d2e --- /dev/null +++ b/firmware/tests/unit/src/test_utils_isp.c @@ -0,0 +1,11 @@ +#include "../lib/unity.h" + +void setUp(void) {} + +void tearDown(void) {} + +int main(void) { + UNITY_BEGIN(); + + return UNITY_END(); +} diff --git a/firmware/tests/unit/src/test_utils_secrets.c b/firmware/tests/unit/src/test_utils_secrets.c index 0566ebf..beed2f5 100644 --- a/firmware/tests/unit/src/test_utils_secrets.c +++ b/firmware/tests/unit/src/test_utils_secrets.c @@ -1,12 +1,9 @@ #include "../lib/unity.h" #include "../../../dvh/shared/src/utils_secrets.c" +#include "../mocks/mock_utils_uart.c" #include #include -void Utils_UART_Writeline(const char* text) { - strncat(SPY_UART_Buffer, text, sizeof(SPY_UART_Buffer) - strlen(SPY_UART_Buffer) - 1); -} - void setUp(void) {} void tearDown(void) {} diff --git a/firmware/tests/unit/src/test_utils_shell.c b/firmware/tests/unit/src/test_utils_shell.c index 86a7b31..1105dcf 100644 --- a/firmware/tests/unit/src/test_utils_shell.c +++ b/firmware/tests/unit/src/test_utils_shell.c @@ -1,16 +1,9 @@ #include "../lib/unity.h" #include "../../../dvh/shared/src/utils_shell.c" +#include "../mocks/mock_utils_uart.c" #include "stm32f1xx_hal.h" #include -void Utils_UART_Writeline(const char* text) { - strncat(SPY_UART_Buffer, text, sizeof(SPY_UART_Buffer) - strlen(SPY_UART_Buffer) - 1); -} - -void Utils_UART_Readline_Ex(char* buffer, uint16_t max_len, Utils_UART_EchoModeTypeDef mode) { - if (max_len > 0) buffer[0] = '\0'; -} - char LAST_ARGS[64]; Utils_Shell_StatusTypeDef Cmd_Test(char* args) { if (args) strcpy(LAST_ARGS, args);