Lately, I have been doing some hardware security research, specifically focusing on side channel stuff, this inspired me to write these challenges for the 0xL4ugh CTF.
You will find all the challenge files on this repo, at the end of the blog I will list some notable writeups by players who have completed this challenge.
Tempus
Summary: Timing side channel attack allows us to bruteforce the pin digit by digit reducing the keyspace significantly (from 10^9 to 10*9).
Tempus is the latin word for time
After spinning up the instance and connecting, we test the challenge logic.
The flag seems to be 9 digits long by elimination, so we know the length of the password. The next step was to check the timing of the responses for different digits. I used the time command to measure this, and it turns out that the digit ‘5’ takes significantly longer to process than the rest, hinting at a possible timing attack.
Non-constant time operations can pose a significant security risk, especially in cryptographic systems. This was demonstrated as early as 1996 by Kocher in his famous paper.
To find the letters of the correct password, we can use the following one-liner:
1
for i in {0..9};do cat <(echo"$i") - |time nc localhost 11111;done
We don’t use echo directly but instead use it along with stdin as parameters for cat. This keeps the connection alive after the echo, and time will give us the correct timing once the netcat connection closes.
Using this one-liner we can start bruteforcing the flag digit by digit:
and the correct pin is 562951413.
562951413 is the reverse of the first 8 digits of pi: 3.14159265
importtimefrompwnimport*importstringimportnumpyasnpfromtqdmimporttqdmdeftry_letter(letter):# io = process("./chal")# host = "f3c7d0159c523472a737fce4144226a6.chal.ctf.ae"# io = remote(host, 443, ssl=True, sni=host)io=remote('localhost',11111)io.recvuntil("Please enter the pin:\r\n")start=time.perf_counter()io.sendline(letter)io.recvuntil("Analyzing...")diff=time.perf_counter()-startio.close()returndiffdefget_timing(attempt,samples=5):times=[try_letter(attempt)for_inrange(samples)]returnnp.mean(times)timings={}flag=""charset=string.digitsforiinrange(9):timings.clear()forchintqdm(charset,position=0):timings[ch]=get_timing(flag+ch,samples=1)max_val=max(timings,key=timings.get)flag+=max_valprint(bytes(flag,encoding="utf-8"))io=remote('localhost',11111)io.clean(1)io.sendline(flag)print(io.clean(1).decode())
Squinty
Summary: We receive a power consumption trace as a NumPy array. By visualizing this array as a line plot, we can identify distinct patterns associated with certain operations. The square and multiply operations each have unique patterns, which help us recover the flag due to their data dependency. This approach is an example of a Simple Power Analysis (SPA) attack.
We are given some power traces in .npy format, which we can load in Python using np.load('traces.npy'). I used Plotly to generate the following plot: