GreyCat2K24

Introduction

For GreyCTF, we were exploring the idea of making an electronic badge since April. Personally, I had experience with PCB Design & Microcontroller Programming for hobby projects, CCA projects as well as for work, so theoretically, I had the necessary skillsets needed to make it work. Furthermore, many local cybersecurity events such as Cyberthon, SINCON, STANDCON, and Off-By-One Conference had their own badges, and so we were motivated to try something like that for ourselves.

1d8TgO_c2qnU1662hLprt-pRmlPbQ4aVtOTihozOs.png|250 1VvgrLZcaHOyMn90qEc8CC9zvwGb0AyZnHGoywhdv.png|350 Pasted image 20240802085230.png|450 1AL5zroDMN8RJzmvnrNfDThYxb1i7SZkZEaPqXLxD.png|300

In about Mid May, we initiated and started planning the project. After about 2 months of effort, we managed to design and produce 300 badges!


Features

I did cover some of it in the slides, but I'll briefly go through them here as needed.

https://docs.google.com/presentation/d/1Xcsd1hn-EbjpaH8gOGFS3LMsyoP91qMkbsXBeuZRDfI/edit?usp=sharing

https://tinyurl.com/greyctfsummit24hw

Lighting

The slide is mostly self explanatory
Pasted image 20240802113814.png

Expandable

The board has debug pads at the body of the cat where you can solder headers. You can control these GPIO pins, send/ receive UART data etc. For my own board, I soldered it as such (bent the pins 90 degrees and solder upright).

You can also add Hot Glue to the hat for better diffusion of the hat leds. We had time/ manpower constraints which did not make it very worth for us to do it for every badge (the effect improvement is not that great), but you can experiment with it!

Pasted image 20240802105450.png|400

You can also solder headers at the front for a "whiskers" effect

Pasted image 20240802113702.png|300

Programmable

The Badges were intentionally designed to be reprogrammable, so that after the event, participants can run their own programs and experiment with STM32 (or even use it as a development board)

Bootloader

To boot into the bootloader short BOOT1 to GND (alternatively, type in the flag grey{what_flag_just_boot1_bro}into the related CTF challenge and press reset)

DO NOT upload the firmware that you dumped from the board - there are some known issues with this. You can compile Blue Pill code using PlatformIO/ STM32CubeMX and then upload the compiled binaries.

Another way is to program the board is using a J-Link/ ST-Link, those can be found online.

New Firmware

Right before GreyCTF Summit, I updated the firmware, adding a menu system, fixing the image upload and adding games like pong

Pasted image 20240811074436.png|300

Pasted image 20240811074617.png|300

Pasted image 20240811074537.png|300

One of the challenges in the hardware booth for GreyCTF 2024 Summit was to put the badge into bootloader mode and update the firmware to the new version. (There were other challenges such as solving the other onboard challenges & controlling a servo, but those were cut due to time constraints)

The new firmware can be found on the github.

Custom Code

Adding your own custom code is definitely possible, you can start off with one of the PlatformIO projects on Github, modify it, compile to a bin file, and then upload through the bootloader.


Challenges

Hornet 9 Hw

I wanted a simple trivia style challenge, like most other badges I knew (hmm Cyberthon). This would hopefully get participants to appreciate hardware more, as well as prepare them for higher level challenges regarding the the STM32 (as needed)

I was told to give credit, so here's the Hornet 9 AAR quote:

we propose changing the homework from multiple assignments to a single larger one, ideally one which is able to better showcase thought process of students while doing the assignment rather than “who is better at Googling” (@elec)

So here's the code (and answers)

int chall_qna(){
  HAL_Delay(1000);
  fastPrintString("Mini Trivia Quiz > \nWe'll be asking you a few simple questions relating to this board.\nPlease answer all in lowercase:\n—-----------------------------------------------------------------");
  if (
    qna("\nWhat is the microcontroller (MCU) chip used?", "stm32f103c8t6") &&
    qna("\nWhat is a popular development board using this MCU?", "bluepill") &&
    qna("\nWhat is the smallest SMD size of a resistor on the board?", "0603") &&
    qna("\nWhat company provides manufacturing of PCBs from $2 (please sponsor us)?", "jlcpcb") &&
    qna("\nWhat does HAL stand for?", "hardware abstraction layer") &&
    qna("\nWhat is the logic level (voltage) used in this board?", "3.3v") &&
    qna("\nWhat usb class are you using to communicate to this board?", "communications device class")
  ) {
    fastPrintString("\nCorrect! Here you get a flag -> grey{STM32_ExP3R1}\n");
    badge_state_set_unlock_status_bit(UNLOCK_BIT_YELLOWHAT);
  } else {
    fastPrintString("\nTry again\n");
  }
}

