Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save toby-bro/30dfe622cb2a46237ba8b0f131b565c4 to your computer and use it in GitHub Desktop.

Select an option

Save toby-bro/30dfe622cb2a46237ba8b0f131b565c4 to your computer and use it in GitHub Desktop.

CryptoBro en détresse

This challenge gives us the power consumption of a "something" that validates PIN codes. We will be going towards differential power analysis (DPA) to solve this problem.

Initially I tried calculating the mean for all the numbers at the same time, but nothing stood out particularly. So I imagined that the code that checked the PIN was probably checking the numbers one by one. So we are going to proceed the same way.

We calculated the average power consumption for all the codes that started with 0??? then all those like 1???... and compared their averages, the one that stood out the furthest from the lot gives us the right first digit.

We got this output

Analyzing digit position 1...
Digit 0 difference score: 0.065464
Digit 1 difference score: 0.057082
Digit 2 difference score: 0.063797
Digit 3 difference score: 0.053306
Digit 4 difference score: 0.057586
Digit 5 difference score: 0.057638
Digit 6 difference score: 0.053020
Digit 7 difference score: 0.053250
Digit 8 difference score: 0.062998
Digit 9 difference score: 0.524083
Most likely digit at position 1: 9 (score: 0.524083)

This was quite promising as 9 was ten times further from the average than the rest of the digits. So we went on for the second step similarly comparing all the traces of 90??, 91??...

And so on and so forth until the end where we just compared 9460, 9461, 9462...

Which gave us the flag.

The code to do this is the following

from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np

PLOT = False

traces_dir: Path = Path('traces')
trace_files: list[Path] = sorted(list(traces_dir.glob('trace_*.npy')))

all_traces: list[np.ndarray] = []
all_pins: list[str] = []
for trace_path in trace_files:
    pin: str = trace_path.stem.split('_')[1]
    trace: np.ndarray = np.load(trace_path)
    all_traces.append(trace)
    all_pins.append(pin)

all_traces_array: np.ndarray = np.array(all_traces)
print(f'Loaded {len(all_traces)} traces, shape: {all_traces_array.shape}')


def digit_by_digit_dpa() -> str:
    print('\nPerforming digit-by-digit DPA analysis...')
    pin_length: int = len(all_pins[0])
    recovered_pin: str = ''

    for position in range(pin_length):
        print(f'\nAnalyzing digit position {position+1}...')

        filtered_indices: list[int] | range
        filtered_indices = range(len(all_pins))
        if position > 0:
            filtered_indices = [i for i, pin in enumerate(all_pins) if pin.startswith(recovered_pin)]
            print(f'Narrowed down to {len(filtered_indices)} candidates starting with {recovered_pin}')

        max_diff_score: float = -1
        best_digit: int | None = None

        for digit in range(10):
            digit_indices: list[int] = [i for i in filtered_indices if all_pins[i][position] == str(digit)]

            if not digit_indices:
                continue

            other_indices: list[int] = [i for i in filtered_indices if all_pins[i][position] != str(digit)]

            if not other_indices:
                continue

            digit_avg: np.ndarray = np.mean(all_traces_array[digit_indices], axis=0)
            other_avg: np.ndarray = np.mean(all_traces_array[other_indices], axis=0)

            diff: np.ndarray = digit_avg - other_avg

            diff_score: float = np.max(np.abs(diff))

            print(f'Digit {digit} difference score: {diff_score:.6f}')

            if PLOT:
                plt.figure(figsize=(12, 6))
                plt.plot(diff)
                plt.title(f'Difference for digit {digit} at position {position+1}')
                plt.axhline(y=0, color='r', linestyle='-', alpha=0.3)
                plt.savefig(f'diffs/diff_pos{position+1}_digit{digit}.png')
                plt.close()

            if diff_score > max_diff_score:
                max_diff_score = diff_score
                best_digit = digit

        if best_digit is not None:
            recovered_pin += str(best_digit)
            print(f'Most likely digit at position {position+1}: {best_digit} (score: {max_diff_score:.6f})')
        else:
            print(f'Could not determine digit at position {position+1}')
            break

    return recovered_pin


recovered_pin: str = digit_by_digit_dpa()

print(f'\nRecovered PIN: {recovered_pin}')
print(f'Flag: FCSC{{{recovered_pin}}}')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment