mov eax, 64

Timing attack on arduino

eax •

I like Side Channels Attacks. It's a way to gather some information about your target (software, hardware device, web site) by a way (the channel) not directly related with the primary task of your target. These channels are unintentional consequences, or artifact, of the primary task.

Let's give some examples.

You are in the same room as your target. The person is writing some text on his laptop and you want to know what it is. With a microphone, you record the audio for a couple of minutes. Guess what? We can use the noise made by pushing keys on a keyboard to recover what is being typed. Here, the primary task of your keyboard is to allow you to type text. The noise made by typing on it is an unintentional consequence.

Every year a secret party is happening between some cool people you want to meet. Only the ones with the password can enter. And... You don't have this password. The good thing is that the host have a very bad memory, so just behind the front door, a screen has been hang up to display the party password in large black letters. Guess what? Screens are emitting electromagnetic leakage that can allow you to reconstruct the displayed image behind the door with some cheap device and some programming skills. Here again, the electromagnetic radiation are artifact of the image being displayed.

Overview

In this post, I will explain a basic side channel attack: Timing attack. In Time attack, the channel that leak information is the time necessary to accomplish a task.

Let's imagine some door keypad:

How does it work? Well, we will assume that each key press is added on a buffer, and when the six digits has been typed, a routine is called to check this password against the one stored in memory. If this is the good password, the door unlock, otherwise, a red led blink.

Assuming

We will assume that the check routine look like this:

int check(char *input)
{
  int i;

  for (i = 0 ; i < 6 ; i++)
    if (input[i] != in_memory_password[i])
      return 0;
  return 1;
}

The C version is too high level to known exactly what will happen. So there is an ASM version of it: (AVR)

 1 ldi   r30, 0x09
 2 ldi  r31, 0x01
 3 movw r26, r24
 4 ld   r19, X+
 5 movw r24, r26
 6 ld   r18, Z+
 7 cpse r19, r18    # Compare, Skip if Equal
 8 rjmp .+14        # jmp to line 16
 9 ldi  r27, 0x01
10 cpi  r30, 0x0F
11 cpc  r31, r27
12 brne .-20        # if eaqual, jmp to line 3
13 ldi  r24, 0x01
14 ldi  r25, 0x00
15 ret
16 ldi  r24, 0x00
17 ldi  r25, 0x00
18 ret

Let's see what happen if the password is 424344 and you enter 123456:

After 7 instructions, the first comparison occur:

1 cpse    r19, r18 # Compare, Skip if Equal
2 rjmp  .+14       # jmp to line 16

If the registers are equal, the next instruction is skipped. Here r18 contain '4' and r19 contain '1'. So the next instruction is not skipped and we jump at line 16. We set the return registers to 0 and return.

With a bad password, 11 instructions have been executed on the routine. Now if we have only the first character good, the rjmp is skipped, and 9 more instructions are executed before the next cpse, and so one.

Number of good characters Number of instructions
0 11
1 20
2 29
3 38
4 47
5 56
6 68

Last row is 68 and not 65 because there is 3 more instructions to do if it's the good password. Okay, so more instruction mean more time, here his our time attack! Let's take a timer and measure it! Well... Kind of.

Demo time!

To simulate the system, I used an arduino. I didn't have a keypad, so I just use the serial input from the computer. And as a timer, let's use an oscilloscope.

Here is the C++ code:

const char *in_memory_password = "424344";
const int led_pin = 13; 

void setup() {
  Serial.begin(9600);
  pinMode(led_pin, OUTPUT);
  digitalWrite(led_pin, LOW);
}

int check(char *input)
{
  int i;

  for (i = 0 ; i < 6 ; i++)
    if (input[i] != in_memory_password[i])
      return 0;
  return 1;
}

void loop() {
  char buf[32];
  int r;

  Serial.setTimeout(10);
  while (!Serial.available())
    delay(10);
  r = Serial.readBytes(buf, 31);
  buf[r] = 0;

  digitalWrite(led_pin, HIGH);
  r = check(buf);
  digitalWrite(led_pin, LOW);

  if (r)
    Serial.println("Good password");
  else
    Serial.println("Wrong password");
}

For the demo, a led is turned on before the check function, and turned off just after. It's just to be simpler, but it could have been done with a beep on each key + a beep after the check.

To measure the time needed for the check function, I just hook up a probe on the pin 13 (led).

We said earlier than the password is 424344. This is what we can see when we type 123456:

input: 123456

Here the first comparison fail, and these 6.48us are corresponding to the 11 instructions. (Actually more since the code to turn on/off the led is consuming some time too)

So what happen if the first character of the input correspond to the one in the password?

input: 400000

Ok so now, it take 0.8us more than the previous input. We guessed our first character!

Ok, yes, we knew the password, so there is no fun.

This time, let have a python script sending USBTMC command to the oscilloscope and talk in serial with the arduino to bruteforce the password!

Sound a little bit funnier now, right?

#!/usr/bin/env python3

import string
import numpy
import serial
import matplotlib.pyplot as plot
import itertools
import instrument
from multiprocessing import Process, Queue
import time

timescale = 2.e-06
timeoffset = 10.e-06
vscale = 2
trig_lvl = 4

o = instrument.RigolScope("/dev/usbtmc0")

def setup_oscilo():
    o.write(":TIM:SCAL %f" % timescale)
    o.write(":TIM:OFFS %f" % timeoffset)
    o.write(":CHAN1:SCAL %f" % vscale)
    o.write(":TRIG:EDG:SOUR CHAN1")
    o.write(":TRIG:EDG:LEV %d" % trig_lvl)

def single_triger():
    o.write(":SINGLE")
    while o.wr(":TRIG:STAT?", 20) != b"WAIT\n":
        time.sleep(0.1)

def mesure_time(q):
    while o.wr(":TRIG:STAT?", 20) != b"STOP\n":
        time.sleep(0.1)
    o.write(":WAV:POIN:MODE NOR")
    o.write(":WAV:DATA? CHAN1")
    rawdata = o.read(9000)

    data = numpy.frombuffer(rawdata, 'B')
    l = len(data)
    data = data - (16.*8)

    # It should be 16 (nb of division on the scope).
    # But 12 works better I don't known why...
    data /= 12.

    elem_high = [x for x in filter(lambda x: x > trig_lvl, data)]
    res = len(elem_high)/50.
    q.put(res)

if __name__ == "__main__":
    sr = serial.Serial('/dev/ttyUSB0', 9600)
    setup_oscilo()

    pwd = ""
    last = ""
    while last != b"Good password":
        longest = (None, -1)

        for c in string.digits:
            print("Trying [%s]" % (pwd + c))

            q = Queue()
            p = Process(target=mesure_time, args=(q,))
            single_triger()
            sr.write(bytes(pwd + c, "utf8"))
            p.start()

            res = q.get()
            print("Time: %f" % res)
            if res > longest[1]:
                longest = (c, res)
            p.join()
            last = sr.readline().strip()
            if last == b"Good password":
                break

        pwd += longest[0]

    print("Found password! [%s]" % pwd)

(You can find the instrument class in the links section bellow)

Enjoy the password cracking:

asciicast

Only 55 try and 26 second and your in!

For the record, there is the screen for each step:

100000:

input: 1

400000:

input: 4

420000:

input: 42asciicast

424000:

input: 424

424300:

input: 4243

424340:

input: 42434

424344:

input: 424344

It was my introduction to side channel attack and particularly to time attack. I might do other post on side channel. Maybe about power analysis on this exact same code.