This is an example of using Jlsca to analyze the trace set from the "moderate" SCA challenge of RHme2 embedded CTF (http://rhme.riscure.com/home).
The challenge features a somewhat protected SW AES-128 implementation on an 8-bit AVR microcontroller. You can send plaintext blocks to the device and receive blocks of ciphertext. 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 end of the cipher execution. 24400 samples preceding the trigger were acquired at 64 MS/s sampling rate to cover the last round of encryption.
A small tarball with traces is available at https://drive.google.com/open?id=0B2slHLSL3nXaZVBfQ3ZFZ1VCNGM, shasum 389d4047d213075eb570dce05a3455de509f1442
. 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
In [2]:
insfname = "rhme2-stillnotscary-lastround.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-stillnotscary-lastround/2017.01.26-17.08.39_"
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]
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
In [3]:
# load the traceset
trs = InspectorTrace(insfname)
Out[3]:
In [4]:
# read 10 traces
((data,samples),eof) = readTraces(trs, 1:10)
Out[4]:
Plot a single trace and then 3 traces overlayed
In [5]:
plot(samples[1,:])
Out[5]:
In [6]:
plot(samples[1:3,:]',linewidth=.3) # note the transpose when plotting multiple traces
Out[6]:
Round structure is visible. Traces are misaligned. We will fix that.
In [7]:
reset(trs)
# selecting the reference pattern in the first traces
referencestart = 5000
referenceend = referencestart + 5000
reference = trs[1][2][referencestart:referenceend]
# in the search of alignemt, traces will be shifted by max this amount of samples
maxShift = 20000
# the rejection threshold
corvalMin = 0.0
# create the alignment engine
alignstate = CorrelationAlignFFT(reference, referencestart, maxShift)
# add the alignment as a sample processing pass
# the end of the next line is somewhat complex I must say :) But it's an experimental toolbox, yay!
addSamplePass(trs, x -> ((shift,corval) = correlationAlign(x, alignstate); corval > corvalMin ? circshift(x, shift) : Vector{eltype(x)}(0)))
# execute the alignment pass by reading the traces
((data,samples),eof) = readTraces(trs, 1:4)
# remove the alignment apss
popSamplePass(trs)
# see what we've got
plot(samples[1:4,:]', linewidth=.3)
Out[7]:
Aligned well! Let's close the trs for now.
In [8]:
close(trs)
In [9]:
trs = InspectorTrace(insfname)
numberOfTraces = length(trs);
Prepare the alignment pass, bump the corvalMin to throw away stuff that is poorly aligned
In [10]:
maxShift = 20000
referencestart = 5000
referenceend = 10000
reference = trs[1][2][referencestart:referenceend]
corvalMin = 0.4
alignstate = CorrelationAlignFFT(reference, referencestart, maxShift)
addSamplePass(trs, x -> ((shift,corval) = correlationAlign(x, alignstate); corval > corvalMin ? circshift(x, shift) : Vector{eltype(x)}(0)));
# throwing away samples to speed up the LRA analysis (removing this pass does not affect the attack outcome)
addSamplePass(trs, x -> x[5000:9000])
Run with conditional averaging
In [11]:
reset(trs) # note, this resets the conditional averager, it doesn't pop the sample passes
params = DpaAttack(AesSboxAttack(), LRA())
params.dataOffset = 17 # ciphertext starts here
params.attack.direction = BACKWARD # attack on last round from the ciphertext
params.analysis.basisModel = x -> basisModelSingleBits(x, 8)
rankData = sca(trs, params, 1, numberOfTraces)
key = getKey(params,rankData)
Out[11]:
Check the key using a paintext-ciphertext pair from the traceset
In [12]:
w = KeyExpansion(key, 10, 4)
Cipher(trs[1][1][1:16], w) == trs[1][1][17:32]
Out[12]:
Here we are! Easy, isn't it?