This is an example of using Jlsca to analyze the trace set from the "hard" 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 22 MS/s sampling rate. The 305 MB tarball with traces is available at https://drive.google.com/open?id=0B2slHLSL3nXaOHdhTXpMMHZkNjQ, shasum d6ca416b5e9b6009710bae5df51f6217333667d0
. 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-escalate-longer-traces.trs"
if !isfile(insfname)
numpy = pyimport("numpy")
# prefix identifying the capture, bulky as it is
# (so far have been lazy to automate based on ChipWhisperer config file)
prefix = "rhme2-escalate-longer-traces/2017.01.27-17.45.58_"
# 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 the parameters
numberOfTraces = size(input)[1] # because it happens that CW software saves more traces then inputs o_O
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 3 traces
In [5]:
plot(samples[1:3,:]',linewidth=.3)
Out[5]:
Zoom in at the end, plot a few (now overlayed), observe the alignment is not very good
In [6]:
zoomstart = 18000
zoomend = 22000
plot(samples[1:4,zoomstart:zoomend]', linewidth=.3)
Out[6]:
In [7]:
reset(trs)
maxShift = 20000
referencestart = zoomstart + 1000
referenceend = referencestart + 1500
reference = trs[3][2][referencestart:referenceend]
corvalMin = 0.0
alignstate = CorrelationAlignFFT(reference, referencestart, maxShift)
addSamplePass(trs, x -> ((shift,corval) = correlationAlign(x, alignstate); corval > corvalMin ? circshift(x, shift) : Vector{eltype(x)}(0)))
((data,samples),eof) = readTraces(trs, 1:4)
popSamplePass(trs)
plot(samples[1:4,18000:22000]', linewidth=.3)
Out[7]:
In [8]:
close(trs)
In [9]:
trs = InspectorTrace(insfname)
params = DpaAttack(AesSboxAttack(), CPA())
params.dataOffset = 17
params.attack.direction = BACKWARD
#params.analysis.leakages = [Bit(i) for i in 0:7] # this is for all-bit abs-sum CPA
params.analysis.leakages = [HW()]
numberOfTraces = length(trs);
Prepare the alignment pass, bump the corvalMin to throw away stuff that is poorly aligned
We're also throwing away everything outside our window of interest before it goes into the attack, after alignment
In [10]:
popSamplePass(trs)
popSamplePass(trs)
zoomstart = 18000
zoomend = 22000
maxShift = 20000
referencestart = zoomstart + 1000
referenceend = referencestart + 1500
reference = trs[3][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)));
addSamplePass(trs, x -> x[zoomstart-1000:zoomend]);
Run with conditional averaging
In [11]:
reset(trs) # note, this resets the conditional averager, it doesn't pop the sample passes
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]:
Success! Surprisignly, the challenge is equivalent to the "still not SCAry" one. Only alignment is needed to recover the key.
Now try incremental correlation instead of conditional averaging (i.e. "what inspector does"). It recovers the key as well, just runs slower.
In [ ]:
reset(trs) # note, this resets the conditional averager, it doesn't pop the sample passes
params.analysis = IncrementalCPA()
#params.analysis.leakages = [Bit(i) for i in 0:7] # this is for all-bit abs-sum CPA
params.analysis.leakages = [HW()]
rankData = sca(trs, params, 1, numberOfTraces)
key = getKey(params,rankData)
Check this key! wooot!
In [ ]:
w = KeyExpansion(key, 10, 4)
Cipher(trs[1][1][1:16], w) == trs[1][1][17:32]
That's all folks!