RHme3 White Box Unboxing qualifier

This example shows how to recover the key from the whitebox qualifier challenge of Rhme 2017. The challenge can be completely solved using the SideChannelMarvels framework as described in this Deadpool writeup. Here we do it somewhat differently.

We will use the wrapper from Deadpool to trace the whitebox binary with Intel Pin. For recovery, we will use Jlsca to illustrate trace pre-processing techniques that it offers. For this toy binary, the effect of these techniques is not so pronounced, however it is significant for more serious challenges.

Before computing the correlation, we perform pre-processing on the traces to automatically remove samples that are irrelevant for the analysis. Such point-of-interest selection drastically reduces the length of traces without the visual inspection of the trace graph and manual filter configuration. As you can see from the log lines staring with Reduction, what remains are about 20 bits per key byte. This is what goes into correlation computation. As a result, the total time of the attack (and the amount of human input) is reduced.

The detailed description of these pre-procesing techniques is available in https://eprint.iacr.org/2018/095

The correlation part of the attack is the same as in Daredevil. The output of the pre-processing could be fed out to Daredevil. However, the point selection is different per key byte, so we would need to script 16 separate Daredevil runs with different tracesets. You can do it as an exercise though.

In case you do not feel like tracing the binary yourself, tracesets for analysis are available alongside this notebook in rhme2017-qual-wb-traces.tar.bz2.

0. Tracing the binary

We do it in a standard Deadpool way based on the examples therein. The acquisition script is leaving default filters on acquired ranges.

#!/usr/bin/env python
import sys
sys.path.insert(0, '../../')
from deadpool_dca import *
def processinput(iblock, blocksize):
    return (None, ['--stdin < <(echo %0*x|xxd -r -p)' % (2*blocksize, iblock)])
def processoutput(output, blocksize):
    return int(''.join([x for x in output.split(' ')]), 16)
T=TracerPIN('./whitebox', processinput, processoutput, ARCH.amd64, blocksize=16, shell=True)
T.run(100)
bin2trs(None, None, False) # get the bit-unpacked trs, keeping the originals
bin2daredevil()            # get the daredevil "split binary", erasing the originals

We execute this script in the environment provided by the Orka docker image refreshed to the latest state. From several output files, for further steps we need mem_addr1_rw1_100_42808.trace and mem_addr1_rw1_100_42808.input. Other memory ranges can be analysed in the same manner.

1. Converting the files

Though Jlsca accepts the "split binary" format directly, we will convert the traces to bit-packed representation and save it as trs. For the short traces of this example it hardly matters, so just as an illustration.

Due to the current limitations of the converter we add only the input. This is enough for the attack but we will not be able to verify the key. For this challenge, the key will be distinguishable by its entropy, but in general the converter deserves improvement. :)

Deadpool's bin2trs converter from deadpool_dca.py can also be used (see the tracing script above), it just does not pack the bits. As an excercise, you can run the attack below on the mem_addr1_rw1_100_42808.trs and see what happens.


In [4]:
using Jlsca.Trs

filename = "rhme2017-qual-wb-traces/mem_addr1_rw1_100_42808_bitpacked.trs"

if !isfile(filename)
    # the true parameter in the end tells the converter to pack the bits 
    splitbin2trs("rhme2017-qual-wb-traces/mem_addr1_rw1_100_42808.input", 16, "rhme2017-qual-wb-traces/mem_addr1_rw1_100_42808.trace", 42808, UInt8, 100, true)
    run(`mv output_UInt8_100t.trs rhme2017-qual-wb-traces/mem_addr1_rw1_100_42808_bitpacked.trs`)
end


UndefVarError: exists not defined

Stacktrace:
 [1] top-level scope at In[4]:4

2. Recovering the key

Here we will run the analysis with the pre-processing implemented in Jlsca.


In [5]:
using Jlsca.Sca
using Jlsca.Trs
using Jlsca.Aes

# attack configuration
attack = AesSboxAttack()   # attacking AES S-box
attack.keyLength = KL128   # attacking AES-128
attack.mode = CIPHER       # encryption (INVCIPHER would have been for decryption)
attack.direction = FORWARD # attacking from input
analysis = CPA()           # use correlation as a distinguisher
analysis.leakages = [Bit(i) for i in 0:7] # absolute-sum DPA with bitwise "leakages"
params = DpaAttack(attack, analysis) # tie the attack and analysis together
params.dataOffset = 1      # data starts from the very first byte (remember Julia is 1-based)

