This challenge is about reversing what a piece of C code does. We get a file with a bunch of function calls and need to look at what exactly it does behind the scenes. Myself being a noob at C programming, I was a bit intimidated by the challenge and doubted if I could solve it. But using logical thinking and looking up documentation online it was a very doable challenge.
The Challenge
After downloading and extracting the zip we get a file called chal.c
containing lots of function calls to some gpio library:
#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);
... // About 120 lines of this
gpio_set_mask(2);
gpio_clr_mask(117);
sleep_us(100);
gpio_put_all(0);
sleep_ms(500);
}
return 0;
}
There was also a file called pico.uf2
. I'm did not use this in my solution but it can probably be used to solve the challenge in a different way.
Analysing
I was interested in what all these function calls were in the for loop. Looking at the first value it looks like possibly an ASCII value, and converting it to ASCII results in the capital letter C. All the previous flags were in the format CTF{...}
so the first character being a C is pretty likely.
But the rest of the numbers seem not to be in any ASCII range, meaning we need to do more digging.
Searching the function name gpio_set_mask
and gpio_clr_mask
on Google gives us documentation on what they do.
From gpio_set_mask() I found that the set
function sets all bits in the mask given as the argument to high or 1.
And gpio_set_mask() does a similar thing but instead sets all the bits in the mask to low or 0.
Then you can see the value that it is changing as a global variable, that all the functions work on.
Now it also makes sense why the rest of the values don't seem like ASCII, because we keep changing bits of the previous value, iterating on it over and over.
It is likely that every time the sleep_us
function is called we have a value to read and a letter of the flag.
Solution
Now we can use something like Python to do this for us and display the letter each time through the whole code.
To simulate gpio_set_mask()
, by setting all bits of our global variable to high if the mask has a 1 in that place, we can simply use an OR
gate with the two. In Python, this is the |
operator. So we can simply use value |= mask
to do this operation.
Next, we need to simulate gpio_set_mask()
, by settings all the bits of our global variable to low if the mask has a 1 in that place. We can again use a simple NAND
gate because this will first invert the mask, and then set all bits that are 0 in the mask to 0 in the global value. In Python, this is also simple to do with value &= ~mask
because ~
inverts the value bitwise, and then the &
to AND
the two values together.
Finally, we can use the sleep_us()
function to print the current value as an ASCII value. And don't forget to set the end of the print statement to an empty string as Python automatically adds newlines after every print.
Combining all this with the function calls we get the following script:
value = 0
def gpio_set_mask(mask):
# Set high
global value
value |= mask
def gpio_clr_mask(mask):
# Set low
global value
value &= ~mask
def sleep_us(_):
print(chr(value), end="")
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)
This goes through all the code and prints the flag character by characters resulting in the challenge flag!
CTF{be65dfa2355e5309808a7720a615bca8c82a13d7}