Can't Observe CW Husky Glitches

Okay we’ve done a lot of work trying to set up voltage glitching on a target of our own, a MAX78000FTHR board. We removed all the decoupling capacitors (and a buck converter) attached to VCOREA with the exception of a tiny one, C9. We cut an SMA cable so the crowbar is soldered to a Dupont jumper cable connected to PCbite which is touching the positive end of the tiny C9 capacitor. We wrote our own firmware that goes on the device that waits 1ms then runs in a loop 50,000 times and adds 1 to the sum each time in the loop (basic “can you make it not be 50000” setup). We wrote our own custom Target class that interfaces with this loop correctly and have verified that it works.

So now, the next move seemed to be trying to get a glitch happening on the board then we can work more on fine tuning parameters like width, offset, and ext_offset. However with glitch_hp=True, we never get any glitches, even with the max width. Also with glitch_lp=True, the first run causes a hard glitch that resets the board, then normal from there.

Note that the MAX78000 CPU runs at 100MHz which I know is kind of high but I think we made all the settings correct? I’ve attached some snippets below of our code that describes the setup and what we’re doing.

import chipwhisperer as cw

scope = cw.scope(scope_type=cw.scopes.OpenADC)
scope.adc.samples = 131070                          # max supported number by the Husky
scope.gain.gain = 22                                # default setting
scope.clock.adc_mul = 2
scope.clock.clkgen_freq = 100_000_000 # 100MHz, speed of MAX78000 CPU

target = eCTF2025Target('/dev/ttyACM1', 115200, 0.5)

def reset_target():
    """
    CW's nRST pin is connected to MAX78000's RST pin. Bringing it low then high
    will power cycle the board. Total time = 0.55s
    """
    
    global scope
    sleep(0.05)
    scope.io.nrst = 'low'
    sleep(0.05)
    scope.io.nrst = 'high_z'
    sleep(0.45) # this is as little as I can wait for it to finish starting up

reset_target()

# set up the LED glitch trigger condition
scope.trigger.module = "edge_counter"
scope.trigger.edges = 1
scope.trigger.triggers = 'tio1'


# set up the glitch controller
gc = cw.GlitchController(groups=["success", "reset", "normal"], parameters=["width", "offset", "ext_offset", "tries"])
scope.glitch.enabled = True
scope.glitch.clk_src = "pll"                        # our internal PLL is generating the 100MHz clock freq, not an external source
scope.io.glitch_hp = True
scope.io.glitch_lp = False

scope.glitch.output = "glitch_only"                 # don't do anything with the clock
scope.glitch.trigger_src = "ext_single"             # our trigger condition is set above

scope.adc.timeout = 0.01                            # it takes between 0.002 and 0.005 seconds for the loop to run

### SET PARAMETERS ###
"""
Note that no matter what these settings are, with glitch_hp we NEVER see any glitches on our logic analyzer, and with glitch_lp only the FIRST one hard glitches (even if we set width to 0).

Since our clock freq is 100MHz, phase_shift_steps is set to 336.
"""

# generic setting, if we don't do this we can't set steps below
gc.set_global_step(10)

# how many times will we do it with each setting?
gc.set_range("tries", 0, 3)
gc.set_step("tries", 1)

# How wide should the glitch be (in terms of phase shift steps) from 0 to 168?
gc.set_range("width", 0, 168)
gc.set_step("width", 40)

# How far along inSIDE of the clock cycle should be glitch be delivered (in terms of phase
# shift steps) from 0 to 336?
# Note - we probably want to start at 168 as this is where the falling clock edge starts
gc.set_range("offset", 0, 336)
gc.set_step("offset", 40)

# How many clock cycles after the trigger should we be sending our glitch?
gc.set_range("ext_offset", 30, 50)
gc.set_step("ext_offset", 10)

resets = 0
successes = 0

for glitch_setting in gc.glitch_values():
    # gc.glitch_values() iterates through all our settings and returns a tuple of them
    width   = glitch_setting[0]
    offset  = glitch_setting[1]
    ext_off = glitch_setting[2]
    try_no  = glitch_setting[3]

    print(f"[+] width: {width}, offset: {offset}, ext_offset: {ext_off}, try_no: {try_no}")

    if scope.adc.state:
        # can detect crash here (fast) before timing out (slow)
        reset_target()
        gc.add("reset")
        resets += 1
        print("[-] Reset from adc.state")

    # NOW everything is set up for the glitch, let's start it
    print("[+] Sending glitch")
    scope.arm()
    good, sum_val = target.get_loop()
    scope.io.vglitch_reset()

    if not good and sum_val == 1:
        reset_target()
        gc.add("reset")
        resets += 1
        print(f"[-] UART response timed out")
    elif not good and sum_val == 0:
        reset_target()
        gc.add("reset")
        resets += 1
        print(f"[-] Bad UART response data")
    elif sum_val == 50000:
        gc.add("normal")
        print("[+] Normal")
    else:
        gc.add("success")
        successes += 1
        print("[+] Success!")

    scope.sc.arm(False)

