Story line

Secret Location - Base

“Welcome back AGENT. It seems like you’ve got a marvelous lead that perhaps gives a clue about where you should head to next. Visit the lab, and talk to that Dr. Klostermann, or is it Cloysterman?, he will know how to decrypt the device.. you would think”. … Dr Klostermann: “Welcome to the technical department AGENT, I’m Dr. Klostermann, and this is my assistant, Konstantin. Let’s not waste any time, is that the device that you’re holding in your hand? Konstantin, start the basic procedure.”

Challenge: Electronics Research Lab (hw)

Welcome back AGENT. It seems like you got a lead that perhaps gives a clue about where the next journey on your quest goes. Visit the lab, and talk to Dr. Klostermann, he will know how to decrypt the device.

After solving

You’re taking a stroll in the lab, when Dr. Klostermann is calling your name: “Agent, we’ve discovered the origin of the device. This time you won’t be able to reach your destination by air, but by the new Trans-Sibiriean Railway, as opposed to the old one, which runs along side it at the same time, it is a bit odd. And it goes to Shenzhen. I am sorry agent, but the further you go into this task, the more precautions you will have to take, and remember, the enemy can be anyone. It could be a conductor, the engineer, it could even be our own people that will meet you at the spot you need to be at. Be selective with who you trust. I think you got the point, go now, I got much to do. Agent, much depends on you!.”

Attachment

attachment.zip

#include <stdbool.h>

#include "hardware/gpio.h"
#include "hardware/structs/sio.h"
#include "pico/stdlib.h"

int main(void)
{
  for (int i = 0; i < 8; i++) {
    gpio_init(i);
    gpio_set_dir(i, GPIO_OUT);
  }
  gpio_put_all(0);

  for (;;) {
    gpio_set_mask(67);
    gpio_clr_mask(0);
    sleep_us(100);
    gpio_set_mask(20);
    gpio_clr_mask(3);
    sleep_us(100);
    gpio_set_mask(2);
    gpio_clr_mask(16);
    sleep_us(100);
    gpio_set_mask(57);
    gpio_clr_mask(4);
    sleep_us(100);
    gpio_set_mask(0);
    gpio_clr_mask(25);
    sleep_us(100);
    gpio_set_mask(5);
    gpio_clr_mask(2);
    sleep_us(100);
    gpio_set_mask(18);
    gpio_clr_mask(65);
    sleep_us(100);
    gpio_set_mask(1);
    gpio_clr_mask(2);
    sleep_us(100);
    gpio_set_mask(64);
    gpio_clr_mask(17);
    sleep_us(100);
    gpio_set_mask(2);
    gpio_clr_mask(0);
    sleep_us(100);
    gpio_set_mask(1);
    gpio_clr_mask(6);
    sleep_us(100);
    gpio_set_mask(18);
    gpio_clr_mask(65);
    sleep_us(100);
    gpio_set_mask(1);
    gpio_clr_mask(0);
    sleep_us(100);
    gpio_set_mask(4);
    gpio_clr_mask(2);
    sleep_us(100);
    gpio_set_mask(0);
    gpio_clr_mask(0);
    sleep_us(100);
    gpio_set_mask(64);
    gpio_clr_mask(16);
    sleep_us(100);
    gpio_set_mask(16);
    gpio_clr_mask(64);
    sleep_us(100);
    gpio_set_mask(2);
    gpio_clr_mask(4);
    sleep_us(100);
    gpio_set_mask(0);
    gpio_clr_mask(3);
    sleep_us(100);
    gpio_set_mask(9);
    gpio_clr_mask(0);
    sleep_us(100);
    gpio_set_mask(0);
    gpio_clr_mask(1);
    sleep_us(100);
    gpio_set_mask(0);
    gpio_clr_mask(8);
    sleep_us(100);
    gpio_set_mask(8);
    gpio_clr_mask(0);
    sleep_us(100);
    gpio_set_mask(65);
    gpio_clr_mask(24);
    sleep_us(100);
    gpio_set_mask(22);
    gpio_clr_mask(64);
    sleep_us(100);
    gpio_set_mask(0);
    gpio_clr_mask(0);
    sleep_us(100);
    gpio_set_mask(0);
    gpio_clr_mask(5);
    sleep_us(100);
    gpio_set_mask(0);
    gpio_clr_mask(2);
    sleep_us(100);
    gpio_set_mask(65);
    gpio_clr_mask(16);
    sleep_us(100);
    gpio_set_mask(22);
    gpio_clr_mask(65);
    sleep_us(100);
    gpio_set_mask(1);
    gpio_clr_mask(6);
    sleep_us(100);
    gpio_set_mask(4);
    gpio_clr_mask(0);
    sleep_us(100);
    gpio_set_mask(66);
    gpio_clr_mask(21);
    sleep_us(100);
    gpio_set_mask(1);
    gpio_clr_mask(0);
    sleep_us(100);
    gpio_set_mask(0);
    gpio_clr_mask(2);
    sleep_us(100);
    gpio_set_mask(24);
    gpio_clr_mask(65);
    sleep_us(100);
    gpio_set_mask(67);
    gpio_clr_mask(24);
    sleep_us(100);
    gpio_set_mask(24);
    gpio_clr_mask(67);
    sleep_us(100);
    gpio_set_mask(2);
    gpio_clr_mask(8);
    sleep_us(100);
    gpio_set_mask(65);
    gpio_clr_mask(18);
    sleep_us(100);
    gpio_set_mask(16);
    gpio_clr_mask(64);
    sleep_us(100);
    gpio_set_mask(2);
    gpio_clr_mask(0);
    sleep_us(100);
    gpio_set_mask(68);
    gpio_clr_mask(19);
    sleep_us(100);
    gpio_set_mask(19);
    gpio_clr_mask(64);
    sleep_us(100);
    gpio_set_mask(72);
    gpio_clr_mask(2);
    sleep_us(100);
    gpio_set_mask(2);
    gpio_clr_mask(117);
    sleep_us(100);

    gpio_put_all(0);
    sleep_ms(500);
  }

  return 0;
}

