Repository navigation
Carter Pattison's Notebook
- Met new members of the VIP.
- Reviewed old work, and researched future work described in presentation.
- Looked into Jesus's PLC Work
- Contacted by Jesus, read papers and suggested material to further understand his work.
- Organized teams and presentation for the device security subteam.
- Created and assigned slides for kickoff presentation.
- Recorded my slides for the presentation.
- Scheduled meeting time with Zeus.
- Finalized slides
- Scheduled meeting with Zeus
##Week 5
- Met with Zeus - discussed semester plan, division of work, literature review, and hardware limitations
- We need a PLC, preferably a 5380.
- We will talk with Hayden about plan.
- Read evaluation paper
* Plan for next week - get familiar with Ghidra and MCP so, when we get the hardware, we can start work
- Ship board to Zeus or hook to ResearchNet?
- Could we use it while hooked to a server?
- Looked into MCP for Ghidra
- As well as an eval harness for this
)
- https://github.com/clearbluejar/pyghidra-mcp
- Seems good for LLMs; headless, containerized, and relatively simple.
- Mainly just waiting for physical 5380.
- Researched vulnerabilities for Rockwell Automation 5370s
- Joined VIP Discord
- Filled out VIP team form
- Explored teams and familiarized myself with current operations
- Researched beginner side-channel attack project
- Joined device security sub-team
- Met on Sunday, Jan 26th to work on slides
- Started basic outline of groups (side channel attacks, PUFs, fault injection, and disk encryption)
- Presented slides in class (1.29)
- Investigated literature on side-channel attacks
- Surveyed attack categories: timing, power analysis, electromagnetic
- Power analysis and timing side-channel attacks are most relevant to our current direction of research
- A couple interesting papers on Raspberry Pis (https://ieeexplore.ieee.org/document/7991108) (https://ieeexplore.ieee.org/document/7991108)
- They show full key recovery from power traces
- Created and initialized the side-channel-attacks repository (https://github.com/cps-vip/side-channel-attacks)
- Implemented a password-checking timing-based side-channel attack in Rust with Estelle (https://github.com/cps-vip/side-channel-attacks/tree/main/timing_attack_rust)
The victim is a vulnerable password checker that compares the attempt to the secret byte by byte and returns false on the first mismatch, with an artificial 9 ms per-character delay to exaggerate the leak:
const SECRET: &str = "Pa55word123";
fn vulnerable_check_password(attempt: &str) -> bool {
if attempt.len() != SECRET.len() { return false; }
let attempt_bytes = attempt.as_bytes();
let secret_bytes = SECRET.as_bytes();
for i in 0..attempt.len() {
if attempt_bytes[i] != secret_bytes[i] { return false; }
thread::sleep(Duration::from_millis(9)); // exaggerates the side-channel
}
true
}The attacker recovers the secret one position at a time. For each position it tries every candidate from [a-zA-Z0-9], pads the rest of the string with filler, calls the checker, and keeps the candidate whose call took the longest, because a longer call means the checker matched one more byte before exiting:
for candidate in charset.chars() {
let mut attempt = current_attempt.clone();
attempt.push(candidate);
while attempt.len() < SECRET.len() { attempt.push('A'); }
let start = Instant::now();
vulnerable_check_password(&attempt);
let duration = start.elapsed();
if duration > longest_duration {
longest_duration = duration;
probable_char = Some(candidate);
}
}- Recovers the full 11-character
"Pa55word123"reliably with a 9 ms per-char delay - Attack succeeded down to ~8 ms delay; below that the OS scheduling jitter on a single measurement starts dominating the per-character signal
- A single
Instant::now()measurement per candidate is too noisy at small delays: a context switch can swamp the signal - The
thread::sleepbased victim is artificial; we want a victim whose per-char work is real CPU work rather than a sleep
- Move from one-shot timing to multi-sample statistics (median or trimmed mean over many runs per candidate) so we can drop artificial delay
- Replace
thread::sleepwith a real per-character workload
- Researched the AES-128 block cipher (SubBytes, ShiftRows, MixColumns, AddRoundKey) and how CPA targets the round-1 S-box output
- Implemented a Correlation Power Analysis (CPA) toy model in Python with Estelle (https://github.com/cps-vip/side-channel-attacks/tree/main/AES-128-CPA-Attack)
We don't have real power traces yet, so we simulate them. For each random plaintext byte, we compute the round-1 intermediate SBOX[plaintext ^ key], take its Hamming weight as the "leak", and add Gaussian noise:
intermediate = plaintexts ^ key # (num_traces, 16)
intermediate_values = SBOX[intermediate]
traces = hamming_weight(intermediate_values).astype(np.float64)
traces += np.random.normal(0, noise_std, traces.shape)The attacker then loops over all 256 candidate values for one key byte, builds the hypothetical Hamming weight under each guess, and computes the Pearson correlation between that hypothesis and the real trace column. The correct key byte is whichever guess produces the highest correlation:
for guess in range(256):
hypothetical_intermediate = SBOX[plaintexts[:, byte_index] ^ guess]
hypothetical_weight = hamming_weight(hypothetical_intermediate)
# Pearson correlation against the observed trace column
pearson_correlations[guess] = np.corrcoef(hypothetical_weight, traces[:, byte_index])[0, 1]
best_guess = int(np.argmax(np.abs(pearson_correlations)))- Target operation: S-box output of round 1
- Leakage model: Hamming weight
- Trace source: simulated, Gaussian noise (std ≈ 1–2)
- Number of traces: 100–500
- Correlation: Pearson
- Recovers all 16 bytes of a random known key from simulated traces at noise std 1.0; degrades gracefully as noise grows
- Simulated traces are unrealistically clean - a single time sample per encryption with i.i.d. Gaussian noise; real traces are vectors of thousands of samples with structured noise
- Continue developing the timing attack on the password checker
- Add visualizations (correlation-vs-guess plots per byte) to make the CPA result legible
- Define research scope: timing attack on AES-128 + roadmap toward physical CPA on hardware
- Wrote the repository README documenting structure, current status, and next steps so the rest of the sub-team and future students can onboard (https://github.com/cps-vip/side-channel-attacks/blob/main/README.md)
- Piper built a Python version of the timing attack with a separate victim module (
password_checkers.py), measurement script (measurement.py), and attacker (attacker.py) so attacker statistics can be iterated on without recompiling Rust (https://github.com/cps-vip/side-channel-attacks/tree/main/timing_attack_python)
The Python victim mirrors the Rust victimso we can A/B them:
def vulnerable_check(attempt):
if len(attempt) != len(SECRET_PASSWORD): return False
for i in range(len(SECRET_PASSWORD)):
if attempt[i] != SECRET_PASSWORD[i]:
return False # early exit -> the leak
return True
def constant_time_check(attempt):
if len(attempt) != len(SECRET_PASSWORD): return False
result = 0
for i in range(len(SECRET_PASSWORD)):
result |= ord(attempt[i]) ^ ord(SECRET_PASSWORD[i])
return result == 0The attacker drops the artificial delay and instead averages over many samples per candidate (5000 perf-counter measurements, taking the median to suppress OS jitter):
sample_times = []
for _ in range(5000):
start = time.perf_counter_ns()
vulnerable_check(test_str)
end = time.perf_counter_ns()
sample_times.append(end - start)
timings[char] = statistics.median(sample_times)
right_char = max(timings, key=timings.get)- Splitting victim and attacker into separate files (and eventually separate processes) makes the experiment much easier to iterate
- Median over thousands of samples + a tiny
time.sleep(0.0001)per character recovers the password without a 9 ms artificial delay
- Drop the per-char sleep entirely; replace it with real CPU work in the Rust victim so we can attack a more realistic target
- Added the first round of visualizations for the AES-128 CPA toy model (correlation-vs-key-guess plots per byte) (https://github.com/cps-vip/side-channel-attacks/tree/main/AES-128-CPA-Attack)
- Wrote up details on the timing-attack angle for AES-128
- For each target key byte, the visualization plots Pearson correlation of the Hamming-weight-of-S-box-output model against the simulated trace column for every key-byte hypothesis (0–255)
- The correct key byte shows up as a clear peak; incorrect guesses cluster near zero correlation
- The plots make it obvious why "more traces" = "cleaner peak" incorrect guesses' correlations shrink while the correct guess stays roughly fixed
- Simulated traces are still unrealistically clean; we need a real or more realistic noisy trace source before claiming the attack "works"
- Aly added a vulnerable Python AES-128 implementation (
aes128_timing_vulnerable.py) and moved it into theAES-128-CPA-Attack/folder so the repo has a single consistent attack target (https://github.com/cps-vip/side-channel-attacks/blob/main/AES-128-CPA-Attack/aes128_timing_vulnerable.py)
- The vulnerable Python implementation deliberately keeps data-dependent branches/lookups so a timing-based analysis has something measurable to attack (vs. a constant-time reference)
- This gives us a software target for an end-to-end timing attack on AES-128 before we move to hardware
- Having a Python victim alongside the Rust victim lets us prototype attacks in Python first, then port the strongest one to Rust for cleaner timing
- Refine the Rust password-checker into a version with realistic per-char work that's easier for an external harness to drive
- Aly refined the Rust timing-attack code into
timing_attack_refined_rust/password_checker.rs, merged remote changes, and resolved repo conflicts so the original and refined versions live side by side (https://github.com/cps-vip/side-channel-attacks/blob/main/timing_attack_refined_rust/password_checker.rs)
Two key changes vs. Week 5: (a) thread::sleep is replaced with real CPU work, and (b) std::hint::black_box is wrapped around the comparison and the work so the optimizer can't fold the loop away:
fn vulnerable_check_password(attempt: &str) -> bool {
if attempt.len() != SECRET.len() { return false; }
let attempt_bytes = attempt.as_bytes();
let secret_bytes = SECRET.as_bytes();
for i in 0..attempt_bytes.len() {
if black_box(attempt_bytes[i]) != black_box(secret_bytes[i]) {
return false; // early-exit vulnerability
}
let _ = black_box(do_per_char_work(secret_bytes[i]));
}
true
}
#[inline(never)]
fn do_per_char_work(byte: u8) -> u64 {
let mut acc: u64 = byte as u64; // seed from input prevents constant-folding
for _ in 0..500 {
acc = black_box(acc.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407));
}
acc
}The binary runs as a stdin/stdout oraclel]; the attacker pipes one attempt per line and times round-trip so this works as a true black-box target.
-
#[inline(never)]+black_boxis what makes the per-char work survive--release; without them the optimizer collapses the loop and the timing channel disappears - Seeding
accfrom the byte value chains iterations to the input, which prevents the compiler from precomputing the loop
- Spring break. reviewed prior visualizations and planned the volatile/black-box timing experiments
- Piper uploaded a new batch of experiment files (precursor to
timing_attack_volatile/)
- plan is toreplace
black_boxwith explicitread_volatile/write_volatileso the per-char work survives aggressive optimizers, then run a full sample-count and iteration-count sweep to find where the attack actually breaks
- Build the visualizations for the volatile timing attack so we can see whether per-character timing differences stay separable as we shrink the per-char workload
- Added visualizations for the volatile timing attack: per-position timing bar charts, per-character distribution plots, confidence-gap plots, and a plot showing the timing increment per matched character (https://github.com/cps-vip/side-channel-attacks/tree/main/timing_attack_volatile)
- Added more per-byte AES-128 CPA visualizations to support the writeup
- Piper uploaded supporting files for the iteration-count / sample-count sweep workflow
The volatile victim uses read_volatile/write_volatile instead of black_box so the per-char busy loop is guaranteed to execute on each character match:
#[inline(never)]
fn do_per_char_work(byte: u8) -> u64 {
let mut buf: [u64; 4] = [byte as u64, 1, 2, 3];
for i in 0..ITERS {
let prev = unsafe { std::ptr::read_volatile(&buf[(i % 4) as usize]) };
let next = prev.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
unsafe { std::ptr::write_volatile(&mut buf[((i + 1) % 4) as usize], next) };
}
unsafe { std::ptr::read_volatile(&buf[0]) }
}The attacker takes 201 samples per candidate, sorts, drops the top and bottom 20%, and averages the middle (a trimmed mean) much more robust than median against bimodal jitter:
times.sort_unstable();
let trim = SAMPLES / 5;
let trimmed = ×[trim..SAMPLES - trim];
trimmed.iter().sum::<u64>() / trimmed.len() as u64- At small
ITERSthe per-char work is fast enough that a single OS scheduling event swamps the signal; trimmed mean over 200+ samples per candidate is what makes it stable
- Standardized axis labels, titles, colormaps, and figure sizes across the AES CPA and volatile-timing plots so the final-presentation slides look consistent
- Reviewed all four "fig*" outputs (
fig1_bar_chart,fig2_distributions,fig3_confidence_gap,fig4_staircase) to pick the strongest two for the slides
- generate the final iteration-count and sample-count sweep CSVs + sample pictures for the presentation, and build the true black-box variant where the attacker has zero source visibility into the checker
- Delivered final presentation Wed Apr 23
- Piper finished
timing_attack_volatile_blackbox/, the password checker lives in a separate module (checker.rs) and the attacker (main.rs) only callscheck_password(&str) -> bool, with no source-level access toSECRETor any internal state (https://github.com/cps-vip/side-channel-attacks/tree/main/timing_attack_volatile_blackbox) - Generated the final iteration-sweep and sample-sweep CSVs (
iter_sweep_stats.csv,sample_sweep_stats.csv) and the matching visualizers (success rate vs. samples, success rate vs. iterations, runtime, confidence gap, char-level accuracy) - Produced sample result pictures
// checker.rs — the attacker has no access to SECRET
const SECRET: &str = "r4ndomP@ss";
#[inline(never)]
pub fn check_password(attempt: &str) -> bool {
if attempt.len() != SECRET.len() { return false; }
let attempt_bytes = attempt.as_bytes();
let secret_bytes = SECRET.as_bytes();
for i in 0..attempt_bytes.len() {
if attempt_bytes[i] != secret_bytes[i] { return false; }
let _ = do_per_char_work(secret_bytes[i]);
}
true
}The attacker calls the oracle, takes a trimmed mean, picks the slowest candidate per position, and records both the chosen character and the runner-up so we can compute a confidence gap:
let result = checker::check_password(attempt);
let _ = unsafe { std::ptr::read_volatile(&result) }; // prevents DCE on the bool
// ...
times.sort_unstable_by(|a, b| b.1.cmp(&a.1));
let (best_char, best_time) = times[0];
let (second_char, second_time) = times[1];
let gap_ratio = best_time as f64 / second_time as f64;- End-to-end black-box recovery of
"r4ndomP@ss"byte by byte using only the boolean oracle and wall-clock timing - Sample sweep shows success rate climbing past ~95% once samples-per-candidate gets to ~50–100 with
ITERS=10000; below that the gap closes and the attack picks the wrong byte - AES-128 CPA toy model: full 16-byte key recovery from simulated Hamming-weight traces, with per-byte correlation plots showing clean peaks at the correct guesses
- The "iteration count" knob in
do_per_char_workis quite important, its what controls how big the timing signal is, and the sweep makes that explicit
- Move from simulated AES traces to traces captured from real hardware (ChipWhisperer / RPi / STM32)
- Re-run the volatile black-box attack against a constant-time reference checker to confirm the mitigation actually closes the channel (we have
constant_time_checkinpassword_checkers.pybut never benchmarked it under the same harness) - Begin physical CPA on hardware and iterate: crack a target → add countermeasures → crack again, with DPA as the multi-semester stretch goal
Active Notebooks
- Aditi Kulkarni's Notebook
- Aly Aadil Salewala's Notebook
- Carter Pattison's Notebook
- Christopher Gossmann's Notebook
- Esha Pansara's Notebook
- Estelle Jung's Notebook
- Favour Adekola's Notebook
- Galadriel Cho's Notebook
- Prisha Baravkar's Notebook
- Rishika Vunnama's Notebook
- Ryosuke Shibuya's notebook
- Txaber Trevino's Notebook
- Zeus Banda's Notebook
Archive
- Adam McAndrews Notebook
- Albert Nguyen Notebook
- Alexander Rice's Notebook
- Amani Bedwa's Notebook
- Aparupa Brahma's Notebook
- Arya Gujarathi's Notebook
- Bermet Talantbekova's Notebook
- Calvin Rioux's Notebook
- Christina Shih Notebook
- Chungman Kim's NoteBook
- Connor McCary's Notebook
- Dhruv Patels Virtual Notebook
- Elliot Kantor's Notebook
- Ethan Lee Notebook
- Filippos Notebook
- Gweneth Smith's Notebook
- Hubert Pan's Notebook
- Hussein Rmaile Notebook
- Isabella Jemal's Notebook
- Joseph Masson's Notebook
- Jude Karaki Notebook
- Kaden McCartney's Notebook
- Maria Cymbalyuk's Notebook
- Natalie Ajemian's Notebook
- Nguyen Nguyen's Notebook
- Nicholas Reed's NoteBook
- Piper Wright's Notebook
- Quintin's notebook
- Rahil's Notebook
- Raj Patel Notebook
- Reva Hajarnis Notebook
- Rick Berglund's Notebook
- Rossy's Notebook
- Sabina Sokol's Notebook
- Safiy Malik Notebook
- Sam Ling's Notebook
- Sam Litchfield Example Notebook
- Shrayes Upadhyayula Notebook
- Soyoung Moon's Notebook
- Suraj Shekar Notebook
- Vidur Modgil's Notebook
- Xin Zhao Notebook
- Xinyan(Rose) Jiang's Notebook
- Zaeem Ahmad Notebook