RHme2 piece of SCAke challenge

This is an example of using Jlsca to analyze the trace set from the simplest SCA challenge of RHme2 embedded CTF (http://rhme.riscure.com/home).

The challenge features an unprotected SW AES-128 implementation on an 8-bit AVR microcontroller. You can send plaintext blocks to the device and receive blocks of ciphertext. The device LED blinks on the start of the encryption. The goal is to recover the encryption key.

Power traces were acquired using ChipWhisperer Lite and accompanying software (https://wiki.newae.com/Main_Page). The trigger was at the start of the cipher execution by using the blinking LED. After some trivial exploration, first round S-boxes can could be identified. For the attack in this example, only the corresponding part of the execution was captured: 10000 samples, at 105 MS/s sampling rate.

The small tarball with traces is available at https://drive.google.com/open?id=0B2slHLSL3nXaWFJ0dHVPWFJqalE, shasum 13b0a41e597cf90d495ad3322bf947e675c71c8d. Uncompress it next to this notebook.


In [1]:
# load the tools
using Jlsca.Sca
using Jlsca.Trs
using Jlsca.Align
using Jlsca.Aes
using PyCall
using Plots

Convert traces from ChipWhisperer capture to Trs format

Needed just once to create the trs file. The rest of the code uses the trs file.


In [2]:
insfname = "rhme2-pieceofscake-firstroundsboxes.trs"

if !isfile(insfname)
    # prefix identifying the capture, bulky as it is
    # (so far have been lazy to automate based on ChipWhisperer config file)
    prefix = "rhme2-pieceofscake-firstroundsboxes/2017.01.26-17.11.15_"

    numpy = pyimport("numpy")
    
    # read the data from chipwhisperer capture
    samples = numpy.load("$(prefix)traces.npy");
    input = numpy.load("$(prefix)textin.npy");
    output = numpy.load("$(prefix)textout.npy");

    # get sizes
    print(size(samples))
    print(size(input))
    print(size(output))

    # set the parameters
    numberOfTraces = size(input)[1] #weirdly, ChipWhisperer capture saves more traces then inputs
    numberOfSamples = size(samples)[2]
    dataSpace = size(input)[2] + size(output)[2]
    sampleType = Float32;

    # create and save the trs
    trs = InspectorTrace(insfname, dataSpace, sampleType, numberOfSamples)
    for t in 1:numberOfTraces
      trs[t] = (vcat(input[t,:],output[t,:]), map(Float32, samples[t,:]))
    end
    close(trs)
end

Explore the traces


In [3]:
# load the traceset
trs = InspectorTrace(insfname)


Opened rhme2-pieceofscake-firstroundsboxes.trs, #traces 135, #samples 10000 (Float32), #data 32

In [4]:
# read and plot 3 traces with some zoom
((data,samples),eof) = readTraces(trs, 1:3)
plot(samples[1:3,1:40]', linewidth=.3) # note the transpose; and here you can play with the zoom


Out[4]:
0 10 20 30 40 -0.4 -0.2 0.0 0.2 0.4 y1 y2 y3

Well aligned, should be piece of cake indeed. Let's close the file for now.


In [5]:
close(trs)

Run correlation power analysis

Vanilla CPA on the first round in the good old Hamming weight leakage model


In [ ]:
trs = InspectorTrace(insfname)

params = DpaAttack(AesSboxAttack(), IncrementalCPA())
params.dataOffset = 1       # plaintext starts from byte 1
params.attack.direction = FORWARD
params.analysis.leakages = [HW()]
numberOfTraces = length(trs);

rankData = sca(trs, params, 1, numberOfTraces)
key = getKey(params,rankData)


Opened rhme2-pieceofscake-firstroundsboxes.trs, #traces 135, #samples 10000 (Float32), #data 32

Jlsca running in Julia version: 1.3.0, 1 processes/1 workers/2 threads per worker

DPA parameters
attack:       AES Sbox CIPHER KL128 FORWARD
mode:         CIPHER
key length:   KL128
direction:    FORWARD
xor:          false
analysis:     Incremental CPA
leakages:     HW
maximization: abs global max
data at:      1

Check the key using a paintext-ciphertext pair from the traceset


In [ ]:
w = KeyExpansion(key, 10, 4)
Cipher(trs[1][1][1:16], w) == trs[1][1][17:32]

Done.