Recon

When opening the attachment, we can find two files: chal.c and pico.uf2.

Before this challenge I had never heard of a uf2 file, but from googling “pico.uf2”, I found that it is probably a firmware file for the Paspberry Pi Pico.

While I would have loved to play around with that, I didn’t have one at hand. So we’ll have to do with the chal.c file (which is probably the file running in the firmware).

chal.c

In this code, we see a lot of calls to the functions gpio_set_mask() and gpio_clr_mask(). When looking up what these to, we find this documentation:

Functions documentation
Functions documentation
gpio_set_mask() documentation
gpio_set_mask() documentation

Bitmasks

In the gpio_set_mask() documentation, we see that it wants a bitmask of GPIO values. So what is a bitmask?

A bitmask is used for logical operations to transform an array of bits using another array. For example, a bitmask can be used with OR:

Operation Value
1001 0101
OR 1111 0000 <- bitmask
= 1111 0101

Solving

To get the values at every sleep(), we just have to apply the masks one by one.

But before we can do that, we first need the individual values to work with. To get them, I wrote the following python script.

set_mask_str = "gpio_set_mask"
clr_mask_str = "gpio_clr_mask"

set_masks = []
clr_masks = []

with open("chal.c", "r") as file:
    for line in file:
        set_mask_loc = line.find(set_mask_str)
        clr_mask_loc = line.find(clr_mask_str)

        end = line.find(")")

        if set_mask_loc != -1:
            set_masks.append(
                int(line[set_mask_loc + len(set_mask_str) + 1:end]))

        if clr_mask_loc != -1:
            clr_masks.append(
                int(line[clr_mask_loc + len(clr_mask_str) + 1:end]))

All this script does, is read the chal.c file line by line. If the line contains either the gpio_set_mask() or the gpio_clr_mask() function, it gets the location of the parameter and appends it to a list.

In the end we get two lists: set_masks and clr_masks.

Applying the masks

Now comes the more difficult part, we have to apply these masks.

For setting the masks, we can use the bitwise OR operation (|). It works like this:

State Value
Initial value 1001 0101
Bitmask 1111 0000
Result 1111 0101

As we can see in this table, it now set the bytes passed to gpio_set_mask() to true.

Clearing the masks is a little harder. For this we’ll need to first invert the bitmask (~), and than use the bistwise AND operation (&) with the current value. It would work like this:

State Value
Initial value 1111 0101
Bitmask 1100 0010
Invert bismask 0011 1101
Result 0011 0101

If we turn this into a python script, it would look like this:

current_output = 0
flag = ""

for set_mask, clr_mask in zip(set_masks, clr_masks):
    current_output |= set_mask
    current_output &= ~ clr_mask
    flag += chr(current_output)

print(flag.strip())
set_mask_str = "gpio_set_mask"
clr_mask_str = "gpio_clr_mask"

set_masks = []
clr_masks = []

with open("chal.c", "r") as file:
    for line in file:
        set_mask_loc = line.find(set_mask_str)
        clr_mask_loc = line.find(clr_mask_str)

        end = line.find(")")

        if set_mask_loc != -1:
            set_masks.append(
                int(line[set_mask_loc + len(set_mask_str) + 1:end]))

        if clr_mask_loc != -1:
            clr_masks.append(
                int(line[clr_mask_loc + len(clr_mask_str) + 1:end]))

current_output = 0
flag = ""

for set_mask, clr_mask in zip(set_masks, clr_masks):
    current_output |= set_mask
    current_output &= ~ clr_mask
    flag += chr(current_output)

print(flag.strip())

Solution

When executing this code, we get the flag! It’s CTF{be65dfa2355e5309808a7720a615bca8c82a13d7}.