Capture seems...wrong

Hello,

I am trying to break the DES round 1 key based on my own captures, however, I am having issues doing so.

First, just to prove that my attack works, I am using traces supplied by CW software. The original key is 2B 7E 15 16 28 AE D2 A6

This all looks good. The offline traces look like this:

To capture my own, I am using the following capture function:

scope.adc.samples = 3500
scope.adc.offset = 14000

import random
def get_rand_bytes(n):
    return random.randbytes(n)

def capture_traces(cmd, data):
    target.flush()
    scope.arm()
    target.send_cmd(cmd, 0, data)
    capture = scope.capture()
    if capture:
        print("Timeout")
    data = target.read_cmd('r')
    trace = scope.get_last_trace()
    return data, trace

Then the capture itself:


#Set your project name here
project = cw.create_project("projects/des", overwrite = True)

#Set your number of traces here
num_traces = 50
target.flush()
target.send_cmd("x", 0, [])
key = target.read_cmd('r')
print_output(key)

for i in trange(num_traces):
    pt = get_rand_bytes(8)
    dout, trace = capture_traces('p',pt)
    print(pt.hex(" ",1).upper())
    if trace is None:
        print("No trace captured")
        continue
    ptrace = cw.Trace(trace,pt,dout[3:-2],key)
    project.traces.append(ptrace)

project.save()

The trace looks like this:

If I overlay offline over my own, I get this:

And the correlation doesn’t work. Not sure what’s happening.

If I use a simpler method to crack the key, it completely errors out (but works with offline traces).

Any advice or what I could look at ?

What’s the origin of these “des_offline” traces? Were they obtained from the same target? I’m guessing not.

This attack targets the first round sbox. For different targets, this will occur at different times. You are configuring scope.adc to capture 3500 samples, 14000 samples after the trigger is fired. What’s likely happening is that this segment of the target operation doesn’t capture the first round sbox.

After a capture, scope.adc.trig_count tells you how many cycles the trigger line was high for. Assuming that the firmware raises the trigger when it begins the DES operation and lowers it when it is done, then this is the number of samples required to capture the full DES operation.

Ideally you would use only a small portion of the power trace that contains the leaky operations that are leveraged by the CPA attack, but you can also use the full power trace (more traces may be required to successfully recover the key).

Regarding the last error: there might be something broken in the DES attack code, related to it having a different number of round keys and / or a different number of key bytes from AES.

These came from V3 of ChipWhisperer package (2017.07.17-16.12.28_traces.npy file) and copied to V5. Not sure what target was.

My target is XMEGA on CWLite and the firmware came from V5.7 of CW - simpleserial-des.c with some minor changes.

I am fairly confident that I am attacking the correct section. The trigger is happening deep inside the DES routine here.

Here’s what the whole thing looks like with offset 0.

Between sample 15k and 17k I can see 8 distinct blobs, which I believe is the S-box loop, which iterates 8 times.

One thing that looks off is the sync of the traces. Up until sample ~14800 the traces are synced. However, after that (S-box?), they go out of sync, so potentially this is cause the analysis to fail. I’ll try syncing that window and see if it helps.

If I look at offline captures, they are perfectly in sync.

Not sure if it’s buggy. It works on the offline traces but crashes out with mine.

So after a lot of faffing, I did manage to get the results out but this is from knowing what the result should be, rather than it telling me.

The issue was indeed the syncing of traces.

Here’s what the original one looks like:

It’s clearly not in sync…

The SAD method of syncing didn’t work well for me but I’ve managed to get them in sync using DTW method, details of which escape me:

%%time
import chipwhisperer.analyzer as cwa
resync_traces = cwa.preprocessing.ResyncDTW(project)
resync_traces.ref_trace = 0
resync_traces.radius = 5
resync_analyzer = resync_traces.preprocess()
plt = cw.plot([])
for i in range(14):
    plt *= cw.plot(resync_analyzer.waves[i][0:2000])
plt

Then run the attack again:

import chipwhisperer.analyzer as cwa
from chipwhisperer.analyzer.attacks.models.DES import DES, SBox_output
from chipwhisperer.analyzer.attacks.cpa_algorithms.progressive import CPAProgressive
from chipwhisperer.analyzer.attacks.cpa_new import CPA

leak_model = DES(SBox_output)
attack = cwa.cpa(resync_analyzer, leak_model)
attack.set_target_subkeys([0, 1, 2, 3, 4, 5, 6, 7])
attack.set_point_range((0,-1))
cb = cwa.get_jupyter_callback(attack, 8)
results = attack.run(cb)

I added a new parameter to get_jupyter_callback() function to take the number of columns to display. Do you accept PRs? Would be good to have this in as default.

One thing I don’t understand is why the key isn’t highlighed in red, like I do with offline version of traces?

Comments are welcome!

Apologies for the delay–

  1. I’m glad that ResyncDTW worked for you. However it’s still a mystery to me why it’s needed; the DES implementation in the chipwhisperer repo should run in constant time, and no re-alignment should be necessary. It could be that some compiler optimization is at fault? I’ve run it myself and traces are perfectly aligned. Which modifications did you make to the target source and how are you compiling it?
  2. We absolutely do accept and encourage PRs! However in this case I’m not sure whether we want to re-introduce support for DES in our maintained code base. I will follow up.

The changes are minimal to get it working with SS2.1. It’s compiled like this:

SCOPETYPE = 'OPENADC'
PLATFORM = 'CWLITEXMEGA'
SS_VER = 'SS_VER_2_1'
%%bash -s "$PLATFORM" "$SS_VER"
cd ../../../hardware/victims/firmware/simpleserial-des
make PLATFORM=$1 CRYPTO_TARGET=AVRCRYPTOLIB SS_VER=$2 -j

Would you be able to share the Python code you used to capture the traces? Wondering if there’s something wrong with mine.

I used our standard cw.capture_trace() as follows:

ktp = cw.ktp.Basic()
ktp.text_len = 8
ktp.key_len = 8
target.output_len = 8
for i in range(NUM_TRACES):
    key, text = ktp.next()
    trace = cw.capture_trace(scope, target, text, key)
    assert scope.adc.trig_count == 1063288
    project.traces.append(trace)

Thanks! Will give it a go. Did you compile your simpleserial-des as it is in the repo or was it just the hex file you flashed?

I compiled simpleserial-des as-is (warnings and all :wink:).

We’ve discussed internally and agreed that it would be good to have a proper DES attack demo; if you submit a PR for this we’ll definitely look at merging it.

1 Like

Not sure my code will be “proper” but I’ll give it a shot.