RHme2 eSCAlate challenge

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

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-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

Explore the traces

To see if there are any interesting patterngs and how well they are aligned.


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


Opened rhme2-escalate-longer-traces.trs, #traces 10000, #samples 24400 (Float32), #data 32
Out[3]:
InspectorTrace(0, 10000, 32, 4, Float32, 24400, 24, IOStream(<file rhme2-escalate-longer-traces.trs>), "rhme2-escalate-longer-traces.trs", 24, false, 18, true, MetaData(0, Pass[], Pass[], missing, missing, missing, true, #undef, #undef, #undef))

In [4]:
# read 10 traces
((data,samples),eof) = readTraces(trs, 1:10)


Out[4]:
((UInt8[0xd4 0xa7 … 0x0b 0xd6; 0x47 0x38 … 0x37 0xf9; … ; 0xff 0x4a … 0x40 0xc6; 0xc2 0xc3 … 0x1d 0xeb], Float32[0.13769531 0.10839844 … 0.09082031 0.16796875; 0.016601562 -0.3095703 … 0.20410156 0.119140625; … ; 0.17871094 -0.0087890625 … -0.00390625 -0.12402344; 0.04296875 0.08300781 … -0.37304688 -0.109375]), false)

Plot 3 traces


In [5]:
plot(samples[1:3,:]',linewidth=.3)


Out[5]:
0 5.0×10 3 1.0×10 4 1.5×10 4 2.0×10 4 2.5×10 4 -0.4 -0.2 0.0 0.2 0.4 y1 y2 y3

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]:
0 1000 2000 3000 4000 -0.4 -0.2 0.0 0.2 0.4 y1 y2 y3 y4

Find good alignment

Playing with the start and end of the reference pattern, and keeping the rejection threshold at 0.


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)


Processing traces .. 100%|██████████████████████████████| Time: 0:00:02
Out[7]:
0 1000 2000 3000 4000 -0.4 -0.2 0.0 0.2 0.4 y1 y2 y3 y4

In [8]:
close(trs)

CPA attack with alignment


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);


Opened rhme2-escalate-longer-traces.trs, #traces 10000, #samples 24400 (Float32), #data 32

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)


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

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

phase: 1 / 1, #targets 16