I tried to map out how participants could have derived the answers to avoid making it feel guessy

  1. Read off the chip label
  2. Google stm32f103c8t6 - https://stm32-base.org/boards/STM32F103C8T6-Blue-Pill.html - iterate through the combinations (space, no space)
  3. Google SMD resistor size - have a list to spam
  4. Google $2 PCB - jlcpcb.com
  5. Google STM32 HAL - in 1 of the results
  6. Google STM32 Logic Level
  7. Google USB Class - https://www.usb.org/defined-class-codes - CDC - USB CDC

Some potential problems with the challenge is that

  1. I should have properly stripped the text input (remove \r for example)
    1. This caused some issues, and I had to address this during the CTF
  2. I should have allowed multiple answer strings (eg. blue pill, bluepill, etc.)
  3. A fair bit of people got stuck on usb class, so it might have helped to make that clearer.

Flag: grey{STM32_ExP3R1}

Short

I wanted a simple challenge to get participants exposed & familiar to glitching and wiring, even if they don't have past experience/ are not very good

The solution is to connect 2 pads together as told by the badge

void chall_hyper_glitch(){
    if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_15) == 0){
        usbPrintf(MAX_BUF_LEN, "grey{d3Bu9_P4D5}");
        badge_state_set_unlock_status_bit(UNLOCK_BIT_REDHAT); 
    }else{
        usbPrintf(MAX_BUF_LEN, "Connect PB15 to GND");
    }
}

Flag: grey{d3Bu9_P4D5}

Name

I wanted a simple pwn challenge to get across the idea that even though it is hardware, fundamentally the device is still coded in C, and there are various vulnerabilities that come with that. I couldn't exactly code this in a very nice way, but I hope I did get the intent across.

The intended solution is to understand that stm32 uses printf (if you code in the HAL libraries, there are printf functions), and enter the name as %s to read the flag

void chall_name(){
    volatile char flag[128] = "grey{you_can_printf_on_stm32?}";
    usbPrintf(MAX_BUF_LEN, "\nEnter your name> ");
    
    char input_ans[STATE_NAME_SIZE];
    memset(input_ans, 0, STATE_NAME_SIZE);
    int ans_len = readString(input_ans, STATE_NAME_SIZE-1);
    input_ans[ans_len] = '\0';
    
    usbPrintf(MAX_BUF_LEN, input_ans, flag);
    usbPrintf(MAX_BUF_LEN, "\n");
    
    if (strstr(input_ans, "%s") != NULL) {
        badge_state_set_unlock_status_bit(UNLOCK_BIT_BLUEHAT);
    }

    oled_update_name(input_ans);
    badge_state_update_name(input_ans);

    usbPrintf(MAX_BUF_LEN, "Updated!\n");
}

Pain Points

  1. The hint in the challenge was the keyword format, but it might have been too small to catch. Maybe changing the title of the challenge would have helped
  2. If possible it might be fun to code a more complex pwn challenge, but for now this would do (I am very rusty at pwn)

Flag: grey{you_can_printf_on_stm32?}

Serialous

This challenge was supposed to be more cancerous (you can guess), but as I got a bit tired/ burnt out, I scaled back the complexity of the challenge to something simpler.

The intended solution is to connect 2 badges together to read the uart data. 1 badge is configured as receiver, 1 badge as sender.

RX  <-  TX
GND <-> GND (or 3.3V - 3.3V)

UART baud rate is 9600 (can google and guess). If you don't enter a baud rate, the badge defaults to 9600 anyway.

An important part of this challenge is realising you DO NOT NEED to connect RX - TX AND TX - RX. You only need 1 wire for the transmission of data, and 1 more wire for a reference voltage. I remember 1 team got confused by this and didn't connect the reference voltage.

