RHme2 still not SCAry challenge

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

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

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-stillnotscary-lastround.trs, #traces 101, #samples 24400 (Float32), #data 32
Out[3]:
InspectorTrace(0, 101, 32, 4, Float32, 24400, 24, IOStream(<file rhme2-stillnotscary-lastround.trs>), "rhme2-stillnotscary-lastround.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[0x1a 0x73 … 0x2c 0x05; 0xd2 0x5e … 0xd3 0xfe; … ; 0xc8 0xe5 … 0x69 0xc8; 0x3f 0x3f … 0x9c 0xbd], Float32[0.0029296875 -0.14453125 … 0.21484375 0.10644531; 0.16210938 0.10839844 … 0.07128906 -0.047851562; … ; -0.07519531 -0.18945312 … 0.16992188 0.11621094; -0.13769531 -0.1796875 … 0.24121094 0.20996094]), false)

Plot a single trace and then 3 traces overlayed


In [5]:
plot(samples[1,:])


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

In [6]:
plot(samples[1:3,:]',linewidth=.3) # note the transpose when plotting multiple traces


Out[6]:
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

Round structure is visible. Traces are misaligned. We will fix that.

Find good alignment

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


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)


Processing traces .. 100%|██████████████████████████████| Time: 0:00:02
Out[7]:
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 y4

Aligned well! Let's close the trs for now.


In [8]:
close(trs)

LRA attack with alignment

Let's run a non-profiled linear regression analysis instead of plain CPA just for fun. Though it will take some time and is not that necessary here. Vanilla CPA would do, and you can do it yourself as an excercise.


In [9]:
trs = InspectorTrace(insfname)

numberOfTraces = length(trs);


Opened rhme2-stillnotscary-lastround.trs, #traces 101, #samples 24400 (Float32), #data 32

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)


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:     LRA
basismodel:   #9
maximization: global max
data at:      17

phase: 1 / 1, #targets 16

Attacking columns 1:4001 out of 4001 columns (run 1 out of 1)
Running "Cond avg" on trace range 1:1:101, 2 data passes, 2 sample passes

