Before this CTF, I had never done any hardware-related challenges. I did have a little experience with Arduino from school, so I thought why not give it a try. This was a great introductory challenge for hardware, and I learned a lot about how Arduino and the wire connections work.
Challenge
We were given an image of the circuit. It uses an Arduino board and a breadboard with quite a few wires and some sort of module in the middle.
We also got the Arduino source code for the challenge, which contained some code to set up the input and output pins, and some logic to print out the flag.
char * flag = "REDACTED";
String curr, first, second;
int in1=29, in2=27, in3=25, in4=23;
int out1=53, out2=51, out3=49, out4=47;
int i;
String get_output(String bits) {
String output;
// Set input bits high
digitalWrite(out1, ((bits[0] == '1')? HIGH : LOW));
digitalWrite(out2, ((bits[1] == '1')? HIGH : LOW));
digitalWrite(out3, ((bits[2] == '1')? HIGH : LOW));
digitalWrite(out4, ((bits[3] == '1')? HIGH : LOW));
delay(1000); // Wait a second
// Save output bits
output += String(digitalRead(in1));
output += String(digitalRead(in2));
output += String(digitalRead(in3));
output += String(digitalRead(in4));
return output;
}
// Integer to binary string
String binary(int number) {
String r;
while(number!=0) {
r = (number % 2 == 0 ? "0" : "1")+r;
number /= 2;
}
while ((int) r.length() < 8) {
r = "0"+r;
}
return r;
}
void setup() { // Setup pins
i = 0;
pinMode(out1, OUTPUT);
pinMode(out2, OUTPUT);
pinMode(out3, OUTPUT);
pinMode(out4, OUTPUT);
pinMode(in1, INPUT);
pinMode(in2, INPUT);
pinMode(in3, INPUT);
pinMode(in4, INPUT);
Serial.begin(9600);
}
void loop() {
if (i < strlen(flag)) {
curr = binary(flag[i]); // Binary character value
first = curr.substring(0,4); // First 4 bits
second = curr.substring(4,8); // Last 4 bits
Serial.print(get_output(first));
Serial.println(get_output(second));
delay(1000);
i++;
}
}
The last loop()
function is the most interesting because there is the logic of how the flag gets printed to the Serial output. Lastly, we also get this serial output in a file with lots of binary strings:
These binary strings are the output of the program, so we need to somehow reverse these back into the flag.
Analysing the code
Looking at the loop()
function, we see that it just loops through every character of the flag. Then it converts the character value to binary and splits it into two strings, one with the first 4 bits, and one with the last 4 bits.
After that, it calls the get_output()
function, on both of these strings. This function sets the bits from the binary string to high in the input pins and then waits a second. Finally, it reads the output pins and returns the binary string of the output. It basically puts the flag through the circuit and reads the output.
The Circuit
So our goal is now to understand what happens in the circuit, and how we can reverse the binary strings back into the flag. We can see a lot of wires going from the Arduino to the breadboard. If we follow the wires we can see that the pins are connected as follows:
GND 27 5V 51 29 GND 53
│ │ │ │ │ │ │
┌┴───┴───┴───┴───┴───┴───┴┐
│ CHIP │
└┬───┬───┬───┬───┬───┬───┬┘
│ │ │ │ │ │ │
25 GND 49 23 5V 47 5V
In the challenge, we also get another file, a photo of the setup in the real world. This is a very high-resolution image, and when we zoom in on the chip we can see the exact model:
We can read the following:
If we now search on Google for the MM74HC86H
string, we get a datasheet of the exact model!
This datasheet shows all how all the connections go through the chip. It looks like it just XORs a few pins with each other.
Reversing the Circuit
Now that we have the exact model of the chip, we can reverse its logic to get back the flag. To play around with this circuit I used a program I had also used for school called Logisim. The program allows you to build a circuit, and then test different inputs to see what comes out. So I built the circuit in the program:
We can see that it's all XOR operations with an input pin, then either 5V or GND and then the output pin as output. We can translate the XOR operation into basically just flipping the input pin if the other input (5V or GND) is on. And since 5V = ON
, and GND = OFF
, we can just flip the input pin whenever 5V is used.
With this logic, the only thing this really does is flip the bit if the input is pin 47 or 51. If we look back at the Arduino code on the following line we can see that these correspond to bits 1 and 3:
If we remember how the Arduino code worked from earlier, it just put the first 4 bits of the character through this circuit and the last 4 bits. Meaning we only have to flip these bits to get back the flag. Putting this in Python code looks something like this:
output = []
with open("output.txt", "r") as f:
output = [line.strip() for line in f.readlines()]
def flip_bit(binary, index):
"""Flips bit with index from right to left"""
index = len(binary) - index
flipped = 1 if binary[index] == "0" else 0 # Flip bit
return binary[:index] + str(flipped) + binary[index+1:] # Start + flipped + end
def decode(binary):
binary = flip_bit(binary, 1)
binary = flip_bit(binary, 3)
return binary
flag = b""
for binary in output:
first = binary[:4]
last = binary[4:]
decoded = decode(first) + decode(last)
flag += bytes([int(decoded, 2)]) # Decode from binary
print(flag)
This script flips the 1 and 3 bits from both parts and puts them back together to form the flag character by character. And at the end, prints the flag!
flag{a16b8027cf374b115f7c3e2f622d84bc}