Pain Points

  1. I forgot to change the flag in the writeup... I'm sorry to the team who actually first blooded this and let me know about it.
  2. We needed to clarify that each badge needed 1 USB cable. As some teams only brought 1 for the whole team, this led to some issues when they tried to solve the challenge. For the CTF, I loaned my USB cable to teams who needed it (or at least offered to loan)
    1. We tried to inform it in the Discord, but the instructions might not have been clear enough/ some teams did not think so far.

Flag: grey{uart_read}

Bootloader

This was also meant to be a more hacky challenge :) but I got lazy.

I wanted a simple rev challenge, but the challenge was focused on how to even get the binary from the device, and how do you rev a raw binary (not an elf). The rough concept was to pull boot1 high (to 3.3v) to bypass password, dump out the firmware, and reverse XORs. However, reversing the binary has its own set of challenges (that I didn't even think of when making the challenge).

Here's elma's rough guideline on this challenge

  1. ghidra, remember to rebase to 0x8000000
  2. look at strings
  3. xref the flag prompt to find the function
  4. most library functions are guessable
  5. can see the simple xor
  6. can either look at the STM firmware manual to find memory layout and map the SRAM, or brute force the entire binary against itself
#define CMD_BOOT 0x4F42UL

#define BOOT_FLAG_LEN 30
// grey{what_flag_just_boot1_bro}
uint8_t boot_flag_array1[BOOT_FLAG_LEN] = {20, 99, 10, 94, 20, 9, 46, 98, 9, 7, 57, 56, 30, 100, 86, 88, 48, 41, 56, 72, 31, 78, 26, 9, 4, 86, 14, 54, 100, 34};
uint8_t boot_flag_array2[BOOT_FLAG_LEN] = {115, 17, 111, 39, 111, 126, 70, 3, 125, 88, 95, 84, 127, 3, 9, 50, 69, 90, 76, 23, 125, 33, 117, 125, 53, 9, 108, 68, 11, 95};
void chall_bootloader(){
    usbPrintf(MAX_BUF_LEN, "\nEnter flag to reboot into bootloader> ");

    uint8_t input_ans[BOOT_FLAG_LEN+1];
    memset(input_ans, 0, BOOT_FLAG_LEN+1);
    uint8_t ans_len = readString(input_ans, BOOT_FLAG_LEN);
    usbPrintf(MAX_BUF_LEN, "%s\n", input_ans);

    if (ans_len != BOOT_FLAG_LEN){
        usbPrintf(MAX_BUF_LEN, "WRONG!!!");
        return;
    }

    for (int i=0; i<BOOT_FLAG_LEN; i++){
        if ((input_ans[i] ^ boot_flag_array1[i]) != boot_flag_array2[i]){
            usbPrintf(MAX_BUF_LEN, "WRONG!!!");
            return;
        }
    }

    // Reboot into bootloader
    badge_state_set_unlock_status_bit(UNLOCK_BIT_PURPLEHAT);
    
    HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, CMD_BOOT);
    usbPrintf(MAX_BUF_LEN, "Success - Press reset to reboot into bootloader!!!");
    //usbPrintf(MAX_BUF_LEN, " - bootloader not implemented yet\n");
    ans_len = readString(input_ans, BOOT_FLAG_LEN);
}

Pain Points

  1. About 3 teams bricked their device by patching the binary and flashing that firmware :( I had personally tried doing something similar of testing my challenge but I didn't expect this to be a big issue.
    1. I ended up reflashing the badges for some of the affected teams, but if there was a better way to avoid this situation it would have been better.

Flag: grey{what_flag_just_boot1_bro}

Overall

Overall, considering the points decay, I think it worked out quite well. Serialous was supposed to be harder but, I think it still worked out.

Pasted image 20240802104519.png|700

Solved Teams (there are 20 teams, 19 present)

  1. Hornet 9 Hw - 17
  2. Short - 19
  3. Serialous - 14
  4. Name - 10
  5. Bootloader - 4

Others

Feel free to check out 2024_07_31_greycat2k24_execution_deep_dive and 2024_08_01_a_summers_pain_in_stm32 for more comprehensive details about how the badge was made and what I've learnt along the way!