Square CTF 2018 C4: leaky power

Analysis

Firstly, we are given two files which contain the plaintexts that were encrypted and the corresponding powertraces, i.e the power dissipation while encrypting those plaintexts. 

Given the challenge text, it was clear that this challenge would involve recovering an AES key using Side Channel Attack using Power Analysis on AES techniques.

Solution

To start this challenge we can plot the powertraces:

import matplotlib.pyplot as plt
import numpy

powertraces = numpy.load(open('leaky_power/powertraces.npy','rb'))

do_plot = True

if do_plot:
	plt.plot(powertraces[0])
	plt.show()

And as result:

The ChipWhisperer Github Repo has example script to break AES using the Power Analysis Side Channel Attack. We will use the modified from of this script to obtain the flag.

This is the code we used:

# Adapted from code at https://wiki.newae.com/Tutorial_B6_Breaking_AES_(Manual_CPA_Attack)

from __future__ import print_function

import base64
import binascii
import json
import re
import sys

import numpy as np

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes


HW = [bin(n).count("1") for n in range(0,256)]

sbox=(
0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16)


def intermediate(pt, keyguess):
    return sbox[pt ^ keyguess]


def do_cpa(pt, traces):
    numtraces = np.shape(traces)[0]-1
    numpoint = np.shape(traces)[1]

    #Use less than the maximum traces by setting numtraces to something
    #numtraces = 10

    #Set 16 to something lower (like 1) to only go through a single subkey
    bestguess = [0]*16
    pge = [256]*16
    for bnum in range(0, 16):
        cpaoutput = [0]*256
        maxcpa = [0]*256
        for kguess in range(0, 256):

            #Initialize arrays & variables to zero
            sumnum = np.zeros(numpoint)
            sumden1 = np.zeros(numpoint)
            sumden2 = np.zeros(numpoint)

            hyp = np.zeros(numtraces)
            for tnum in range(0, numtraces):
                hyp[tnum] = HW[intermediate(pt[tnum][bnum], kguess)]


            #Mean of hypothesis
            meanh = np.mean(hyp, dtype=np.float64)

            #Mean of all points in trace
            meant = np.mean(traces, axis=0, dtype=np.float64)

            #For each trace, do the following
            for tnum in range(0, numtraces):
                hdiff = (hyp[tnum] - meanh)
                tdiff = traces[tnum,:] - meant

                sumnum = sumnum + (hdiff*tdiff)
                sumden1 = sumden1 + hdiff*hdiff
                sumden2 = sumden2 + tdiff*tdiff

            cpaoutput[kguess] = sumnum / np.sqrt( sumden1 * sumden2 )
            maxcpa[kguess] = max(abs(cpaoutput[kguess]))

        bestguess[bnum] = np.argmax(maxcpa)
        
    print(" ")
    print("Best Key Guess: ", end="")
    for b in bestguess: print("%02x" % b, end="")
    print(" ")
        
    return bestguess

    
def b64d(data):
    return base64.urlsafe_b64decode(str(data) + '=' * ((4 - len(data) % 4) % 4))


def main():
    pt = np.load('leaky_power/plaintexts.npy')
    traces = np.load('leaky_power/powertraces.npy')

    key = ''.join(chr(c) for c in do_cpa(pt, traces))
    
    with open('leaky_power/instructions_corrected.jwe', 'rb') as f:
        enc = json.load(f)
        
    ciphertext = b64d(enc['ciphertext'])
    iv = b64d(enc['iv'])
    tag = b64d(enc['tag'])

    decryptor = Cipher(algorithms.AES(key), modes.GCM(iv, tag),
        backend=default_backend()).decryptor()

    plaintext = decryptor.update(ciphertext).decode()
    
    print(" ")
    print(plaintext)


if __name__ == '__main__':
    main()

And this is the result:

The flag was:

flag-e2f27bac480a7857de45

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo di WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione /  Modifica )

Google photo

Stai commentando usando il tuo account Google. Chiudi sessione /  Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione /  Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione /  Modifica )

Connessione a %s...

Questo sito utilizza Akismet per ridurre lo spam. Scopri come vengono elaborati i dati derivati dai commenti.