Averaged 101 input traces into 16 averages, UInt8 data type, Float64 sample type
LRA on samples shape (81, 4001) (range 1:4001) and data shape (81,)
Results @ 81 rows, 4001 cols (101 rows consumed)
target: 1, phase: 1, #candidates 256
rank:   1, candidate: 0x4a, score: 0.963259 @ 137
rank:   2, candidate: 0xc9, score: 0.418260 @ 2005
rank:   3, candidate: 0x23, score: 0.415857 @ 708
rank:   4, candidate: 0x6c, score: 0.415228 @ 2649
rank:   5, candidate: 0xa0, score: 0.410367 @ 3220
recovered key material: 4a
LRA on samples shape (84, 4001) (range 1:4001) and data shape (84,)
Results @ 84 rows, 4001 cols (101 rows consumed)
target: 2, phase: 1, #candidates 256
rank:   1, candidate: 0x5d, score: 0.917956 @ 1254
rank:   2, candidate: 0x0d, score: 0.431558 @ 1023
rank:   3, candidate: 0xa5, score: 0.425027 @ 1837
rank:   4, candidate: 0xc0, score: 0.411568 @ 2443
rank:   5, candidate: 0xd5, score: 0.405718 @ 2324
recovered key material: 5d
LRA on samples shape (85, 4001) (range 1:4001) and data shape (85,)
Results @ 85 rows, 4001 cols (101 rows consumed)
target: 3, phase: 1, #candidates 256
rank:   1, candidate: 0xd7, score: 0.939697 @ 2373
rank:   2, candidate: 0x48, score: 0.440708 @ 1880
rank:   3, candidate: 0x44, score: 0.421439 @ 337
rank:   4, candidate: 0xc4, score: 0.384163 @ 1099
rank:   5, candidate: 0x87, score: 0.381331 @ 2928
recovered key material: d7
LRA on samples shape (86, 4001) (range 1:4001) and data shape (86,)
Results @ 86 rows, 4001 cols (101 rows consumed)
target: 4, phase: 1, #candidates 256
rank:   1, candidate: 0x80, score: 0.891270 @ 3495
rank:   2, candidate: 0x84, score: 0.402550 @ 394
rank:   3, candidate: 0x49, score: 0.366666 @ 3548
rank:   4, candidate: 0xe8, score: 0.364582 @ 1065
rank:   5, candidate: 0xa9, score: 0.363192 @ 3167
recovered key material: 80
LRA on samples shape (79, 4001) (range 1:4001) and data shape (79,)
Results @ 79 rows, 4001 cols (101 rows consumed)
target: 5, phase: 1, #candidates 256
rank:   1, candidate: 0xca, score: 0.945974 @ 1030
rank:   2, candidate: 0x91, score: 0.407096 @ 3103
rank:   3, candidate: 0x19, score: 0.403006 @ 3822
rank:   4, candidate: 0xc9, score: 0.391026 @ 3424
rank:   5, candidate: 0x48, score: 0.381692 @ 1564
recovered key material: ca
LRA on samples shape (80, 4001) (range 1:4001) and data shape (80,)
Results @ 80 rows, 4001 cols (101 rows consumed)
target: 6, phase: 1, #candidates 256
rank:   1, candidate: 0x0d, score: 0.884221 @ 2152
rank:   2, candidate: 0xa9, score: 0.437181 @ 1352
rank:   3, candidate: 0x36, score: 0.429830 @ 2862
rank:   4, candidate: 0xb8, score: 0.411503 @ 1543
rank:   5, candidate: 0x97, score: 0.409207 @ 1128
recovered key material: 0d
LRA on samples shape (84, 4001) (range 1:4001) and data shape (84,)
Results @ 84 rows, 4001 cols (101 rows consumed)
target: 7, phase: 1, #candidates 256
rank:   1, candidate: 0x45, score: 0.916466 @ 3271
rank:   2, candidate: 0x3e, score: 0.404234 @ 1108
rank:   3, candidate: 0xda, score: 0.389092 @ 3984
rank:   4, candidate: 0x3a, score: 0.387537 @ 2485
rank:   5, candidate: 0x3f, score: 0.385641 @ 2184
recovered key material: 45
LRA on samples shape (85, 4001) (range 1:4001) and data shape (85,)
Results @ 85 rows, 4001 cols (101 rows consumed)
target: 8, phase: 1, #candidates 256
rank:   1, candidate: 0x46, score: 0.909726 @ 809
rank:   2, candidate: 0xe1, score: 0.392624 @ 1992
rank:   3, candidate: 0x69, score: 0.389698 @ 204
rank:   4, candidate: 0xf7, score: 0.386561 @ 3759
rank:   5, candidate: 0xda, score: 0.375222 @ 882
recovered key material: 46
LRA on samples shape (88, 4001) (range 1:4001) and data shape (88,)
Results @ 88 rows, 4001 cols (101 rows consumed)
target: 9, phase: 1, #candidates 256
rank:   1, candidate: 0xa7, score: 0.891549 @ 1928
rank:   2, candidate: 0x4a, score: 0.450879 @ 2695
rank:   3, candidate: 0x65, score: 0.400933 @ 1577
rank:   4, candidate: 0x3b, score: 0.392436 @ 3547
rank:   5, candidate: 0xba, score: 0.389130 @ 842
recovered key material: a7
LRA on samples shape (86, 4001) (range 1:4001) and data shape (86,)
Results @ 86 rows, 4001 cols (101 rows consumed)
target: 10, phase: 1, #candidates 256
rank:   1, candidate: 0x9b, score: 0.928990 @ 3047
rank:   2, candidate: 0x60, score: 0.391674 @ 1429
rank:   3, candidate: 0x1b, score: 0.388059 @ 3680
rank:   4, candidate: 0x37, score: 0.370871 @ 3946
rank:   5, candidate: 0x74, score: 0.366779 @ 3155
recovered key material: 9b
LRA on samples shape (84, 4001) (range 1:4001) and data shape (84,)
Results @ 84 rows, 4001 cols (101 rows consumed)
target: 11, phase: 1, #candidates 256
rank:   1, candidate: 0x4d, score: 0.925873 @ 585
rank:   2, candidate: 0x87, score: 0.400184 @ 912
rank:   3, candidate: 0xba, score: 0.379392 @ 3575
rank:   4, candidate: 0x2f, score: 0.378145 @ 3435
rank:   5, candidate: 0x2a, score: 0.373661 @ 2662
recovered key material: 4d
LRA on samples shape (80, 4001) (range 1:4001) and data shape (80,)
Results @ 80 rows, 4001 cols (101 rows consumed)
target: 12, phase: 1, #candidates 256
rank:   1, candidate: 0xf8, score: 0.921495 @ 1704
rank:   2, candidate: 0x22, score: 0.443383 @ 1557
rank:   3, candidate: 0xe2, score: 0.440912 @ 1784
rank:   4, candidate: 0xfe, score: 0.412673 @ 785
rank:   5, candidate: 0x58, score: 0.410842 @ 1458
recovered key material: f8
LRA on samples shape (83, 4001) (range 1:4001) and data shape (83,)
Results @ 83 rows, 4001 cols (101 rows consumed)
target: 13, phase: 1, #candidates 256
rank:   1, candidate: 0xc0, score: 0.947863 @ 2823
rank:   2, candidate: 0x5b, score: 0.445848 @ 1258
rank:   3, candidate: 0x32, score: 0.402196 @ 3906
rank:   4, candidate: 0x97, score: 0.392114 @ 955
rank:   5, candidate: 0x2c, score: 0.384050 @ 3170
recovered key material: c0
LRA on samples shape (87, 4001) (range 1:4001) and data shape (87,)
Results @ 87 rows, 4001 cols (101 rows consumed)
target: 14, phase: 1, #candidates 256
rank:   1, candidate: 0x1b, score: 0.928656 @ 361
rank:   2, candidate: 0x8b, score: 0.434982 @ 2243
rank:   3, candidate: 0xea, score: 0.389391 @ 218
rank:   4, candidate: 0xcb, score: 0.373043 @ 281
rank:   5, candidate: 0x81, score: 0.369480 @ 2756
recovered key material: 1b
LRA on samples shape (79, 4001) (range 1:4001) and data shape (79,)
Results @ 79 rows, 4001 cols (101 rows consumed)
target: 15, phase: 1, #candidates 256
rank:   1, candidate: 0x9d, score: 0.931561 @ 1480
rank:   2, candidate: 0x4d, score: 0.435555 @ 1046
rank:   3, candidate: 0xaa, score: 0.428923 @ 1953
rank:   4, candidate: 0xfb, score: 0.425603 @ 453
rank:   5, candidate: 0xf3, score: 0.415997 @ 2796
recovered key material: 9d
LRA on samples shape (85, 4001) (range 1:4001) and data shape (85,)
Results @ 85 rows, 4001 cols (101 rows consumed)
target: 16, phase: 1, #candidates 256
rank:   1, candidate: 0x8c, score: 0.922924 @ 2597
rank:   2, candidate: 0x36, score: 0.394937 @ 1785
rank:   3, candidate: 0xb5, score: 0.393860 @ 1524
rank:   4, candidate: 0x2c, score: 0.382536 @ 1325
rank:   5, candidate: 0x9e, score: 0.378714 @ 769
recovered key material: 8c
recovered key: d70f5f3f85b27cb699c6167ed620bc3f
Out[11]:
16-element Array{UInt8,1}:
 0xd7
 0x0f
 0x5f
 0x3f
 0x85
 0xb2
 0x7c
 0xb6
 0x99
 0xc6
 0x16
 0x7e
 0xd6
 0x20
 0xbc
 0x3f

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

Here we are! Easy, isn't it?