Note that we also have a Saleae Logic Analyzer connected to the probes so we can see everything that’s happening (ik the husky also has capabilities like this but we’re more familiar with it). We’ve simply clipped the logic analyzer wires to the PCbite ends.

Here’s an example of running it with glitch_lp = True. The first little spike on VCOREA around 4s is our first reset. The big spike after that is when it glitched the thing and had to be reset (next little spike around 5s). No more spikes after that.

Here’s a close up of the glitch happening. The thing that I don’t understand is the MOMENT we set glitch_lp = True, once we send UART data this happens. Even if we don’t arm the scope, even if we haven’t set glitch parameters yet. If we literally do

# set up target

scope.io.glitch_lp = True

target.get_loop()

It will do that spike the first time. We haven’t armed it or set any parameters but it still goes, and then that never happens again. We are sure the board is working past that point because we still get the 50,000 sum response from the loop messages. Our trigger is found, the loop seems to be working, and the logic analyzer shows all the data so we think it’s all connected properly.

Any idea what we’re doing wrong? I can send a picture of our setup or more code if you need.

There’s a lot of variables at play here. I’m not sure I’ve understood correctly but it sounds like maybe Husky is not issuing glitches when you expect it to? I would focus on that first, without worrying about anything else for now.

And instead of trying to infer whether or not a glitch occurred from other measurements, look directly at the glitch signal on the trigger/glitch MCX port, with scope.io.glitch_trig_mcx = "glitch".

Thank you for pointing us towards the MCX - we didn’t know that that was a thing. To be honest, we’re still kind of confused about what the MCX is - it’s the digital counterpart for the crowbar or something? (don’t even know what that really means) We connected our logic analyzer to the MCX port and can see a little blip (rise) in the signal. As we change the external offset, the position of that blip changes too (with both the LP and HP MOSFETs). That seems to indicate it’s sending a glitch at those times? However, when we switch back to measuring the crowbar instead we don’t see any blips.

Something else we think was previously wrong with our script and have now set is:

for glitch_setting in gc.glitch_values():
    # gc.glitch_values() iterates through all our settings and returns a tuple of them
    scope.glitch.width   = glitch_setting[0]
    scope.glitch.offset  = glitch_setting[1]
    scope.glitch.ext_offset = glitch_setting[2]

Previously it was (notice no scope.glitch):

for glitch_setting in gc.glitch_values():
    # gc.glitch_values() iterates through all our settings and returns a tuple of them
    width   = glitch_setting[0]
    offset  = glitch_setting[1]
    ext_offset = glitch_setting[2]

So we think that may have been part of the problem.

Additionally, setting the width to 168 (which is phase_shift_steps/2, the maximum width according to the documentation) doesn’t make it reset. We are expecting to see a reset with the maximum width - would that be the case?

Also, we realized our logic analyzer can do a maximum of 50Msamples/s, meaning 1 sample for every 2 clock cycles (100MHz CPU). This means a 3ns or even 1/2 clock cycle drop wouldn’t be picked up by an analyzer of this caliber - is that why we’re not seeing a drop? And we can still see the drop using the MCX port? Do we just need to get an oscilloscope or something?


Okay actually, looking a little harder we connected our logic analyzer to the crowbar and there’s this blip that pops up (highlighted at 17 mV), and as we change our external offset the location of that blip changes also. So this seems to indicate a glitch is coming from the CW-Husky? We were expecting to see it get pulled all the way to ground and back up, but due to our low sample rate this may just be what we see instead?

So assuming that all is correct (our offsets are working, the glitch is actually being sent out, etc.), why don’t we get any resets when we send a max-width glitch?

I can’t answer that since this isn’t our target board. But first things first, you need to verify that when you execute a glitch, the target’s supply is shorted to ground.

Second, it’s not true that you’re sending the maximum width glitch. By setting scope.glitch.output to “enable_only”, you can glitch for scope.glitch.repeat clock cycles. We teach this in our voltage glitching introduction notebook. Do be careful to not execute a too-long glitch: shorting ground and VCC for a long time is obviously not good and can cause damage.

If you haven’t gone through the fault101 notebooks, I strongly recommend that you do so first.

When scope.io.glitch_trig_mcx is set to “trigger”, it will be a pulse that indicates when the ADC capture is triggered.

When it is set to “glitch”, it is the output of the glitch module, e.g. in your case it’s what’s sent to the crowbar MOSFETs.

In either case it’s intended to trigger an external oscilloscope. In your case it would be useful to trigger an oscilloscope that is probing your target’s VCC.