DownUnderCTF 2024
Table-of-contents
Hardware
Down under had quite the interesting setup for a series of hardware based CTF challenges. Instead of just reversing some embedded firmware, they crafted chalenges that required uploading rpi2040 firmware to their web interface, that would then be run on real hardware circuts to get the flag.
I See [291 points, 61 solves, Easy]
Somewhat of a warmup, we are provided with this schematic
The symbol to the right is an eeprom, with its E0-E2 pins pulled high, write_protect pulled high, and its i2c lines connected to our IO footprint.
Given no other context, the reasonable approach is to dump the eprom memory to see if a flag is in there, this code got the flag from the eprom.
#include <stdint.h>
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#define I2C_PORT i2c0
#define EEPROM_ADDRESS 0b1010111
#define SDA_PIN 4
#define SCL_PIN 5
// UART0 for cluster communication (115200 baud)
#define UART_CLUSTER uart0
#define UART_CLUSTER_TX_PIN 0
#define UART_CLUSTER_RX_PIN 1
#define UART_CLUSTER_BAUD_RATE 115200
uint8_t i2c_read_byte(uint8_t address) {
uint8_t data;
i2c_write_blocking(I2C_PORT, EEPROM_ADDRESS, &address, 1, true); // Send the address
i2c_read_blocking(I2C_PORT, EEPROM_ADDRESS, &data, 1, false); // Read the data
return data;
}
int main() {
stdio_init_all();
i2c_init(I2C_PORT, 100 * 1000); // 100 kHz
// Initialize UART0 (Cluster)
uart_init(UART_CLUSTER, UART_CLUSTER_BAUD_RATE);
gpio_set_function(UART_CLUSTER_TX_PIN, GPIO_FUNC_UART);
gpio_set_function(UART_CLUSTER_RX_PIN, GPIO_FUNC_UART);
gpio_set_function(SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(SCL_PIN, GPIO_FUNC_I2C);
gpio_pull_up(SDA_PIN);
gpio_pull_up(SCL_PIN);
uint8_t d = 0;
for (int i = 0; i < UINT8_MAX; i++) {
d = i2c_read_byte(i);
sleep_ms(10); // EEPROM write delay
printf("%c", d);
}
printf("\n");
return 0;
}
The Door [335, 15 solves, Medium]
This time the circut is much more complicated.
The goal is to flip the output of flag_release presumably. Looking up the datasheets for the symbols, its clear the circuit is comparing whatever value we output to a 32-bit secret value, linking together each 8bit comparator, requiring them all to match to set the flag pin.
Normally in a ctf, brute forcing something in the 32bit range is likely not going to work, but this setup is a bit different. Since we are uploading firmware that gets uploaded to the device, we can try values very quickly. The thing that will be somewhat tricky, is that our payloads are time constrained on their infrastructure, and we write out our value one bit at a time through the shift registers in the circuit.
Iterating through all 32bit values in order will be too slow. Thinking of how to quickly trigger all possible values, my mind first jumped to a lfsr. LFSR’s are circuits that produce sequence of bits, by shifting their state by one bit and determining the new bit by a linear function on its prior state. These sequences can be non-repeating for a cycle equal to the number of bits of the lfsr with the right, linear function.
So if we use a lfsr to shift through all possible values of 32bits, one bit change at a time, we will have tried all values only needing to output 2^32 bit flips, rather than 32x as many bits.
Here was my code for the solution:
#include <stdint.h>
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#define WCLK 3
#define RCLK 4
#define WDAT 5
#define UART_CLUSTER uart0
#define UART_CLUSTER_TX_PIN 0
#define UART_CLUSTER_RX_PIN 1
#define UART_CLUSTER_BAUD_RATE 115200
void clock_cycle(bool value) {
gpio_put(WDAT, value);
// sleep_us(1);
gpio_put(WCLK, 1);
gpio_put(RCLK, 1);
// sleep_us(1);
gpio_put(WCLK, 0);
gpio_put(RCLK, 0);
}
uint32_t lfsr = 0xACE1ACE1;
uint32_t lfsr_next() {
uint32_t bit = ((lfsr >> 31) ^ (lfsr >> 21) ^ (lfsr >> 1) ^ lfsr) & 1;
lfsr = (lfsr >> 1) | (bit << 31);
return bit;
}
int main() {
stdio_init_all();
uart_init(UART_CLUSTER, UART_CLUSTER_BAUD_RATE);
gpio_set_function(UART_CLUSTER_TX_PIN, GPIO_FUNC_UART);
gpio_set_function(UART_CLUSTER_RX_PIN, GPIO_FUNC_UART);
gpio_init(WCLK);
gpio_set_dir(WCLK, GPIO_OUT);
gpio_init(RCLK);
gpio_set_dir(RCLK, GPIO_OUT);
gpio_init(WDAT);
gpio_set_dir(WDAT, GPIO_OUT);
gpio_put(WCLK, 0);
gpio_put(RCLK, 0);
gpio_put(WDAT, 0);
// feed in state
for (int i = 0; i < 32; i++) {
uint8_t b = (lfsr >> i) & 0x1;
clock_cycle(b);
}
uint32_t initial_state = lfsr;
uint64_t count = 0;
do {
clock_cycle(lfsr_next());
count++;
} while (lfsr != initial_state);
printf("finished");
return 0;
}
The flag references de Brujin sequences, which are the like the pure math equivalent of LFSR’s.