Logo

Writeup

Power of Statistics — Writeup

0 0xAsta
February 6, 2026
2 min read
hardware side-channel cpa sbox firmware reversing

Power of Statistics — Writeup

This writeup explains how to solve the Power of Statistics hardware challenge using CPA (Correlation Power Analysis) on power traces to recover a 16‑byte key, then invert the cipher to decrypt the encrypted flag. The firmware is given as a decompiled listing and an ELF, plus plaintexts and traces.

Challenge Interface

Summary

The device applies an 8‑round, 16‑byte block transform built from an S‑box. Each round computes sbox[key[i] ^ pt[i]] for i=0..7, rotates those outputs into the first 8 bytes, and mixes them into bytes 8..15 with an XOR against another S‑box output. The trigger is raised around this computation, giving clean side‑channel alignment.

We use a Hamming‑weight model on the S‑box output for each key byte, correlate against the traces, select the best key guess per byte, and then invert the round function 8 times to decrypt the ciphertext. The final flag is recovered (redacted below).

Files Provided

  • crypto_firmware.elf — binary containing the S‑box
  • plaintext.npy — 1000 plaintexts (shape 1000×16)
  • traces.npy — 1000 traces (shape 1000×1500)
  • encrypted_flag — 16‑byte ciphertext block
  • crypto_firmware_decompiled.txt — decompiled firmware from dogbolt.org

Firmware Analysis

Core encryption in get_pt()

The encryption happens in get_pt(). The decompiled code shows a loop with 8 rounds and the power trigger:

trigger_high();
int32_t i_1 = 8;
do {
char r8_1 = sbox[key[0] ^ *pt];
char lr_1 = sbox[key[1] ^ pt[1]];
char r12_1 = sbox[key[2] ^ pt[2]];
char r7_1 = sbox[key[3] ^ pt[3]];
char r6_1 = sbox[key[4] ^ pt[4]];
char r0 = sbox[key[5] ^ pt[5]];
char r1_7 = sbox[key[6] ^ pt[6]];
uint8_t r2_17 = sbox[key[7] ^ pt[7]];
*pt = r2_17;
pt[1] = r8_1;
pt[2] = lr_1;
pt[3] = r12_1;
pt[4] = r7_1;
pt[5] = r6_1;
pt[6] = r0;
pt[7] = r1_7;
pt[8] = r8_1 ^ sbox[key[8] ^ pt[8]];
pt[9] = lr_1 ^ sbox[key[9] ^ pt[9]];
pt[0xa]= r12_1 ^ sbox[key[0xa]^ pt[0xa]];
pt[0xb]= r7_1 ^ sbox[key[0xb]^ pt[0xb]];
pt[0xc]= r6_1 ^ sbox[key[0xc]^ pt[0xc]];
pt[0xd]= r0 ^ sbox[key[0xd]^ pt[0xd]];
pt[0xe]= r1_7 ^ sbox[key[0xe]^ pt[0xe]];
pt[0xf]= r2_17 ^ sbox[key[0xf]^ pt[0xf]];
} while (i != 1);
trigger_low();

Key observations:

  • The sensitive computation is the S‑box output sbox[key[i] ^ pt[i]].
  • This is the textbook target for CPA with a Hamming‑weight leakage model.
  • The trigger surrounds the entire 8‑round block, so the traces are well aligned.

S‑box location

The decompiled reset handler copies the S‑box from flash into RAM:

while (&sbox[r1] < &__TMC_END__)
{
*(&sbox + r1) = *(0x80014c0 + r1);
r1 += 4;
}

From the ELF layout, 0x080014c0 maps to file offset 0x020000, so the first 256 bytes at that offset in crypto_firmware.elf are the S‑box.

Side‑Channel Attack (CPA)

Leakage model

We assume Hamming‑weight leakage on the S‑box output:

L ≈ HW(SBOX[pt_byte ^ key_guess])

We compute, for each byte index 0..15:

  1. For each key guess k in 0..255, compute the hypothesis vector: h = HW(SBOX[pt[:,byte] ^ k])
  2. Correlate h with every time sample in the trace.
  3. Use the max absolute correlation across time as the score.
  4. Select the k with the highest score.

Why this works

The trigger isolates the S‑box computation, and each trace corresponds to a known plaintext, so the correlation peak appears when the hypothetical S‑box output matches the real device state.

Key Recovery

Running CPA over all 16 bytes yields a full 16‑byte key. (Key intentionally omitted in this write‑up.)

Cipher Inversion

We invert one round analytically using the S‑box inverse.

Let the output state be q[0..15]. From the round structure:

  • q[0] = a7, q[1] = a0, q[2] = a1, …, q[7] = a6
  • q[8] = a0 ^ sbox[key8 ^ p8]
  • q[9] = a1 ^ sbox[key9 ^ p9]
  • q[15] = a7 ^ sbox[key15 ^ p15]

Recover the previous state:

p0 = key0 ^ sbox_inv[a0]
p1 = key1 ^ sbox_inv[a1]
...
p7 = key7 ^ sbox_inv[a7]
p8 = key8 ^ sbox_inv[q8 ^ a0]
p9 = key9 ^ sbox_inv[q9 ^ a1]
...
p15 = key15 ^ sbox_inv[q15 ^ a7]

Apply this inverse 8 times to the encrypted_flag block.

Final Result

The decrypted plaintext yields the flag.

Flag (redacted): FlagY{***********}

Flag Output (redacted)

Notes

  • The attack succeeds with just 1000 traces because the S‑box output is a strong, single‑byte leakage point.
  • The cipher is not AES; it’s a custom 8‑round S‑box permutation + XOR mix.
  • The trigger makes this a clean CPA target with minimal preprocessing.