This is a simulation of a Correlation Power Analysis attack.
The service allows us to encrypt any input, and leaks voltage measurements for the state right after the call to `sub_bytes` in the AES algorithm.
The attack itself is explained in depth [here](https://wiki.newae.com/Correlation_Power_Analysis). We just need to extract the first set of measurements (for the first iteration of AES) and ignore the rest.
We compare the actual measuremetns of the first round to the expected volatage of encrypting our inupt with each possible byte value of a key, and find the closest match. 
```pythonfrom pwn import *import base64from scipy import spatialfrom Crypto.Cipher import AES
# export LANG=C.UTF-8
BLOCK_LEN = 16BITS_IN_BYTE = 8NUM_BYTE_VALUES = 2**BITS_IN_BYTE
VOLTAGE_MIN = 2.5VOLTAGE_MAX = 5
HAMMING_WEIGHT_MIN = 0HAMMING_WEIGHT_MAX = 8
def hamming_weight(x):    return bin(x).count('1') def normalize(value, from_min, from_max, to_min, to_max):    old_range = from_max - from_min    new_range = to_max - to_min
    return ((value - from_min) / old_range) * new_range + to_min
def weight_to_voltage(weight):    return normalize(HAMMING_WEIGHT_MAX - weight, HAMMING_WEIGHT_MIN, HAMMING_WEIGHT_MAX, VOLTAGE_MIN, VOLTAGE_MAX) s_box = (    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,)
expected_volt_per_key_byte = [[0 for x in range(NUM_BYTE_VALUES)] for y in range(NUM_BYTE_VALUES)] 
with log.progress("Calculating expected voltage per key byte") as p:    for key_byte_value in range(NUM_BYTE_VALUES):        for plaintext_byte_value in range(NUM_BYTE_VALUES):            #p.status(f"Key byte: {key_byte_value}, plaintext byte: {plaintext_byte_value}")            value = s_box[key_byte_value ^ plaintext_byte_value]            weight = hamming_weight(value)            voltage = weight_to_voltage(weight)            expected_volt_per_key_byte[key_byte_value][plaintext_byte_value] = voltage
actual_volt_per_key_index = [[] for _ in range(BLOCK_LEN)] 
r = remote("project_power.ichsa.ctf.today", 8012)
r.recvuntil("The following base64-encoded ciphertext is encrypted with aes-128-ecb:\n")encrypted_flag = r.recvlineS(keepends = False)
log.info(f"Encrypted flag: {encrypted_flag}")
with log.progress("Reading measurements") as p:    for byte_val in range(NUM_BYTE_VALUES):        p.status(f"Byte value {byte_val}")        to_encrypt = bytes([byte_val] * BLOCK_LEN)        r.sendlineafter("Enter base64-encoded input to be encrypted: ", base64.b64encode(to_encrypt))        first_round_measurements = map(float, r.recvlineS(keepends=False).strip("[]").split(", "))         for key_index, measurement in enumerate(first_round_measurements):            actual_volt_per_key_index[key_index].append(measurement)
        # Ignore remaining measurements
key = b""
with log.progress("Reconstructing key") as p:    tree = spatial.KDTree(expected_volt_per_key_byte)    for i, actual_volt_vector in enumerate(actual_volt_per_key_index):        p.status(f"Recovering character #{i}")        res = tree.query(actual_volt_vector)        key += bytes([res[1]])
log.success(f"Key: {key}")
unpad = lambda s: s[:-ord(s[len(s) - 1:])]flag = unpad(AES.new(key, AES.MODE_ECB).decrypt(base64.b64decode(encrypted_flag))).decode('ascii')log.success(f"Flag: {flag}")assert(flag == "ICHSA_CTF{Wh0_n3eds_c0mpliC4t3d_m4th_Wh3n_u_HaVe_CPA}")```