# pre-processing setup 
trs = InspectorTrace(filename)   # open traceset with efficient readout of packed bits
addSamplePass(trs, BitPass())             # add bit-unpacking pass over samples
params.analysis.postProcessor = CondReduce
# TODO: expalin the difference between a sample pass and a post-processor in Jlsca readme

# attack using all available traces
rankData = sca(trs, params, 1, length(trs))
key = getKey(params, rankData)


Opened rhme2017-qual-wb-traces/mem_addr1_rw1_100_42808_bitpacked.trs, #traces 100, #samples 5351 (UInt8), #data 16

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

DPA parameters
attack:       AES Sbox CIPHER KL128 FORWARD
mode:         CIPHER
key length:   KL128
direction:    FORWARD
xor:          false
analysis:     CPA
leakages:     bit0,bit1,bit2,bit3,bit4,bit5,bit6,bit7
maximization: abs global max
combination:  +
data at:      1

phase: 1 / 1, #targets 16

Attacking columns 1:42808 out of 42808 columns (run 1 out of 1)
Running "Cond reduce" on trace range 1:1:100, 1 data passes, 1 sample passes
Reduction for 1: 5962 left after global dup col removal, 5097 left after removing the inv dup cols, 20 left after sample reduction
Reduction for 2: 5962 left after global dup col removal, 5097 left after removing the inv dup cols, 19 left after sample reduction
Reduction for 3: 5962 left after global dup col removal, 5097 left after removing the inv dup cols, 20 left after sample reduction
Reduction for 4: 5962 left after global dup col removal, 5097 left after removing the inv dup cols, 19 left after sample reduction
Reduction for 5: 5962 left after global dup col removal, 5097 left after removing the inv dup cols, 20 left after sample reduction
Reduction for 6: 5962 left after global dup col removal, 5097 left after removing the inv dup cols, 19 left after sample reduction
Reduction for 7: 5962 left after global dup col removal, 5097 left after removing the inv dup cols, 20 left after sample reduction
Reduction for 8: 5962 left after global dup col removal, 5097 left after removing the inv dup cols, 19 left after sample reduction
Reduction for 9: 5962 left after global dup col removal, 5097 left after removing the inv dup cols, 20 left after sample reduction
Reduction for 10: 5962 left after global dup col removal, 5097 left after removing the inv dup cols, 19 left after sample reduction
Reduction for 11: 5962 left after global dup col removal, 5097 left after removing the inv dup cols, 21 left after sample reduction
Reduction for 12: 5962 left after global dup col removal, 5097 left after removing the inv dup cols, 19 left after sample reduction
Reduction for 13: 5962 left after global dup col removal, 5097 left after removing the inv dup cols, 20 left after sample reduction
Reduction for 14: 5962 left after global dup col removal, 5097 left after removing the inv dup cols, 19 left after sample reduction
Reduction for 15: 5962 left after global dup col removal, 5097 left after removing the inv dup cols, 20 left after sample reduction
Reduction for 16: 5962 left after global dup col removal, 5097 left after removing the inv dup cols, 19 left after sample reduction

