1. Challenge Overview
The challenge provides a remote service that performs encryption. The description hints that the algorithm leaks a "bit" of data during computation. Unlike traditional crypto challenges where you attack the math, here we attack the implementation by observing side-channel leakage.
2. The Vulnerability: Side-Channel Leakage
The core of the problem is a Power Analysis vulnerability. In a real-world scenario, a CPU uses slightly more power to process a 1 than a 0, or takes more time if a specific branch of code is executed.
In this challenge, we assume the leakage allows us to determine if our guess for a specific bit of the key is correct.
3. Exploitation Strategy
The attack is performed bit-by-bit. Instead of brute-forcing $2^{128}$ possibilities (which is impossible), we only need to test each bit position.
- Connect to the challenge via a Python socket.
- Iterate through each bit of the key (from 0 to 127).
- Submit a guess for the current bit.
- Analyze the leakage: If the response indicates a "hit" (usually through a timing delay or a specific returned value representing power "leaked"), we lock that bit as
1. Otherwise, it's a0. - Repeat until the full 128-bit key is reconstructed.
4. The Solution Script
Here is the logic used to automate the recovery of the key:
Python
import socket
import logging
import time
import argparse
from concurrent.futures import ThreadPoolExecutor
from typing import Optional
# Setup professional logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
handlers=[logging.StreamHandler()]
)
logger = logging.getLogger(__name__)
class PowerAnalysisClient:
"""A professional interface for interacting with the challenge server."""
def __init__(self, host: str, port: int, timeout: int = 5):
self.address = (host, port)
self.timeout = timeout
def _communicate(self, payload: str) -> str:
"""Handles the low-level socket lifecycle."""
try:
with socket.create_connection(self.address, timeout=self.timeout) as sock:
sock.sendall(payload.encode() + b'\n')
response = sock.recv(4096).decode().strip()
return response
except socket.error as e:
logger.error(f"Socket error: {e}")
return ""
def measure_leak(self, guess: str, samples: int = 1) -> float:
"""
Measures the side-channel leak.
In professional tools, we sample multiple times to filter out network noise.
"""
timings = []
for _ in range(samples):
start = time.perf_counter()
self._communicate(guess)
timings.append(time.perf_counter() - start)
# Using the median or max is often more stable than the mean in network timing
return max(timings)
class KeyRecoverer:
"""Orchestrates the bit-by-bit recovery of the secret key."""
def __init__(self, client: PowerAnalysisClient, key_length: int = 128):
self.client = client
self.key_length = key_length
self.recovered_bits = ""
def recover(self):
logger.info(f"Starting recovery for {self.key_length}-bit key...")
for bit_index in range(self.key_length):
# Test both possibilities for the current bit
t0 = self.client.measure_leak(self.recovered_bits + "0")
t1 = self.client.measure_leak(self.recovered_bits + "1")
if t1 > t0:
self.recovered_bits += "1"
else:
self.recovered_bits += "0"
if bit_index % 8 == 0:
logger.info(f"Progress: {bit_index}/{self.key_length} bits recovered.")
self.finalize()
def finalize(self):
key_hex = hex(int(self.recovered_bits, 2))[2:].zfill(self.key_length // 4)
logger.info(f"SUCCESS: Recovered Key (Hex): {key_hex}")
if __name__ == "__main__":
# Command-line interface for portability
parser = argparse.ArgumentParser(description="Professional Power Analysis Tool")
parser.add_argument("--host", default="saturn.picoctf.net", help="Server hostname")
parser.add_argument("--port", type=int, required=True, help="Server port")
args = parser.parse_args()
client = PowerAnalysisClient(args.host, args.port)
recoverer = KeyRecoverer(client)
recoverer.recover()5. Conclusion
PowerAnalysis: Warmup demonstrates that even mathematically "perfect" encryption like AES can be broken if the physical hardware leaks information. By measuring metadata (power/time) rather than the data itself, we reduced an exponential problem to a linear one.