ChipWhisperer
An open-source capture and glitch platform for side-channel and fault-injection work, built around owning the target's clock so power traces line up sample for sample and a glitch can be aimed at a chosen clock cycle instead of a chosen microsecond.
The ChipWhisperer is NewAE's open-source platform for the two physical attacks that are governed by timing rather than by a software bug: power side-channel capture and fault injection. Hardware, firmware and the chipwhisperer Python package are all published, and the whole thing is driven from Python, usually out of a Jupyter notebook.
What separates it from a bench oscilloscope plus a pulse generator is that it owns the target's clock. The capture board generates the clock the target runs on and derives its own sampling clock from the same source, so a sample index means the same point of execution on every capture, and a glitch can be placed on a numbered clock cycle. That is a different unit of work from "trigger a scope and hope the jitter is small", and it is the reason a power analysis campaign that would need alignment gymnastics on a scope often needs none here.
Two boards, on purpose
The platform is deliberately split into a capture board and a target board.
The capture board holds the ADC front end, the clock generator, the glitch generator, the trigger inputs and the USB link to the host. It never runs the victim code. The target board is the victim: a small microcontroller you compile firmware for and deliberately attack.
- The CW308 UFO board is a baseboard that takes swappable target modules, so the same capture board attacks an STM32, an XMEGA, a RISC-V part or an FPGA without rewiring.
- The CW305 is an Artix-7 FPGA target board, used when the victim is a hardware crypto core rather than software.
- On the one-part ChipWhisperer-Lite, the two halves are on a single PCB separated by a break-away line, so a beginner gets a known-good target for free.
The split is what makes the platform teachable. You develop and debug the attack against a target whose firmware you wrote and whose leakage you can verify, then move the same script to something you do not control. Skipping that step is the single most common reason a first campaign produces nothing and the operator cannot tell whether the fault is in the rig, the model, or the target.
The capture side
MEASURE is a single-ended ADC input that reads the voltage across a shunt resistor placed in the target's supply or ground path. The CW308 has that shunt built in; on your own board you have to add one, or fall back to an EM probe.
The settings that decide whether a capture is usable:
scope.clock.clkgen_freqsets the clock handed to the target, andscope.clock.adc_src = "clkgen_x4"locks the ADC to four samples per target clock cycle. Synchronous capture is the whole point; do not disable it because a higher raw sample rate sounds better.scope.gain.dbsets the analogue gain. Too low buries the signal in quantisation noise, too high clips it, and a clipped trace correlates against nothing.scope.adc.samplesandscope.adc.offsetcut the window: how many samples to keep, and how many cycles after the trigger to start keeping them. The buffer is finite. You capture a window around one operation, not a whole boot.scope.trigger.triggers = "tio4"withscope.adc.basic_mode = "rising_edge"is the default IO trigger: the target toggles a pin, the capture starts.
The Pro and Husky add a SAD trigger (sum of absolute differences), which arms on a stored reference snippet of the power trace and fires when the live signal matches it. That is the answer when the target gives you no trigger pin at all, which is the normal case outside a training board.
The glitch side
The same board generates faults, in two forms.
Clock glitching inserts an extra or malformed edge into the clock the capture board is already supplying, so an instruction fetch or a register latch happens at the wrong moment. scope.glitch.output is set to "clock_xor" or "clock_or" for this. It only works on a target that runs from an external clock you supply, which in practice means a training board or an older MCU. A part booting from its internal RC oscillator cannot be clock-glitched at all.
Voltage glitching briefly shorts the target's core supply to ground through a crowbar transistor, collapsing the rail for a fraction of a cycle. The crowbar MOSFETs are on the capture board, low-power and high-power, and you enable them with scope.io.glitch_lp = True and scope.io.glitch_hp = True; scope.glitch.output becomes "glitch_only". This is the technique that still applies to a real device, and it is what the secure boot bypass in the fault-injection course is built on.
Both are placed with the same three numbers, and their units are where nearly every beginner goes wrong:
| Setting | What it means | Unit |
|---|---|---|
scope.glitch.ext_offset |
how many target clock cycles after the trigger to fire | clock cycles |
scope.glitch.repeat |
how many consecutive cycles to keep glitching | clock cycles |
scope.glitch.width / .offset |
the shape of the pulse inside one cycle, and its phase | percentage of one clock period on Lite and Pro |
There is no nanosecond and no microsecond anywhere in that table. The WHEN is a cycle count, and the pulse shape is a fraction of a period, so both scale with clkgen_freq: change the target clock and every glitch parameter you found moves with it.
A capture and a glitch, end to end
import chipwhisperer as cw
scope = cw.scope() # autodetects Nano/Lite/Pro/Husky
target = cw.target(scope, cw.targets.SimpleSerial)
scope.default_setup() # sane clock, gain, trigger
# --- one power trace -------------------------------------------------
trace = cw.capture_trace(scope, target, bytearray(16), bytearray(16))
print(trace.wave.shape, trace.textout) # samples, and what the target replied
# --- one voltage glitch ----------------------------------------------
scope.glitch.clk_src = "clkgen"
scope.glitch.output = "glitch_only"
scope.glitch.trigger_src = "ext_single"
scope.io.glitch_lp = True # arm the on-board crowbar
scope.glitch.ext_offset = 1200 # WHEN, in target clock cycles
scope.glitch.repeat = 3 # HOW MANY cycles
scope.glitch.width = 8.2 # pulse shape, percent of a period
scope.io.nrst = "low"; scope.io.nrst = "high_z" # reset, then let it run
scope.arm()
scope.capture()
print(target.read(timeout=100)) # NORMAL, silence, or something new
scope.default_setup() is doing real work in that snippet: it sets the clock, the ADC source, the gain, the trigger pin and the IO routing to the values every tutorial assumes. Reading what it sets, once, saves a lot of guessing later.
Which model, and what actually differs
| Board | What it is for |
|---|---|
| ChipWhisperer-Nano | the cheapest teaching board, with its target integrated; coarse glitch timing and a low fixed sample rate |
| ChipWhisperer-Lite (CW1173) | the standard rig: OpenADC capture, clock generation, crowbar glitching, available with an integrated target |
| ChipWhisperer-Pro (CW1200) | a much larger capture buffer, trace streaming, and the SAD trigger |
| ChipWhisperer-Husky | the current flagship: higher sample rate and ADC resolution, finer glitch phase control, SAD trigger |
Husky gates its modules: scope.glitch.enabled = True before any glitch setting takes effect. A Lite script copied onto a Husky silently does nothing until that line is added.
Where it stops
- Synchronous capture needs a clock you can drive. On a target with an internal oscillator, a PLL you cannot reach, or aggressive clock gating, you fall back to asynchronous sampling and inherit every alignment problem a plain oscilloscope has.
- Clock glitching barely applies outside training boards. Nearly every modern part boots from an internal clock source. Assume voltage or EM for real targets.
- Voltage glitching needs the core rail. A modern SoC with on-die regulators, several rails and hundreds of decoupling capacitors may not see your crowbar at all. The bench work of finding, exposing and decoupling the right rail is most of the effort, and it is destructive.
- It is not an oscilloscope. The capture buffer holds a window, the front end is single-ended and gain-limited, and there is no second channel to probe something else with. Keep a real scope on the bench for debugging the rig.
- It reads nothing on its own. It has no debug transport; it cannot halt a core or dump memory. That is a debug probe job, and the two are used together.
- A negative result is not immunity. "No glitch found across this sweep, with this rig" is a finding. "Not vulnerable to fault injection" is not something one campaign establishes.
Pitfalls
- Reading
widthandoffsetas time. On Lite and Pro they are a percentage of one clock period, and the delay from the trigger isext_offsetin clock cycles. A course or lab shim that exposesoffset_usandwidth_ns(the Espilon fault-injection lab does) is teaching the concept with friendlier units; the hardware counts cycles, and your notes do not transfer verbatim. - Glitch settings with no crowbar enabled. Everything reads back correctly and nothing happens, because
scope.io.glitch_lpandglitch_hpare both stillFalse. - No clock reaching the target.
scope.io.hs2 = "clkgen"is what puts the generated clock on the HS2 pin. Without it, clock glitching does nothing and synchronous capture is not actually synchronous. - Not resetting between attempts. A target left crashed by attempt 41 will report a "success" on attempt 42 that has nothing to do with attempt 42. Reset, and verify the target answers normally, before every shot.
- Sweeping before the trigger is stable. If t=0 moves,
ext_offsetmeans a different instruction each run and the sweep is measuring noise. - Firmware and Python package out of step. The capture board carries its own firmware and
cw.scope()complains when it does not match the installed package. Take the warning seriously and runscope.upgrade_firmware(); a mismatched pair fails in ways that look like target behaviour. - Comparing traces captured at different gains or clock frequencies. Both change the trace shape. A campaign has to be captured under one configuration, start to finish.