Reduced 100 input traces, UInt8 data type
CPA on samples shape (74, 20) (range 1:20) and data shape (74,)
Results @ 74 rows, 20 cols (100 rows consumed)
target: 1, phase: 1, #candidates 256
rank:   1, candidate: 0x61, score: 7.209363 @ 10
rank:   2, candidate: 0x49, score: 2.420336 @ 8
rank:   3, candidate: 0x9e, score: 2.356039 @ 2
rank:   4, candidate: 0xea, score: 2.320170 @ 6
rank:   5, candidate: 0x42, score: 2.303592 @ 3
recovered key material: 61
CPA on samples shape (87, 19) (range 1:19) and data shape (87,)
Results @ 87 rows, 19 cols (100 rows consumed)
target: 2, phase: 1, #candidates 256
rank:   1, candidate: 0x31, score: 8.000000 @ 17
rank:   2, candidate: 0x49, score: 2.267663 @ 15
rank:   3, candidate: 0x15, score: 2.198580 @ 16
rank:   4, candidate: 0x81, score: 2.156447 @ 17
rank:   5, candidate: 0xfc, score: 2.152295 @ 8
recovered key material: 31
CPA on samples shape (81, 20) (range 1:20) and data shape (81,)
Results @ 81 rows, 20 cols (100 rows consumed)
target: 3, phase: 1, #candidates 256
rank:   1, candidate: 0x6c, score: 7.131208 @ 2
rank:   2, candidate: 0xf8, score: 2.311709 @ 6
rank:   3, candidate: 0xf7, score: 2.293035 @ 17
rank:   4, candidate: 0x04, score: 2.271515 @ 19
rank:   5, candidate: 0x7f, score: 2.232452 @ 12
recovered key material: 6c
CPA on samples shape (82, 19) (range 1:19) and data shape (82,)
Results @ 82 rows, 19 cols (100 rows consumed)
target: 4, phase: 1, #candidates 256
rank:   1, candidate: 0x5f, score: 8.000000 @ 2
rank:   2, candidate: 0xd9, score: 2.394716 @ 8
rank:   3, candidate: 0xba, score: 2.159239 @ 4
rank:   4, candidate: 0x1c, score: 2.128540 @ 14
rank:   5, candidate: 0x02, score: 2.126880 @ 13
recovered key material: 5f
CPA on samples shape (82, 20) (range 1:20) and data shape (82,)
Results @ 82 rows, 20 cols (100 rows consumed)
target: 5, phase: 1, #candidates 256
rank:   1, candidate: 0x74, score: 7.166967 @ 10
rank:   2, candidate: 0xbd, score: 2.380427 @ 10
rank:   3, candidate: 0x4d, score: 2.287843 @ 11
rank:   4, candidate: 0x1f, score: 2.270442 @ 3
rank:   5, candidate: 0x44, score: 2.236426 @ 16
recovered key material: 74
CPA on samples shape (84, 19) (range 1:19) and data shape (84,)
Results @ 84 rows, 19 cols (100 rows consumed)
target: 6, phase: 1, #candidates 256
rank:   1, candidate: 0x34, score: 8.000000 @ 17
rank:   2, candidate: 0x48, score: 2.305490 @ 18
rank:   3, candidate: 0x18, score: 2.288389 @ 2
rank:   4, candidate: 0xd7, score: 2.244434 @ 11
rank:   5, candidate: 0xa3, score: 2.213195 @ 17
recovered key material: 34
CPA on samples shape (85, 20) (range 1:20) and data shape (85,)
Results @ 85 rows, 20 cols (100 rows consumed)
target: 7, phase: 1, #candidates 256
rank:   1, candidate: 0x62, score: 7.291667 @ 2
rank:   2, candidate: 0xb1, score: 2.206902 @ 3
rank:   3, candidate: 0x85, score: 2.190008 @ 14
rank:   4, candidate: 0x6e, score: 2.172845 @ 14
rank:   5, candidate: 0x18, score: 2.161490 @ 14
recovered key material: 62
CPA on samples shape (82, 19) (range 1:19) and data shape (82,)
Results @ 82 rows, 19 cols (100 rows consumed)
target: 8, phase: 1, #candidates 256
rank:   1, candidate: 0x31, score: 8.000000 @ 2
rank:   2, candidate: 0x4b, score: 2.311398 @ 7
rank:   3, candidate: 0x3a, score: 2.249177 @ 13
rank:   4, candidate: 0xfa, score: 2.234016 @ 10
rank:   5, candidate: 0x35, score: 2.232291 @ 7
recovered key material: 31
CPA on samples shape (81, 20) (range 1:20) and data shape (81,)
Results @ 81 rows, 20 cols (100 rows consumed)
target: 9, phase: 1, #candidates 256
rank:   1, candidate: 0x33, score: 7.205499 @ 10
rank:   2, candidate: 0x54, score: 2.292755 @ 15
rank:   3, candidate: 0x15, score: 2.226108 @ 8
rank:   4, candidate: 0x6b, score: 2.208906 @ 14
rank:   5, candidate: 0x74, score: 2.200654 @ 17
recovered key material: 33
CPA on samples shape (83, 19) (range 1:19) and data shape (83,)
Results @ 83 rows, 19 cols (100 rows consumed)
target: 10, phase: 1, #candidates 256
rank:   1, candidate: 0x35, score: 8.000000 @ 17
rank:   2, candidate: 0x4f, score: 2.230499 @ 15
rank:   3, candidate: 0xa0, score: 2.197782 @ 15
rank:   4, candidate: 0x74, score: 2.185080 @ 2
rank:   5, candidate: 0xd1, score: 2.183511 @ 7
recovered key material: 35
CPA on samples shape (86, 21) (range 1:21) and data shape (86,)
Results @ 86 rows, 21 cols (100 rows consumed)
target: 11, phase: 1, #candidates 256
rank:   1, candidate: 0x5f, score: 7.138007 @ 2
rank:   2, candidate: 0xc3, score: 2.310446 @ 13
rank:   3, candidate: 0xdd, score: 2.203060 @ 15
rank:   4, candidate: 0x26, score: 2.199146 @ 14
rank:   5, candidate: 0x0a, score: 2.194269 @ 21
recovered key material: 5f
CPA on samples shape (85, 19) (range 1:19) and data shape (85,)
Results @ 85 rows, 19 cols (100 rows consumed)
target: 12, phase: 1, #candidates 256
rank:   1, candidate: 0x52, score: 8.000000 @ 2
rank:   2, candidate: 0xa3, score: 2.249309 @ 5
rank:   3, candidate: 0xbe, score: 2.234721 @ 13
rank:   4, candidate: 0x6a, score: 2.215089 @ 19
rank:   5, candidate: 0x7b, score: 2.171197 @ 6
recovered key material: 52
CPA on samples shape (81, 20) (range 1:20) and data shape (81,)
Results @ 81 rows, 20 cols (100 rows consumed)
target: 13, phase: 1, #candidates 256
rank:   1, candidate: 0x5f, score: 7.277094 @ 10
rank:   2, candidate: 0x55, score: 2.217166 @ 16
rank:   3, candidate: 0x8d, score: 2.214655 @ 17
rank:   4, candidate: 0xd1, score: 2.203207 @ 13
rank:   5, candidate: 0x52, score: 2.203173 @ 12
recovered key material: 5f
CPA on samples shape (78, 19) (range 1:19) and data shape (78,)
Results @ 78 rows, 19 cols (100 rows consumed)
target: 14, phase: 1, #candidates 256
rank:   1, candidate: 0x6f, score: 8.000000 @ 17
rank:   2, candidate: 0x2a, score: 2.415309 @ 12
rank:   3, candidate: 0x4f, score: 2.327784 @ 6
rank:   4, candidate: 0x30, score: 2.281356 @ 14
rank:   5, candidate: 0x3e, score: 2.260520 @ 12
recovered key material: 6f
CPA on samples shape (87, 20) (range 1:20) and data shape (87,)
Results @ 87 rows, 20 cols (100 rows consumed)
target: 15, phase: 1, #candidates 256
rank:   1, candidate: 0x52, score: 7.156915 @ 2
rank:   2, candidate: 0x4b, score: 2.150102 @ 14
rank:   3, candidate: 0xf1, score: 2.110684 @ 9
rank:   4, candidate: 0x84, score: 2.105577 @ 16
rank:   5, candidate: 0x3f, score: 2.101487 @ 13
recovered key material: 52
CPA on samples shape (80, 19) (range 1:19) and data shape (80,)
Results @ 80 rows, 19 cols (100 rows consumed)
target: 16, phase: 1, #candidates 256
rank:   1, candidate: 0x35, score: 8.000000 @ 2
rank:   2, candidate: 0x01, score: 2.335403 @ 17
rank:   3, candidate: 0x33, score: 2.237892 @ 6
rank:   4, candidate: 0x58, score: 2.230428 @ 13
rank:   5, candidate: 0xdd, score: 2.199871 @ 17
recovered key material: 35
recovered key: 61316c5f7434623133355f525f6f5235
Out[5]:
16-element Array{UInt8,1}:
 0x61
 0x31
 0x6c
 0x5f
 0x74
 0x34
 0x62
 0x31
 0x33
 0x35
 0x5f
 0x52
 0x5f
 0x6f
 0x52
 0x35

As said before, we did not include the output into the traceset. But apparently this key has entropy low enough to see that it is the right one. :)


In [6]:
print(String(key))


a1l_t4b135_R_oR5