Attacking columns 1:5001 out of 5001 columns (run 1 out of 1)
Running "Cond avg" on trace range 1:1:10000, 2 data passes, 2 sample passes
Processing traces 1:10000 ..100%|███████████████████████| Time: 0:00:13
Averaged 10000 input traces into 16 averages, UInt8 data type, Float64 sample type
CPA on samples shape (256, 5001) (range 1:5001) and data shape (256,)
Results @ 256 rows, 5001 cols (10000 rows consumed)
target: 1, phase: 1, #candidates 256
rank:   1, candidate: 0x42, score: 0.366851 @ 121
rank:   2, candidate: 0x50, score: 0.280596 @ 4674
rank:   3, candidate: 0xdf, score: 0.276888 @ 3882
rank:   4, candidate: 0xee, score: 0.273794 @ 4376
rank:   5, candidate: 0xbc, score: 0.265013 @ 1490
recovered key material: 42
CPA on samples shape (256, 5001) (range 1:5001) and data shape (256,)
Results @ 256 rows, 5001 cols (10000 rows consumed)
target: 2, phase: 1, #candidates 256
rank:   1, candidate: 0x93, score: 0.414283 @ 574
rank:   2, candidate: 0xaa, score: 0.318319 @ 4926
rank:   3, candidate: 0x8b, score: 0.302099 @ 2916
rank:   4, candidate: 0xf2, score: 0.285334 @ 2328
rank:   5, candidate: 0xfc, score: 0.284925 @ 4946
recovered key material: 93
CPA on samples shape (256, 5001) (range 1:5001) and data shape (256,)
Results @ 256 rows, 5001 cols (10000 rows consumed)
target: 3, phase: 1, #candidates 256
rank:   1, candidate: 0xb5, score: 0.450316 @ 890
rank:   2, candidate: 0xd1, score: 0.300801 @ 4785
rank:   3, candidate: 0xe6, score: 0.282278 @ 4321
rank:   4, candidate: 0x77, score: 0.277869 @ 2855
rank:   5, candidate: 0x87, score: 0.276501 @ 4649
recovered key material: b5
CPA on samples shape (256, 5001) (range 1:5001) and data shape (256,)
Results @ 256 rows, 5001 cols (10000 rows consumed)
target: 4, phase: 1, #candidates 256
rank:   1, candidate: 0xc7, score: 0.371301 @ 1206
rank:   2, candidate: 0x29, score: 0.313079 @ 4249
rank:   3, candidate: 0xb3, score: 0.312462 @ 4128
rank:   4, candidate: 0x55, score: 0.308368 @ 1965
rank:   5, candidate: 0xad, score: 0.286960 @ 161
recovered key material: c7
CPA on samples shape (256, 5001) (range 1:5001) and data shape (256,)
Results @ 256 rows, 5001 cols (10000 rows consumed)
target: 5, phase: 1, #candidates 256
rank:   1, candidate: 0x0a, score: 0.416047 @ 496
rank:   2, candidate: 0xb0, score: 0.282873 @ 3053
rank:   3, candidate: 0x64, score: 0.265361 @ 2420
rank:   4, candidate: 0xc7, score: 0.257808 @ 4773
rank:   5, candidate: 0x04, score: 0.253544 @ 3127
recovered key material: 0a
CPA on samples shape (256, 5001) (range 1:5001) and data shape (256,)
Results @ 256 rows, 5001 cols (10000 rows consumed)
target: 6, phase: 1, #candidates 256
rank:   1, candidate: 0x84, score: 0.528586 @ 744
rank:   2, candidate: 0x53, score: 0.281632 @ 4224
rank:   3, candidate: 0x4b, score: 0.281419 @ 3911
rank:   4, candidate: 0xfe, score: 0.281246 @ 675
rank:   5, candidate: 0x9b, score: 0.279739 @ 4992
recovered key material: 84
CPA on samples shape (256, 5001) (range 1:5001) and data shape (256,)
Results @ 256 rows, 5001 cols (10000 rows consumed)
target: 7, phase: 1, #candidates 256
rank:   1, candidate: 0xa4, score: 0.450507 @ 1129
rank:   2, candidate: 0x47, score: 0.320328 @ 4632
rank:   3, candidate: 0x25, score: 0.315876 @ 4534
rank:   4, candidate: 0x63, score: 0.289402 @ 4065
rank:   5, candidate: 0xa1, score: 0.288303 @ 4323
recovered key material: a4
CPA on samples shape (256, 5001) (range 1:5001) and data shape (256,)
Results @ 256 rows, 5001 cols (10000 rows consumed)
target: 8, phase: 1, #candidates 256
rank:   1, candidate: 0x85, score: 0.312700 @ 419
rank:   2, candidate: 0x86, score: 0.293134 @ 3593
rank:   3, candidate: 0x6f, score: 0.285291 @ 202
rank:   4, candidate: 0xd7, score: 0.281789 @ 1192
rank:   5, candidate: 0xd3, score: 0.274634 @ 2257
recovered key material: 85
CPA on samples shape (256, 5001) (range 1:5001) and data shape (256,)
Results @ 256 rows, 5001 cols (10000 rows consumed)
target: 9, phase: 1, #candidates 256
rank:   1, candidate: 0xc7, score: 0.392149 @ 735
rank:   2, candidate: 0xea, score: 0.292911 @ 4523
rank:   3, candidate: 0x44, score: 0.287991 @ 3931
rank:   4, candidate: 0xd7, score: 0.284060 @ 4903
rank:   5, candidate: 0xa1, score: 0.278581 @ 2040
recovered key material: c7
CPA on samples shape (256, 5001) (range 1:5001) and data shape (256,)
Results @ 256 rows, 5001 cols (10000 rows consumed)
target: 10, phase: 1, #candidates 256
rank:   1, candidate: 0x1f, score: 0.426742 @ 1051
rank:   2, candidate: 0xe2, score: 0.301109 @ 2752
rank:   3, candidate: 0x62, score: 0.291281 @ 3837
rank:   4, candidate: 0xd2, score: 0.280351 @ 4460
rank:   5, candidate: 0xbb, score: 0.272914 @ 1030
recovered key material: 1f
CPA on samples shape (256, 5001) (range 1:5001) and data shape (256,)
Results @ 256 rows, 5001 cols (10000 rows consumed)
target: 11, phase: 1, #candidates 256
rank:   1, candidate: 0x0f, score: 0.423500 @ 343
rank:   2, candidate: 0xfb, score: 0.294014 @ 4016
rank:   3, candidate: 0x99, score: 0.291086 @ 3481
rank:   4, candidate: 0xec, score: 0.288327 @ 4738
rank:   5, candidate: 0xe9, score: 0.288188 @ 2939
recovered key material: 0f
CPA on samples shape (256, 5001) (range 1:5001) and data shape (256,)
Results @ 256 rows, 5001 cols (10000 rows consumed)
target: 12, phase: 1, #candidates 256
rank:   1, candidate: 0x1a, score: 0.337356 @ 728
rank:   2, candidate: 0x3a, score: 0.336160 @ 1969
rank:   3, candidate: 0xe1, score: 0.299024 @ 3154
rank:   4, candidate: 0xe6, score: 0.298525 @ 4448
rank:   5, candidate: 0x88, score: 0.298250 @ 1602
recovered key material: 1a
CPA on samples shape (256, 5001) (range 1:5001) and data shape (256,)
Results @ 256 rows, 5001 cols (10000 rows consumed)
target: 13, phase: 1, #candidates 256
rank:   1, candidate: 0x1e, score: 0.458799 @ 1044
rank:   2, candidate: 0x8d, score: 0.306687 @ 3414
rank:   3, candidate: 0x08, score: 0.294062 @ 1098
rank:   4, candidate: 0xec, score: 0.284336 @ 2885
rank:   5, candidate: 0xdd, score: 0.275915 @ 4991
recovered key material: 1e
CPA on samples shape (256, 5001) (range 1:5001) and data shape (256,)
Results @ 256 rows, 5001 cols (10000 rows consumed)
target: 14, phase: 1, #candidates 256
rank:   1, candidate: 0x97, score: 0.368177 @ 266
rank:   2, candidate: 0x45, score: 0.306109 @ 1952
rank:   3, candidate: 0x2e, score: 0.299626 @ 3496
rank:   4, candidate: 0x0c, score: 0.280763 @ 3028
rank:   5, candidate: 0x2f, score: 0.279384 @ 3247
recovered key material: 97
CPA on samples shape (256, 5001) (range 1:5001) and data shape (256,)
Results @ 256 rows, 5001 cols (10000 rows consumed)
target: 15, phase: 1, #candidates 256
rank:   1, candidate: 0x1e, score: 0.361511 @ 651
rank:   2, candidate: 0x08, score: 0.301634 @ 4291
rank:   3, candidate: 0xe1, score: 0.295890 @ 3826
rank:   4, candidate: 0x5e, score: 0.285151 @ 4489
rank:   5, candidate: 0x21, score: 0.279305 @ 4115
recovered key material: 1e
CPA on samples shape (256, 5001) (range 1:5001) and data shape (256,)
Results @ 256 rows, 5001 cols (10000 rows consumed)
target: 16, phase: 1, #candidates 256
rank:   1, candidate: 0x50, score: 0.398272 @ 898
rank:   2, candidate: 0x0a, score: 0.304307 @ 4886
rank:   3, candidate: 0x9a, score: 0.296004 @ 815
rank:   4, candidate: 0x2c, score: 0.289818 @ 3235
rank:   5, candidate: 0x57, score: 0.282105 @ 4531
recovered key material: 50
recovered key: 32be2a051b6c0946b8f14fd0b2933e90
Out[11]:
16-element Array{UInt8,1}:
 0x32
 0xbe
 0x2a
 0x05
 0x1b
 0x6c
 0x09
 0x46
 0xb8
 0xf1
 0x4f
 0xd0
 0xb2
 0x93
 0x3e
 0x90

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]:
true

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)


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

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

phase: 1 / 1, #targets 16

Attacking columns 1:5001 out of 5001 columns (run 1 out of 1)
Running "Incremental correlation" on trace range 1:1:10000, 2 data passes, 2 sample passes
Processing traces 1:10000 .. 69%|███████████████▉       |  ETA: 0:00:10

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!