Categories

Hey guys !

From October 24th 2020 to November 11th 2020 took place the "Brigitte Friang Operation". It's a Capture The Flag competition organized by the DGSE, the French Directorate-General for External Security. After some introduction challenges (that were already fairly hard to be honest), you'll finally get access to the read CTF, that you could play with a team up to 4 people.

There were 14 challenges total, distributed in different categories (hardware, cryptography, steganography, etc).

The one I want to present today is a hardware challenge.

Here is the orginal datasheet :

Le code d'accès d'un centre militaire de télécommunications est saisi sur un clavier. Un agent a accédé au matériel (Cf. photos face avant et face arrière du clavier) et a inséré un dispositif pour enregister les données binaires qui transitent sur le connecteur du clavier. Le fichier joint (keypad_sniffer.txt) comprend les données binaires (échantillonnées à une fréquence de 15 kHz) au moment où une personne autorisée rentrait le code d'accès. Retrouvez le code d'accès.Le flag est de la forme DGSESIEE{X} où X est le code saisikeypad_sniffer.txt (SHA256=f5660a0b1c8877b67d7e5ce85087138cbd0c061b0b244afc516c489b39a7f79d) : http://challengecybersec.fr/d3d2bf6b74ec26fdb57f76171c36c8fa/keypad_sniffer.txtkeypad_face.jpg (SHA256=b39c0d732f645fc73f41f0955233bec3593008334a8796d2f1208346f927fef2) : http://challengecybersec.fr/d3d2bf6b74ec26fdb57f76171c36c8fa/keypad_face.jpgkeypad_back.jpg (SHA256=1f5d41c3521d04494779e43a4d5fae7cb14aad44e6e99cf36642ff4e88fab69f) : http://challengecybersec.fr/d3d2bf6b74ec26fdb57f76171c36c8fa/keypad_back.jpg

Basically, you get the following file, which is the binary output intercepted on a 16 digit keypad when someone typed in the right password. In addition to that, you get a front and back picture of that keypad.

The first step is to convert the 12 bits we get on each line of the file to standard one-byte format (8 bits). When checking out the backside of the keypad, you'll notice that 4 of the output pins are welded together, leaving 8 wired to the keypad.

We'll leave out the first two of each sequence, and the 7th and 8th. I'll explain why just below.

Next thing to do is to understand how a such keypad works, and how it detects which key was pressed.

The keypad is based on a matrix; with lines and columns.

One of the axis is always on high state. In our case, the lines are. A sweep operation is made on the columns, by sending a low state on each column, one after the other, continuously. In parallel, the lines are checked. If a low state is noticed the key at the intersection of the low state line and column is currently pressed.

Here is how the real thing looks like, for those of you who are interested.

The difficulty is that there is no way to know if the sweep is operated on the lines or columns without knowing the internal wiring (which we do not). I tried both to determine the sweep axis.

Here is a full sweep patern extracted from the file :

101111100111
101111100111
101111100111
101111100111
101111100111
101111100111
101111100111
101111100111
101111101011
101111101011
101111101011
101111101011
101111101011
101111101011
101111101011
101111101011
101111101101
101111101101
101111101101
101111101101
101111101101
101111101101
101111101101
101111101110
101111101110
101111101110
101111101110
101111101110
101111101110
101111101110
101111101110

You'll notice that the bits I said to ignore earlier always are 10. The two other groups illustrate my sayings : the first group of 4 is always 1111 (on high state), and the other group is periodically equal to 0111, 1011, 1101, 1110. The 0 is moving to the right and starts again when finished.

Now in this example, no key was pressed, you'll have to scroll down a long way through the file to get the first changes in that pattern.

101110100111

If we break down this line by hand we get this :

So the key pressed here is "A", because it's at the intersection of the 4th line and the 1st column. If the sweep was done the other way, it would have been the 4th column and the 1st line, so a "F".

The challenges instruction points out that a sampling frequency of 15kHz is used. It means that 15 000 values are taken every second by the sniffer in place. That means you'll never get a single line, but always a few times the same line (often around 7-8 times). We will have to take that into account when extracting the password.

Of course, we can't do this by hand, so here is the script I used. I'll break it down below :

# Reading the file

positions = {"p1": "0111", "p2": "1011", "p3": "1101", "p4": "1110", "inactive": "1111"}

result, lastTested, lastSweep = "", "", ""

for l in content:
# Ignoring the 4 useless bits
line = l[2:6]
column = l[8:12]
# If a key is held down
if line != positions['inactive'] and lastTested != column:
# Storing the last sweep key
lastSweep = column
if line == positions['p1']:
if column == positions['p1']:
result += "1"
elif column == positions['p2']:
result += "2"
elif column == positions['p3']:
result += "3"
elif column == positions['p4']:
result += "F"
elif line == positions['p2']:
if column == positions['p1']:
result += "4"
elif column == positions['p2']:
result += "5"
elif column == positions['p3']:
result += "6"
elif column == positions['p4']:
result += "E"
elif line == positions['p3']:
if column == positions['p1']:
result += "7"
elif column == positions['p2']:
result += "8"
elif column == positions['p3']:
result += "9"
elif column == positions['p4']:
result += "D"
elif line == positions['p4']:
if column == positions['p1']:
result += "A"
elif column == positions['p2']:
result += "0"
elif column == positions['p3']:
result += "B"
elif column == positions['p4']:
result += "C"
# If you do a complete sweep without having the previous key pressed
# add a space (to detect the same key pressed 2 times)
elif line == positions['inactive'] and lastSweep == column:
result += " "
lastSweep = ""
# Storing the very last tested case
lastTested = column

# Removing the duplicats for a same key press (the sampling frequency is high)
flag, t = "", ""
for i in result:
if i != t:
flag += i
t = i
# Removing spaces
flag = flag.replace(" ","")

print("DGSESIEE{"+flag+"}")


First thing I do is read the file and declare my variables. The positions dictionary stores the different states representing the line or column. The result stores the pressed keys, lastTested the state of the column of the very last tested line, and lastSweep stores the last state of the column the last time a low state was detected on the lines.

I then loop through the files lines, and append the corresponding digit to my result, depending on the states. The lastTested variable allows me not to write the same digit 7 ro 8 times because of the samplig frequency. The lastSweep variable allows me to to append a space character when the key is released (when the next sweep on the same column detects an inactive line state).

I still have many times the same value, because of the sweep pattern. I then loop trough the result to remove redundant characters, using the spaces to determine when a single key is really pressed more the once in a row.

Finally I remove the spaces, and print the flag in the right format.

Here you go : DGSESIEE{AE78F55C666B23011924}. I hope you enjoyed, and that I was clear enough (it's a hard topic to explain). I had very much fun solving this challenge, even if I really struggled at first, because I never really gave hardware hacking so much thougth. Regardless, it allowed me to learn much on the topic, and to give the interest to look up deeper this discipline.

If you want other write-ups from that particular CTF, here is my mates website SoEasY : (he didn't write them yet )

Big up à h25 du coup 🙂 Désolé pour la gueule du script