+++ author = "Maik de Kruif" title = "Secret Location - Base" subtitle = "Beginners Quest 4 - Google CTF" date = 2021-09-24T16:33:25+01:00 description = "A writeup for challenge 4 of the beginners quests of the Google CTF." cover = "img/writeups/google-ctf/2021/beginners-quest/4/cover.png" tags = [ "Google CTF", "Beginners Quest", "ctf", "hacking", "writeup", "web", ] categories = [ "ctf", "writeups", "hacking", ] +++ ## Story line 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](/files/writeups/google-ctf/2021/beginners-quest/4/attachment.zip) ## 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` {{< code language="c" title="chal.c" isCollapsed="true" >}} ```c #include #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; } ``` {{< /code >}} 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](https://raspberrypi.github.io/pico-sdk-doxygen/group__hardware__gpio.html): {{< figure src="/img/writeups/google-ctf/2021/beginners-quest/4/doc-functions.png" title="Functions documentation" >}} {{< figure src="/img/writeups/google-ctf/2021/beginners-quest/4/doc-set-mask.png" title="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. ```py 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: ```py 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()) ``` {{< code language="py" title="Full code" isCollapsed="true" >}} ```py 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()) ``` {{< /code >}} ## Solution When executing this code, we get the flag! It's `CTF{be65dfa2355e5309808a7720a615bca8c82a13d7}`.