<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://lightturtles.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://lightturtles.github.io/" rel="alternate" type="text/html" /><updated>2026-10-28T12:01:58-05:00</updated><id>https://lightturtles.github.io/feed.xml</id><title type="html">LightTurtles, LLC</title><subtitle>Consulting, education, and technical writing at the intersection of hardware, firmware, and real-world constraints.
</subtitle><author><name>Nathan Jones</name><email>contact@lightturtles.com</email></author><entry><title type="html">How Hardware Gets Hacked (Part 10): The ‘Birthday Problem’ Attack</title><link href="https://lightturtles.github.io/works/2026/10/26/how-hardware-gets-hacked-part-10-the-birthday-problem-attack/" rel="alternate" type="text/html" title="How Hardware Gets Hacked (Part 10): The ‘Birthday Problem’ Attack" /><published>2026-10-26T00:00:00-05:00</published><updated>2026-10-26T00:00:00-05:00</updated><id>https://lightturtles.github.io/works/2026/10/26/how-hardware-gets-hacked-part-10-the-birthday-problem-attack</id><content type="html" xml:base="https://lightturtles.github.io/works/2026/10/26/how-hardware-gets-hacked-part-10-the-birthday-problem-attack/"><![CDATA[<h1 id="contents">Contents</h1>

<ul id="markdown-toc">
  <li><a href="#contents" id="markdown-toc-contents">Contents</a></li>
  <li><a href="#introduction" id="markdown-toc-introduction">Introduction</a></li>
  <li><a href="#the-birthday-problem" id="markdown-toc-the-birthday-problem">The “Birthday Problem”</a></li>
  <li><a href="#attack-8-the-birthday-problem-attack" id="markdown-toc-attack-8-the-birthday-problem-attack">Attack #8: The “Birthday Problem” attack</a></li>
  <li><a href="#defense-8-widen-the-nonces" id="markdown-toc-defense-8-widen-the-nonces">Defense #8: Widen the nonces</a></li>
  <li><a href="#other-defenses" id="markdown-toc-other-defenses">Other defenses</a>    <ul>
      <li><a href="#the-problem-with-oracles" id="markdown-toc-the-problem-with-oracles">The problem with “oracles”</a></li>
    </ul>
  </li>
  <li><a href="#updated-threat-model" id="markdown-toc-updated-threat-model">Updated threat model</a></li>
  <li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
</ul>

<h1 id="introduction">Introduction</h1>

<p>When we <a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-7-freshness-and-randomness">last left the unlocking process</a>, things seemed pretty secure: the car generates a totally random nonce (“number used only once”) which the attacker couldn’t possibly have guessed ahead of time. The fob then has to provide the proper MAC value of this nonce, computed using the <code class="language-plaintext highlighter-rouge">unlock_key</code>, which is unknown to an attacker. What could go wrong?</p>

<p><img src="/assets/hhghp10/hhghp9b_00_challenge_response_sequence_diagram_300.png" style="display: block; margin: 0 auto;" /></p>

<p>Ah, famous last words. In this article, we’ll discover <em>yet another</em> type of replay attack based on something called the “birthday problem” and the fact that even PRNGs repeat values WAY more often than you’d think they would.</p>

<h1 id="the-birthday-problem">The “Birthday Problem”</h1>

<p>Let’s take a closer look at the phrase “totally random nonce.” In reality, this isn’t quite true. Sure, an attacker can’t guess what nonce values are coming next. But the nonce itself has a predetermined size, so there are only so many nonces that could be generated before our PRNG has to emit a duplicate. If our car repeats a nonce and an attacker happened to save the fob’s MAC response for that first nonce, then we’re back in replay territory!</p>

<p><img src="/assets/hhghp10/hhghp9b_01_table_attack_300.png" alt="" /></p>

<p>All an attacker has to do is collect enough <code class="language-plaintext highlighter-rouge">(nonce, response)</code> pairs for the car to repeat a nonce and then BOOM: they send back the recorded response and they can get the car to unlock.</p>

<p><img src="/assets/hhghp10/hhghp9b_02_replay_meme_300.jpg" width="400" style="display: block; margin: 0 auto;" /></p>

<p>Two examples of an attacker who could collect these <code class="language-plaintext highlighter-rouge">(nonce, response)</code> pairs are</p>

<ol>
  <li>a valet who is holding a person’s key fob while they’re eating dinner or</li>
  <li>an attacker who places a listening device outside a person’s house to capture unlocks every time that person gets into their car over the course of a year.</li>
</ol>

<p>In both cases, the attacker records enough valid unlock responses that when they return to the car and try to unlock it themselves, they have a good chance of knowing the correct MAC response without needing to know the unlock key.</p>

<blockquote>
  <p>“But c’mon! Nonces are currently 32 bits wide, giving over <em>4.3 billion</em> possible values. How likely is it, really, for an attacker to find a repeated nonce?”</p>
</blockquote>

<p>Let’s find out! First, let’s assume that an attacker can trigger an unlock and store the <code class="language-plaintext highlighter-rouge">(nonce, response)</code> pair every 3.4 ms<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>. This means that if an attacker had control of a fob for a mere 30 minutes (like a valet might) they could potentially trigger 530,973 unlocks. We want to find out how long it would take that attacker to later break into that car by requesting unlocks until the car challenged them with one of the 530,973 nonces they recorded earlier.</p>

<p>The general statement for this problem is “Given a set of T values from a distribution of size N, what’s the probability that in M random draws from that distribution at least one of them matches a value from T?” It’s known as the “birthday problem” since another example of this exact same question is “Assume you have a group of T people with different birthdays and you ask M random strangers for <em>their</em> birthdays. What are the chances that at least one of those M people shares a birthday with someone in the group of T people?”<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup> (in this case, “dates in a year” forms the pool of “N” values, N being 365).</p>

<p>The formula for this probability is \(P(M) = 1 - ( 1 - {T \over N})^M\)<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup>, which can be approximated as \(P(M) \approx 1 - e^{-TM/N}\). Weirdly, M doesn’t need to be that big to have a really good chance of finding a match! If you took a group of 20 people with different birthdays, it would only take about <em>12 random strangers</em> to have a 50/50 chance of finding someone who shared a birthday with a person from the original group!</p>

<p><img src="/assets/hhghp10/hhghp9b_03_birthday_bound_graph_300.png" alt="" />
<em>In a group of 20 people, it would only take about 12 random strangers to have a 50% chance of finding someone who shared a birthday with one of the people from the group. Image from https://picryl.com/media/crowd-human-silhouettes-6333fd.</em></p>

<blockquote class="aside">
  <p>❗ <strong>Test it out for yourself!</strong></p>

  <p>Generate a set of 20 random values from 1 to 365. How long does it <em>feel</em> like it will take to get a repeated value (regardless of what the math says)?</p>

  <p>Now generate random values until you get one that matches one of the original 20 values. How many random numbers did you need to generate to find that match? What was the probability of finding a match when you did?</p>
</blockquote>

<p>In the case of our key fob, \(T = 530,973\) and \(N = 2^{32} = 4,294,967,296\). Let’s say the attacker had another 30 minutes to try to get into the car using that table, during which time they could request another 942,408 unlocks (our value for M), since each failed unlock attempt would only take 1.91 ms<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup>.</p>

<blockquote class="aside">
  <p>❓ <strong>What’s the probability?</strong></p>

  <p>So, what’s the probability that our attacker finds a repeated nonce within that window? Calculate it before looking at my answer below!</p>

  <center>⮮ ANSWER BELOW ⮯</center>
</blockquote>

<p>Let’s see, \(P(M) = 1 - ( 1 - {T \over N})^M = 1 - (1 - {530973 \over 4294967296})^{942408} = 1\).</p>

<p>Woah, it’s basically guaranteed?! I’m afraid so. In fact, it gets even worse. We can rearrange the “birthday problem” formula to calculate approximately how many unlocks (M) we’d need to have a 50% chance of a repeated value, and that formula is \(M = {\log (1-P) \over \log (1-T/N)}\) . For our 4.3 billion nonces and 530,973 table size, an attacker would only need to trigger about <strong>5,606 unlocks</strong> before they had a 50% chance of seeing a repeated value, which they could do with our system in as little as <strong><em>11 seconds</em></strong>.</p>

<blockquote class="aside">
  <p>❓ <strong>What about the second scenario?</strong></p>

  <p>In the second scenario above, an attacker placed a listening device outside a person’s house to capture each of their unlocks over the course of a year.</p>

  <p>What’s a reasonable assumption for how many unlocks that attacker might capture in that year (i.e. the size of T)?</p>

  <p>How long would it take an attacker who had that table to unlock a car using this attack? (Answer<sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote" rel="footnote">5</a></sup>)</p>
</blockquote>

<h1 id="attack-8-the-birthday-problem-attack">Attack #8: The “Birthday Problem” attack</h1>

<p>This is exactly the threat model described by <strong>Car #2</strong> in the competition, in which an attacker (such as a valet or anyone who “borrows” your keys) only has temporary access to your fob.</p>

<p><img src="/assets/hhghp10/hhghp9b_04_car2_300.png" alt="" /></p>

<p>The security test I wrote performs exactly the attack just described, using <code class="language-plaintext highlighter-rouge">getBoardMsgLog</code> to simulate eavesdropping on each unlock transaction. It collects a set number of <code class="language-plaintext highlighter-rouge">(nonce, response)</code> pairs (configurable on the command-line with <code class="language-plaintext highlighter-rouge">--oracle-table-size</code>; default: 65535) then triggers unlocks until it finds a nonce that matches one from the table (or until <code class="language-plaintext highlighter-rouge">--oracle-max-iter</code> is reached; default: 0, meaning run indefinitely).</p>

<p>In simulation, this test only takes an astonishing <strong>40 seconds</strong> to fail.</p>

<p><img src="/assets/hhghp10/hhghp9b_05_birthday_bound_fail_300.png" alt="" /></p>

<h1 id="defense-8-widen-the-nonces">Defense #8: Widen the nonces</h1>

<p>So, <em>apparently</em> 4.3 billion possible nonce values isn’t enough. (Perhaps you were thinking exactly that way back in <a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-7-freshness-and-randomness">Part 7</a> when I made nonces only 32 bits long.) The easy fix is to make nonces longer, up to the 16 bytes that are generated by our PRNG each time we request a new random number.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// messages.h</span>
<span class="cp">#define NONCE_SIZE 16  // nonce is 16 bytes
</span></code></pre></div></div>

<p>If an attacker now attempts to unlock our car using their table of 530,973 stored (nonce, response) pairs, it will take them over <em>4.16 x 10<sup>20</sup></em> <em>centuries</em> (over <em>3 trillion times the age of our universe</em>) to even have a 50% chance of success! (Note that the time per attempt went up from 1.91 ms to 2.95 ms, since nonce messages are now 18 bytes instead of 6.)</p>

\[\text {Time} = \text {M attempts} \cdot 2.95 {ms \over attempts} = {\log (1-0.5) \over \log (1-530973/2^{128})} \cdot 2.95 = 4.44 \times 10^{32} \cdot 2.95 = 1.31 \times 10^{33} ms\]

<p>Our current test would try to corroborate this by running for at least 4.16 x 10<sup>17</sup> centuries before finding a nonce collision. This is, clearly, terribly unreasonable. Although we can, and should, let this test run for long periods of time to substantiate that the attack is infeasible (I’ve run the actual “birthday problem” attack for longer than 8 hours with no repeated nonce values), it would be nice if we had a faster test to still check that we’ve implemented a reasonable defense against the attack. To that end, I’ve added another security test that just checks the length of the nonce values and calculates the probability of the attack using the equations above. This does <em>not</em> simulate the “birthday problem” attack, but it’s better than nothing for a quick security test. This test passes.</p>

<p><img src="/assets/hhghp10/hhghp9b_06_length_check_success_300.png" alt="" /></p>

<p>Lowering the nonce length back to 4 bytes and re-running the test also confirms that our earlier design would have failed this test.</p>

<p><img src="/assets/hhghp10/hhghp9b_07_length_check_failure_300.png" alt="" /></p>

<h1 id="other-defenses">Other defenses</h1>

<p>Widening the nonces may have been the obvious solution, but there are others that we could consider. For instance, we could apply to this situation the same reasoning that was described in <a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-8-brute-force-and-timing-attacks">Part 8</a> about protecting the pairing pin from brute force attacks by:</p>

<ul>
  <li><strong>Making each attempt categorically more difficult</strong>: Requiring an attacker to provide a fingerprint scan alongside the unlock request or using a non-repeating PRNG algorithm and locking/erasing the device after 2<sup>32</sup> total unlock attempts over the lifetime of the car.</li>
  <li><strong>Making each individual attempt take longer</strong>: Enforcing a rate-limit of one unlock per second or having a 1 minute timeout after 3 unsuccessful unlock attempts.</li>
  <li><strong>Decreasing the odds of any single guess being successful</strong> by widening the nonce (which we did above)</li>
</ul>

<p>As with the pairing pin, though, the competition and its specific threat model eliminate many of these as viable options.</p>

<ul>
  <li>We can’t change the competition rules (or the hardware!) to require a fingerprint scan or other additional requirement to make unlocking more difficult.</li>
  <li>Attackers can still reflash the hardware at will, making it meaningless to save counters or timeout values or to even have any sort of saved system state whatsoever (attackers in the competition could simply reflash the target with fresh firmware whenever they wanted, resetting any variables that were intended to be stored between power cycles).</li>
</ul>

<p>Adding a roughly 1 second delay for each unlock request (the maximum time an unlock could take, per the competition rules) would definitely help, but it would be insufficient on its own to prevent the “birthday problem” attack.</p>

<blockquote class="aside">
  <p>❓ <strong>What about that “non-repeating PRNG algorithm” thing?</strong></p>

  <p>The problem with pseudo-random number generation is that <em>every</em> possible answer should have the same probability of showing up on any given step, giving rise to the “birthday problem.” An alternative to this form of random number generation might be to encrypt a monotonically increasing counter using something like AES, a technique that was brought up during our initial discussion about PRNGs in <a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-7-freshness-and-randomness">Part 7</a>:</p>

  <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">uint32_t</span> <span class="n">counter</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="nf">rand</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
 <span class="c1">// return upper 4 bytes of AES_CMAC(prng_key, counter++);</span>
<span class="p">}</span>
</code></pre></div>  </div>

  <p>The counter would increment on each unlock, ensuring that the output from AES (the random number) would be different on each unlock and, critically, <em>non-repeating</em><sup id="fnref:6" role="doc-noteref"><a href="#fn:6" class="footnote" rel="footnote">6</a></sup>. (At least until the counter rolled over from 2<sup>32</sup>-1 to 0, at which point the car could just permanently disable itself, preventing any further unlocks. This number could be made high enough to never be reached during normal operation.)</p>

  <p>This would seem to solve our “birthday problem” attack, since there’s no birthday problem to contend with! Unfortunately, it wouldn’t survive the attackers in this competition. Can you explain why? (Answer<sup id="fnref:7" role="doc-noteref"><a href="#fn:7" class="footnote" rel="footnote">7</a></sup>)</p>

</blockquote>

<h2 id="the-problem-with-oracles">The problem with “oracles”</h2>

<p>Part of the reason the “birthday problem” attack works at all is that both the fob and the car act as <strong>oracles</strong>: devices that an attacker can query at will to learn some information about the system.</p>

<ol>
  <li>Attackers can send unlock messages to a car whenever they want to see which nonce message is sent back.</li>
  <li>They can also trigger an unlock on the fob and then send the fob a made-up nonce message to see what response the fob sends back.</li>
</ol>

<p><img src="/assets/hhghp10/hhghp9b_08_oracles_300.png" alt="" /></p>

<p>Right now, the “birthday problem” attack uses primarily the second fact above to generate its table of <code class="language-plaintext highlighter-rouge">(nonce, response)</code> pairs (though an attacker could also get that information by just observing valid unlock transactions, as was depicted originally) and then the first fact above during the actual attack to start an unlock sequence without the fob being present.</p>

<p>Ideally, neither the fob nor the car ever talks to a device without authenticating it as a valid car or fob, though. If the car had a way to verify that an unlock request came from an authentic fob, then it would cease to be an oracle, eliminating situation #1. And if the fob had a way to verify that the nonce messages it sees after sending an unlock request came from an authentic car, then it would cease to be an oracle in the sense of situation #2. Eliminating both the fob and car as oracles would <em>categorically</em> eliminate the “birthday problem” attack, not simply make it less likely to succeed.</p>

<p>I spent some time trying to think of ways to do that, however, and couldn’t find a solution that still offered any meaningful improvements after accounting for the fact that attackers in the competition could reflash a car or fob whenever they wanted.</p>

<p>For example, say we try to eliminate the car as an oracle by expanding the unlock message (which is back to being <code class="language-plaintext highlighter-rouge">[ 0x56 | 0x6 | ‘unlock’ ]</code> right now) to include a rolling counter and MAC value.</p>

<pre><code class="language-ascii">        Plaintext input                    MAC
  /—————————————————————————————\   /————————————————\
[ 0x56 | 0xB | 0x0C | 0xED | 0x0C | 0xA15B32390B17FF4C ]
   ^      ^    \——/   \—————————/
   |   Length  Fob ID   Counter
 UNLOCK_MAGIC
</code></pre>

<p>The car would refuse to send a nonce for any unlock message that didn’t have the right counter and MAC values.</p>

<blockquote class="aside">
  <p>❓ <strong>Can you spot the problem?</strong></p>

  <p>There’s a problem with the above design! What is it? (Answer<sup id="fnref:8" role="doc-noteref"><a href="#fn:8" class="footnote" rel="footnote">8</a></sup>)</p>
</blockquote>

<p>Fundamentally, systems that try to authenticate devices with a single message (static passwords, rolling codes + MAC, etc) end up being vulnerable to attacks that simply reflash the device and reset whatever state was supposed to be saved between unlock attempts. The challenge-response we developed in Part 7 fixed this, but at the cost of the car acting like an oracle, for which there isn’t a workaround: the car can’t withhold a challenge before a device has authenticated itself if it’s using that challenge to authenticate the other device!</p>

<p>Or perhaps we try to eliminate the fob as an oracle by requiring that nonce messages also be accompanied by a MAC value (using a new, unique key, separate from the unlock key, of course!), verifying that they came from a real car.</p>

<p><img src="/assets/hhghp10/hhghp9b_09_nonce_mac_300.png" alt="" /></p>

<p>The fob would refuse to reply with its own MAC response (computed using the unlock key) unless it could validate the nonce MAC using its own “nonce key.”</p>

<p>This one is interesting, since it <em>does</em> prevent an attacker from querying a fob by itself to generate the table of <code class="language-plaintext highlighter-rouge">(nonce, response)</code> pairs. However, in both of the realistic attack scenarios that were mentioned above (a valet or an attacker who has installed a listening device outside a person’s home), this would have no effect on the attacker, since the fob is already interacting with an authentic car and the attacker is merely listening to that conversation.</p>

<p>All of which goes to show the importance of things like hardware monotonic counters (which <em>can’t</em> be reset to 0) and of designing your system so that an attacker can’t reset or reflash your device.</p>

<h1 id="updated-threat-model">Updated threat model</h1>

<p>Our updated threat model with this new attack (scoped just to show the relevant features) is below.</p>

<p><img src="/assets/hhghp10/hhghp9b_10_threat_model_scoped.png" alt="" /></p>

<p>The primary <strong>pitfall</strong> (a plausibly wrong solution) in trying to defend against the “birthday problem” attack is thinking we could add a rolling code+MAC to the unlock message to close off the car as an oracle. This doesn’t work because it’s vulnerable to exactly the attacks we developed in <a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-7-freshness-and-randomness">Part 7</a>: RollJam, Forced rollback, and Forced rollover.</p>

<p>Two additional pitfalls are the ideas that:</p>

<ul>
  <li>A valid PRNG algorithm like the one we’re using <em>won’t</em> repeat any values (it will; that’s kind of the whole point of this article) and</li>
  <li>That it’s possible to prevent a nonce from being repeated by simply tracking in software which ones have already been sent out. This pitfall is vulnerable to devices being reflashed and having that history reset.</li>
</ul>

<p>The only two <strong>alternatives</strong> to widening the nonce (defenses that would or could have worked as well as it) would have been to have:</p>

<ul>
  <li>Added something to the unlock process like a fingerprint scan or requiring the fob to be within a certain distance of the car (such as in a <strong>passive keyless entry</strong> system, which uses RF signals to detect if a fob is near the car) or</li>
  <li>Replaced our PRNG with a non-repeating algorithm whose counter was stored in an anti-rollback counter.</li>
</ul>

<p>Adding some sort of timeout after N unlock attempts within a certain small window might also have worked, had our threat model not included reflashing (i.e. had our design/the competition prevented that from happening).</p>

<p>In addition to widening the nonce, there are a few things that could be <strong>add-ons</strong> to the design for additional security or better features.</p>

<ul>
  <li>Adding a 0.75 or 1 sec delay to each unlock (the maximum length of time the rules say unlocking is allowed to take) would make the “birthday problem” attack take a few hundred times longer than it would otherwise.</li>
  <li>Adding a MAC to the nonce message would prevent an attacker from querying a fob directly for response values, forcing them to obtain that data via a live unlock session between a valid fob and car.</li>
  <li>Had the hardware/rules allowed it, logging, locking, or erasing the device upon a rapid sequence of unlock requests or failed attempts could have made this attack much harder or even categorically infeasible.</li>
</ul>

<h1 id="conclusion">Conclusion</h1>

<p>In this article we discovered that 4.3 billion isn’t actually that big of a number and there’s a real chance that a random number generated from that pool will repeat often enough to facilitate another replay attack! The simple solution was to generate nonces from a much larger pool (up to 2<sup>128</sup>, instead of 2<sup>32</sup>), making repeated values <em>much</em> less likely to occur. Along the way, we discussed the idea of an “oracle”, including what it is and why it can’t always be avoided.</p>

<p>If you’ve made it this far, thanks for reading and happy hacking!</p>

<hr />

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>A full unlock is an unlock message (3 bytes), followed by a nonce message (6 bytes), a response message (10 bytes), an ACK message (3 bytes), and a start message (17 bytes). At 115200 baud, and assuming no processing delays between messages, this entire series of messages only takes 3.39 ms. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>This is technically a form of the “birthday problem” called a “two-set cross-collision”, the two sets being T and M. A simpler formulation (the one you’re likely to find described if you search for “birthday problem”) is a “single-set cross-collision”, in which we ask “given a set of T values taken from a distribution of size N, what’s the probability that at least two of those T values are the same?” In birthday terms, this is like asking “Given a group of T people, what are the chances that two of them share a birthday?” <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>The probability works like this: pretend you have a box of 10 green balls (N) and you pull out 4 of them (T) and paint them orange. If you reach in to pull out a ball 3 times (M), what’s the probability of pulling out at least one orange ball? Well, there are only four possible outcomes in terms of the colors of those 3 balls: 3 orange, 2 orange/1 green, 1 orange/2 green, or 3 green. The probability of drawing at least one orange ball (the “birthday problem”) is the summed probability of the first three scenarios or, put another way, “1 – the probability of the last scenario”. The probability of the last scenario is the chance of drawing a green ball, \({N-T} \over N\) or simply \(1-{T \over N}\), M times in a row, which has a probability of \((1-T/N)^M\). In this case, that’s \(P(collision)=1-(1-4/10)^3=0.784\).<br /><img src="/assets/hhghp10/hhghp9b_11_birthday_probability_300.png" alt="" /> <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4" role="doc-endnote">
      <p>A failed unlock attempt includes everything from a normal unlock transaction minus a start message. See note 1 (above) for how that time could be calculated. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5" role="doc-endnote">
      <p>It seems reasonable to me to guess that a person might unlock their car 2-3 times a day, which we’ll approximate as 1000 total unlocks over the course of a year. Thus, an attacker would need \(M = {\log (1-P) \over \log (1-T/N)} = {\log (1-0.5) \over \log (1-1000/4.3billion)} = 2,977,044\) attempts using that table to have a 50% chance of unlocking the car, which they could accomplish in as little as 1.58 hours. <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6" role="doc-endnote">
      <p>This non-repeating quality is also what makes this a <strong>bad</strong> PRNG construction, actually, since as numbers are produced by the algorithm it gives the remaining numbers a <em>higher</em> probability of being the next one produced. This is why the CTR-DRBG construction recommended by NIST includes several more steps than merely “encrypting an increasing counter”. <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:7" role="doc-endnote">
      <p>The “non-repeating PRNG algorithm” wouldn’t survive the attackers in the competition since they can reflash the car at will, resetting the counter to 0. We defeated that system in <a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-7-freshness-and-randomness">Part 7</a> by simply recording valid <code class="language-plaintext highlighter-rouge">(nonce, response)</code> pairs and then resetting the car, making all those responses valid again since the car was about to reissue all of the same nonces! If this were a production device and we could add a hardware security module (HSM) with an anti-rollback counter on it, however, this could be a viable defense. <a href="#fnref:7" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8" role="doc-endnote">
      <p>The fundamental problem is that this is exactly the design we arrived at in <a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-6">Part 6</a> and subsequently broke in <a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-7-freshness-and-randomness">Part 7</a>! Thus, “authenticating the fob using a rolling code + MAC” is defeated by any of the attacks we looked at in Part 7 (RollJam, Forced Rollback, Forced Rollover) and, plus, it makes the subsequent challenge-response rather redundant. <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Nathan Jones</name><email>contact@lightturtles.com</email></author><summary type="html"><![CDATA[A 32-bit nonce gives over 4.3 billion possible values, which sounds like plenty, until the "birthday problem" shows that an attacker with a table of recorded (nonce, response) pairs can expect to see a repeat in seconds. In this article we break our challenge-response unlock with a birthday-problem replay attack, defend against it by widening the nonce to 128 bits, and look at why "oracles" (devices an attacker can query at will) can be so dangerous.]]></summary></entry><entry><title type="html">How Hardware Gets Hacked (Part 9): A Practical Security Exercise</title><link href="https://lightturtles.github.io/works/2026/10/12/how-hardware-gets-hacked-part-9-a-practical-security-exercise/" rel="alternate" type="text/html" title="How Hardware Gets Hacked (Part 9): A Practical Security Exercise" /><published>2026-10-12T00:00:00-05:00</published><updated>2026-10-12T00:00:00-05:00</updated><id>https://lightturtles.github.io/works/2026/10/12/how-hardware-gets-hacked-part-9-a-practical-security-exercise</id><content type="html" xml:base="https://lightturtles.github.io/works/2026/10/12/how-hardware-gets-hacked-part-9-a-practical-security-exercise/"><![CDATA[<h1 id="contents">Contents</h1>

<ul id="markdown-toc">
  <li><a href="#contents" id="markdown-toc-contents">Contents</a></li>
  <li><a href="#introduction" id="markdown-toc-introduction">Introduction</a></li>
  <li><a href="#enable-feature-a-practical-security-exercise" id="markdown-toc-enable-feature-a-practical-security-exercise">“Enable feature”: A practical security exercise</a>    <ul>
      <li><a href="#design-review-of-the-feature-feature" id="markdown-toc-design-review-of-the-feature-feature">Design review of the “feature” feature</a></li>
      <li><a href="#security-analysis-of-features" id="markdown-toc-security-analysis-of-features">Security analysis of “features”</a></li>
      <li><a href="#a-possible-attack-and-its-defense-attackdefense-6" id="markdown-toc-a-possible-attack-and-its-defense-attackdefense-6">A possible attack and its defense (Attack/Defense #6)</a></li>
    </ul>
  </li>
  <li><a href="#the-blast-radius-problem-again" id="markdown-toc-the-blast-radius-problem-again">The “blast radius” problem again</a></li>
  <li><a href="#in-the-drivers-seat" id="markdown-toc-in-the-drivers-seat">In the driver’s seat</a></li>
  <li><a href="#updated-threat-model" id="markdown-toc-updated-threat-model">Updated threat model</a></li>
  <li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
  <li><a href="#attacking-and-defending-start-messages" id="markdown-toc-attacking-and-defending-start-messages">Attacking and defending “start” messages</a></li>
</ul>

<h1 id="introduction">Introduction</h1>

<p>In the <a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-8-brute-force-and-timing-attacks">last article</a> we shifted our attention away from the unlocking process (which would seem to have required some rather sophisticated attacks after <a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-7-freshness-and-randomness">Part 7</a>) to the pairing process, which was vulnerable to some very simple brute force and timing attacks. The last thing left to secure for the competition is the process of <strong>packaging and enabling a feature</strong>.</p>

<p><img src="/assets/hhghp9/hhghp9a_00_enable_feature_rules_300.png" alt="" /></p>

<p>As with pairing, this code is woefully insecure at the moment. But the good news is that we already have all the skills we need to make it secure! In this article, I’ll ask you to apply what you’ve learned so far in this series to do exactly that, walking you through my own solution. Before we close, we’ll also get to talk about per-device keys and asymmetric cryptography.</p>

<h1 id="enable-feature-a-practical-security-exercise">“Enable feature”: A practical security exercise</h1>

<p>Let’s put those security skills you’ve been developing in this series to the test! Can you protect the “enable feature” aspect of our system from exploitation?</p>

<p>We’ll tackle this challenge in three steps:</p>

<ol>
  <li><strong>Reviewing</strong> how features are packaged, formatted, and enabled</li>
  <li>Identifying the <strong>security requirements</strong> our system needs</li>
  <li>Demonstrating a <strong>successful attack</strong> on the system (by exploiting a security requirement which is lacking enforcement) and then implementing a <strong>defense</strong> against that attack</li>
</ol>

<p>Each step is composed of several questions which I hope you take the time to answer (even if it’s just briefly and in your own head!) before I provide you with my answer.</p>

<h2 id="design-review-of-the-feature-feature">Design review of the “feature” feature</h2>

<blockquote class="aside">
  <p><strong>❓ How are features packaged?</strong></p>

  <p>Before a feature can be enabled, it must be “packaged” into a feature file. What is the shell command that packages a feature for our system? Who is responsible for executing that command? How were these features distributed to the teams in the competition?</p>

  <p><em>Hint:</em> You can find this information in either the <a href="https://github.com/nathancharlesjones/howHardwareGetsHacked">repo README</a>, <a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-2-on-boarding">HHGH (Part 2): On-boarding</a>, and/or the <a href="https://github.com/nathancharlesjones/howHardwareGetsHacked/blob/main/docs/2023%20eCTF%20Rules%20v1.1.pdf">competition rules</a>.</p>

  <center>⮮ ANSWER BELOW ⮯</center>
</blockquote>

<p>Features are packaged by executing the following command in the terminal.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Packages Feature 1 for Car 1234</span>
./tools/package.py <span class="nt">--id</span> 1234 <span class="nt">--num</span> 1
</code></pre></div></div>

<p>In the competition, the <code class="language-plaintext highlighter-rouge">package.py</code> tool also had read/write access to any “host secrets” (e.g. <code class="language-plaintext highlighter-rouge">secrets.json</code>).</p>

<p><img src="/assets/hhghp9/hhghp9a_01_rules_package_feature_300.png" alt="" /></p>

<p>Features were packaged at the “factory” (i.e. by the MITRE competition organizers) and were distributed to attacking teams alongside the encrypted firmware binaries for each of the car scenarios (except for Car #5; more on this in just a bit).</p>

<p><img src="/assets/hhghp9/hhghp9a_02_rules_cars_300.png" alt="" /></p>

<blockquote class="aside">
  <p><strong>❓ What is the format of a feature file?</strong></p>

  <p>What does a feature file actually look like? What is actually being saved to disk or sent over the UART port when a feature is being packaged or enabled?</p>

  <p><em>Hint</em>: You can find this information in either the <a href="https://github.com/nathancharlesjones/howHardwareGetsHacked/tree/main/application">repo README</a>, <a href="https://www.digikey.com/en/maker/blogs/2025/how-hardware-gets-hacked-part-1">HHGH (Part 1)</a>, or by reviewing the <a href="https://github.com/nathancharlesjones/howHardwareGetsHacked/blob/main/tools/package.py">code</a>.</p>

  <center>⮮ ANSWER BELOW ⮯</center>
</blockquote>

<p>Feature files are simply binary files composed of the car ID and a feature number.</p>

<pre><code class="language-ascii"> FEATURE PACKET:
   ┌──────────────────┬───────────────────┐
   │Car ID (11 bytes) │Feature # (1 byte) │
   └──────────────────┴───────────────────┘
</code></pre>

<blockquote class="aside">
  <p><strong>❓ How are features enabled on a fob?</strong></p>

  <p>What initiated the process of enabling a feature? What steps were taken by the fob before that feature was possibly enabled?</p>

  <p><em>Hint</em>: You can find this information in either the <a href="https://github.com/nathancharlesjones/howHardwareGetsHacked/tree/main/application">repo README</a> or <a href="https://www.digikey.com/en/maker/blogs/2025/how-hardware-gets-hacked-part-1">HHGH (Part 1)</a>.</p>

  <center>⮮ ANSWER BELOW ⮯</center>
</blockquote>

<p>Features were enabled on a paired fob by sending it the ASCII message <code class="language-plaintext highlighter-rouge">enable &lt;feature&gt;\n</code> to the HOST UART port, where <code class="language-plaintext highlighter-rouge">&lt;feature&gt;</code> is the Base16-encoded contents of the binary feature file. (Base16 encoding converts binary or hex values like <code class="language-plaintext highlighter-rouge">0b1001 1111</code> (<code class="language-plaintext highlighter-rouge">0x9F</code>) into ASCII <code class="language-plaintext highlighter-rouge">“9F”</code> (<code class="language-plaintext highlighter-rouge">0x39 0x46</code>), which makes it easier to read and debug later on when looking at a transcript of what was sent to a fob.)</p>

<div style="display:flex; gap:1em; justify-content:center;">
    <img src="/assets/hhghp9/hhghp9a_03_enabling_sequence_diagram_300.png" style="height:400px; width:auto;" />
    <img src="/assets/hhghp9/hhghp9a_04_enabling_flowchart_300.png" style="height:400px; width:auto;" />
  </div>

<p>An unpaired fob would reject the message straightaway. A paired fob would check that the feature file was valid (length was greater than or equal to the expected value, car ID in the feature file matched its own car ID, feature number within range) and that it could be enabled (feature list wasn’t full, feature wasn’t already enabled) before enabling it, sending back <code class="language-plaintext highlighter-rouge">OK</code> or <code class="language-plaintext highlighter-rouge">ERROR</code> (with an error message) as appropriate.</p>

<h2 id="security-analysis-of-features">Security analysis of “features”</h2>

<p>Now that we have a thorough understanding of the “enable feature” process, we can begin to analyze what security requirements must be present to meet the competition rules/our design goals.</p>

<blockquote class="aside">
  <p><strong>❓ What flags are at stake?</strong></p>

  <p>What flags could an attacking team capture by abusing the “enable feature” part of our system? What things should our system prevent a malicious person from doing?</p>

  <center>⮮ ANSWER BELOW ⮯</center>
</blockquote>

<p>In the MITRE eCTF competition, teams could capture a flag if they could cause Car #5 to emit its flag for “Feature 2” (which wasn’t given to the attacking teams; see the graphic above of the attack scenarios) and which was only <em>supposed</em> to be possible if the paired fob for Car #5 had Feature 2 legitimately enabled.</p>

<p>More broadly, features should only ever be made for owners who’ve paid for them, car owners should not be able to enable a feature without a valid feature file from the manufacturer, and they should also not be able to enable a feature on their car using a valid feature file that was created by the manufacturer for a different car (these are, essentially, security requirements #5 and #6 from the competition rules).</p>

<p>In a real production system, you may also want to ensure that feature files are unique to certain drivers or that feature files eventually “expire” or are “one-time use”.</p>

<blockquote class="aside">
  <p><strong>❓ What security requirements do we need?</strong></p>

  <p>In <a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-5">Part 5</a> we listed 5 important security principles: confidentiality, integrity, availability, authentication, and non-repudiation. Which, specifically, do we need and might possibly be missing from our current implementation? (Hint: there may be more than one!)</p>

  <center>⮮ ANSWER BELOW ⮯</center>
</blockquote>

<p>If a fob needs to enforce that a feature file which it has received has come from none other than the manufacturer themselves, then it needs to <strong>authenticate</strong> that feature file. Additionally, if it’s important that a valid feature file not be modified by an attacker to enable a feature on a different car or to enable a different feature on the same car, then the fob needs a way to verify the <strong>integrity</strong> of each feature file.</p>

<h2 id="a-possible-attack-and-its-defense-attackdefense-6">A possible attack and its defense (Attack/Defense #6)</h2>

<p>Knowing what security requirements we need, let’s discuss how an attacker can exploit the fact that we’re not doing anything right now to enforce those security requirements and then show how to enforce those requirements and close off the attacks.</p>

<blockquote class="aside">
  <p><strong>❓ What is an attack that could exploit our current system?</strong></p>

  <p>What is the easiest possible attack you can think of to capture the “Feature 2” flag from Car #5 by exploiting our current system?</p>

  <center>⮮ ANSWER BELOW ⮯</center>
</blockquote>

<p>The easiest attack that could be conducted against a feature file is <strong>forgery</strong>. Forging a feature file is quite simple, since the feature files are simply binary files composed of the car ID and a feature number.</p>

<pre><code class="language-ascii"> FEATURE PACKET:
   ┌──────────────────┬───────────────────┐
   │Car ID (11 bytes) │Feature # (1 byte) │
   └──────────────────┴───────────────────┘
</code></pre>

<p>We can easily modify either field to be whatever we want, allowing us to enable <em>any</em> feature on <em>any</em> car.</p>

<p>We’ll call this <strong>“Attack #6: Forging a feature file”</strong>.</p>

<blockquote class="aside">
  <p><strong>❓ What would a security test for this attack look like?</strong></p>

  <p>How would you write the test to go in <code class="language-plaintext highlighter-rouge">test_security.py</code> that implements this attack? Write it out in Python (or in pseudocode) and then run it to make sure that it fails, as expected!</p>

  <center>⮮ ANSWER BELOW ⮯</center>
</blockquote>

<p>The <a href="https://github.com/nathancharlesjones/howHardwareGetsHacked/blob/681d8aad130e557a97590c9332e5b270947dbe88/testing/test_security.py#L448">failing test that I wrote</a> forges a feature file by performing the following actions.</p>

<ol>
  <li>Pull the data off a fob (to ensure feature 2 isn’t enabled yet),</li>
  <li>Package feature 1,</li>
  <li>Create a forged feature 2 file by modifying the “feature number” field, and</li>
  <li>Attempt to enable the forged feature file.</li>
</ol>

<p>As expected, this test currently fails.</p>

<p><img src="/assets/hhghp9/hhghp9a_05_feature_mac_test_fail_300.png" alt="" /></p>

<blockquote class="aside">
  <p><strong>❓ How can we defend against that attack?</strong></p>

  <p>What’s a way we can prevent an attacker from enabling a forged feature file?</p>

  <p><em>Hint</em>: We’ve already talked about how a car can authenticate an unlock message from a fob (<a href="https://www.digikey.com/en/maker/search-results?t=Nathan%20Jones%20How%20Hardware%20Gets%20Hacked&amp;f=1981359301">Parts 5-7</a>); how could you adapt or modify that solution to the problem of authenticating a feature file?</p>

  <center>⮮ ANSWER BELOW ⮯</center>
</blockquote>

<p>The current problem is that an attacker is allowed to modify a feature file without a fob being able to detect it; they can easily forge any feature file they want. Any solution we come up with has to be able to detect this forgery and reject those forged messages.</p>

<p>Actually, we’ve already seen the best defense for this attack: <strong>computing a MAC value</strong> over the feature file and appending that to each feature packet.</p>

<p><img src="/assets/hhghp9/hhghp9a_06_feature_file_mac_300.png" alt="" /></p>

<p><em>The</em> most important property that a MAC algorithm actually has is “resistance to forgery”, which provides both authenticity and integrity: an attacker could neither create their own valid feature message nor could they modify a valid feature message and have it remain valid. In either case, they wouldn’t be able to compute the correct MAC value without knowing the secret key.</p>

<p>Previously we used MAC values to authenticate the unlock messages from a paired fob. In that case, the MAC value gave us integrity in addition to authenticity, though integrity wasn’t necessarily a requirement of the design. The only thing an attacker could do by modifying an unlock message was have it rejected by the car.</p>

<p>Our new feature file will have a MAC value appended to the end, computed over the car ID and feature number using a new “feature key” (which every fob will get at build-time). The new format looks like this:</p>

<pre><code class="language-ascii"> FEATURE PKT:
   ┌──────────────────┬───────────────────┬────────────────────┐
   │Car ID (11 bytes) │Feature # (1 byte) │MAC value (8 bytes) │
   └──────────────────┴───────────────────┴────────────────────┘
</code></pre>

<blockquote class="aside">
  <p><strong>❓ Why not use the unlock key?</strong></p>

  <p>You might be asking, at this point, “There’s already a secret key on the fob; why can’t we use that?”</p>

  <p>The short answer is that we <em>could</em>, but it’s considered bad practice. Much like toothbrushes, everybody should get their own; meaning, it’s best to only use each secret key for a single purpose. Although outside the scope of this series, it’s possible for an attacker to gain information about a key if it’s reused for multiple purposes. And if they were able to compute the key value, then they would have access to both forging a new feature file <em>and</em> impersonating a fob during the unlock process. If, instead, we use separate keys then the damage isn’t as bad if they happen to correctly compute one of them. This is known as <strong>key separation</strong>.</p>

  <p>An alternative to storing unique keys on the fob for each unique purpose is to store one key and <em>derive</em> any other keys from it (<strong>key derivation</strong>). For instance, a fob could get a single <code class="language-plaintext highlighter-rouge">fob_key</code> and then compute the unlock and feature keys on-the-fly using the HKDF algorithm. HKDF is composed of two operations: Extract and Expand, the first of which can be skipped if the seed material is uniform (as would be the case if it were the output from a CSPRNG).</p>

  <p><img src="/assets/hhghp9/hhghp9a_07_hkdf_300.png" alt="" />
<em>https://www.researchgate.net/figure/Extract-then-expand-model-for-KDFs_fig2_287478235</em></p>

  <p>If keys are being derived from a <code class="language-plaintext highlighter-rouge">fob_key</code>, then <code class="language-plaintext highlighter-rouge">fob_key</code> should <em>never</em> be used in any other context (to ensure proper key separation).</p>
</blockquote>

<p>The fob will test that this MAC value matches the one it computes over the first part of the message using its feature key before going on to check any of the other fields, failing early if it doesn’t match.</p>

<p><img src="/assets/hhghp9/hhghp9a_08_new_feature_flowchart_300.png" style="display: block; margin: 0 auto;" /></p>

<p>Since an attacker doesn’t have access to the feature key, they can’t recompute a new MAC for a modified message nor can they construct new messages. Even a one-bit change to either the car ID or the feature number causes the MACs to mismatch, which the fob will then reject.</p>

<p>We’ll call this <strong>“Defense #6: Authenticating feature files with MACs”</strong>.</p>

<p>After implementing this defense, our test now passes!</p>

<p><img src="/assets/hhghp9/hhghp9a_09_feature_mac_test_pass_300.png" alt="" /></p>

<p>There’s a subtlety to this defense, though; a vulnerability we’ve seen before and need to make sure we keep closed in our new defense.</p>

<blockquote class="aside">
  <p><strong>❓ What key property does the MAC comparison need to have?</strong></p>

  <p>In other words, what attack is left open by the naive code below?</p>

  <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">uint8_t</span> <span class="n">computed_mac</span><span class="p">[</span><span class="mi">16</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>
<span class="n">AES_CMAC_digest</span><span class="p">(</span><span class="o">&amp;</span><span class="n">feature_cmac_ctx</span><span class="p">,</span>
                <span class="p">(</span><span class="kt">uint8_t</span><span class="o">*</span><span class="p">)</span><span class="n">enable_message</span><span class="p">,</span>
                <span class="n">offsetof</span><span class="p">(</span><span class="n">ENABLE_PACKET</span><span class="p">,</span> <span class="n">mac</span><span class="p">),</span>
                <span class="n">computed_mac</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span> <span class="n">memcmp</span><span class="p">(</span><span class="o">&amp;</span><span class="n">computed_mac</span><span class="p">[</span><span class="mi">8</span><span class="p">],</span> <span class="n">enable_message</span><span class="o">-&gt;</span><span class="n">mac</span><span class="p">,</span> <span class="mi">8</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span> <span class="p">)</span>
<span class="p">{</span>
    <span class="n">sendError</span><span class="p">(</span><span class="s">"bad MAC"</span><span class="p">);</span>
    <span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>  </div>

  <p><em>Hint</em>: The pairing pin suffered this attack until we fixed it!</p>

  <center>⮮ ANSWER BELOW ⮯</center>
</blockquote>

<p>When comparing MACs, don’t forget to use a <strong>constant-time memcmp</strong> (i.e. <code class="language-plaintext highlighter-rouge">memcmp_ct</code>), like we did in <a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-8-brute-force-and-timing-attacks">Part 8</a> when comparing pairing pins. Otherwise you’ll leave yourself open to a timing attack!</p>

<blockquote class="aside">
  <p><strong>❗ Attack it!</strong></p>

  <p>Write a test to conduct a timing attack on the naive code above and run it to prove that it’s possible. Then switch out <code class="language-plaintext highlighter-rouge">memcmp</code> for <code class="language-plaintext highlighter-rouge">memcmp_ct</code> and prove that the code is no longer susceptible to a timing attack on the feature file’s MAC value. (<a href="https://github.com/nathancharlesjones/howHardwareGetsHacked/blob/681d8aad130e557a97590c9332e5b270947dbe88/testing/test_security.py#L481">One possible answer</a>)</p>
</blockquote>

<h1 id="the-blast-radius-problem-again">The “blast radius” problem again</h1>

<p>Although our code is secure at this point, there are two modifications we could make that would help limit the negative effects of an attacker learning the value of “feature key” in the future. This is the “blast radius” problem that was mentioned in <a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-8-brute-force-and-timing-attacks">Part 8</a>. If an attacker is able to extract the “feature key” that’s stored on <em>every fob</em> then they can have full control over the entire feature file verification system: they can forge whatever feature file they want for as long as that key remains in use. This would be a catastrophic failure of security, so instead we might use one of the techniques below (in addition to locking the debug port and doing our best to ensure that an attacker can’t actually extract that “feature key”).</p>

<p>First, we could use a <strong>per-device feature key</strong>, as opposed to a company-wide one like we’re using now. In this system, each paired fob would get a feature key that’s unique to a single car ID, similar to how each paired fob gets an unlock key that’s unique to each car ID. If an attacker ever extracts that feature key, the most damage they can do is forge feature files <em>for that specific car</em>, but not for every car we’ve ever produced.</p>

<p><img src="/assets/hhghp9/hhghp9a_10_per_device_feature_key_300.png" alt="" /></p>

<p>The downside is that our design gets a little more complex. Our company database (i.e. <code class="language-plaintext highlighter-rouge">secrets.json</code> in our codebase) would need to keep track of the feature key that’s been associated with each car ID. Additionally, this key would need to be transmitted to an unpaired fob that was being paired as part of the <code class="language-plaintext highlighter-rouge">PAIR_PACKET</code> it gets sent.</p>

<p>If we wanted to reduce the blast radius even further, we could replace our MAC algorithm with a <strong>digital signature</strong>, which uses asymmetric cryptography, a.k.a. public-key cryptography. Asymmetric cryptography uses two keys, a “public” and a “private” key, as opposed to symmetric cryptography, which uses just one key. (We’ve been using symmetric cryptographic algorithms this whole time, as evidenced by the fact that our unlock and feature keys needed to be shared by all devices computing the same MAC values: one key on all devices.)</p>

<p>In asymmetric cryptography the private key is kept secret from <em>everybody</em> except the person or device holding the key while the public key can be distributed to anyone. The weird and neat thing about asymmetric cryptography is that you can’t “double dip”: if the <strong>public</strong> key is used to encrypt a message then only the <strong>private</strong> key can be used to decrypt it; trying to decrypt it with the public key just yields garbledy-gook. Similarly, if a message is signed with a <strong>private</strong> key then only the matching <strong>public</strong> key can verify it.</p>

<p><img src="/assets/hhghp9/hhghp9a_11_digital_signature_300.png" alt="" /></p>

<p>That’s the essence of a digital signature: an entity signs a message with its private key, which anyone else can then verify using that same entity’s public key. If the verify operation returns true, then it <em>had</em> to have been that actual entity that did the signing in the first place (since no one else could produce a valid signature to pass the verify operation without knowing the value of the private key). What’s more, there’s no shared secret that could get leaked if an attacker extracts a device’s keys; the only key that’s actually on the device is already public knowledge.</p>

<p>Using this system, we would generate a public/private key pair during build time and put the public key inside every fob. The private key would be used to sign any feature files, which the fobs could then verify. This system has the smallest blast radius (it’s essentially none), since there’s no key on the fob that could be extracted to allow an attacker the ability to forge a feature file.</p>

<h1 id="in-the-drivers-seat">In the driver’s seat</h1>

<p>Now I’m going to step back even further, by letting you run through that same process to identify <em>where else</em> the feature data is vulnerable, to describe and mount the attack, and then to design and implement an appropriate defense.</p>

<p>To clarify, yes, there is one other place in the current code where an attacker could force a car to divulge its feature message despite not having that feature enabled on the fob. <strong>But where?</strong> (Take a second to consider your answer before moving on!)</p>

<center>⮮ ANSWER BELOW ⮯ </center>

<p>Sending a feature file to a fob is only the first of two places where the feature data can be attacked. The second is when it’s being transmitted as part of a start message after a car has been successfully unlocked!</p>

<p><img src="/assets/hhghp9/hhghp9a_12_vulnerable_start_message_300.png" style="height:600px; width:auto; display: block; margin: 0 auto;" /></p>

<p>Each start message contains the fob’s list of enabled features, and these are sent to the car in plaintext, with no additional security features whatsoever.</p>

<p>The process of attacking and securing this exchange is the same as what we’ve just walked through, even if the exact attack and defense steps are slightly different. I’ll leave it to you to complete that process for this part of the design, all the way from “What’s a start message and how is it used?” to “What’s the easiest way to attack this and force Car #5 to spit out the Feature #2 flag?” and “How to protect against that attack?” If you get stuck, you can follow along with my explanation at the <a href="#attacking-and-defending-start-messages">end of this article</a>. Good luck!</p>

<h1 id="updated-threat-model">Updated threat model</h1>

<p><strong>SPOILER ALERT</strong>: This section and the conclusion assume you’ve worked through the process of securing the start messages, the vulnerability identified in the last section.</p>

<p>The updated threat model (scoped just to show the latest attacks) is depicted below.</p>

<p><img src="/assets/hhghp9/hhghp9a_13_threat_model_scoped_300.png" alt="" /></p>

<p>A few things worth noting that weren’t previously discussed:</p>

<ul>
  <li>
    <p>One <strong>pitfall</strong> to watch out for is using an algorithm that provides integrity but not authentication. An example would be a hash, like SHA256 or MD5.
<img src="/assets/hhghp9/hhghp9a_14_hash_300.png" style="width:500px;" /></p>

    <p>These algorithms could help a fob or car detect <em>accidental</em> errors in a feature file or start message, but they wouldn’t stop an attacker from changing a value and then computing a new hash (these are public algorithms, after all!).</p>
  </li>
  <li>An alternative to adding a MAC value to each feature file or start message is to <strong>encrypt them</strong> using an authenticated encryption algorithm like AES-GCM. This would be only moderately more complicated than computing a MAC and would add “confidentiality” to our feature enabling process, but that’s not necessarily something we care about.</li>
  <li>The Python <strong><code class="language-plaintext highlighter-rouge">secrets</code></strong> <strong>module</strong> could be used to generate the keys instead of <code class="language-plaintext highlighter-rouge">randbytes</code> (previously mentioned when discussing how unlock keys were generated in <a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-6">Part 6</a>).</li>
  <li>We could <strong>remove the explicit chain of error messages</strong> in <code class="language-plaintext highlighter-rouge">enableFeature</code>. If an attacker is able to somehow generate a valid MAC, then the rest of this function is set up to inform them exactly which part of their feature file (if any) is invalid for this specific fob. This is another example of a “side-channel leakage” (introduced in <a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-8-brute-force-and-timing-attacks">Part 8</a>) and although it may not be exploitable today, it’s a thing to keep your eye on.</li>
  <li><strong>Compiler optimizations</strong> could still remove the “constant-time” feature of our constant-time <code class="language-plaintext highlighter-rouge">memcmp</code>, <code class="language-plaintext highlighter-rouge">memcmp_ct</code>.</li>
  <li>An <strong>added delay</strong> during each enabling attempt would further help to defend against timing attacks or any other attack that relied on repeatedly and quickly attempting to enable a new feature.</li>
</ul>

<h1 id="conclusion">Conclusion</h1>

<p>In this article, we applied the security principles we’ve been developing in this series to the process of enabling new features on a fob.</p>

<p>Upon analyzing the firmware, it became clear that attackers could easily forge their own feature files <em>and</em> start messages, representing a lack of integrity and authentication in our current system. As the primary feature of a MAC (message authentication code) is “resistance to forgery”, appending one both to each feature file and to each start message cleanly solved the problem of forged messages. But don’t forget to make sure that you compare the MAC values using a constant-time memcmp! Otherwise you’ll leave yourself open to a timing attack. At this point, an attacker must find a way to defeat our MAC algorithm (an exceedingly hard thing to do) to be able to forge a feature file or a start message.</p>

<p>If you’ve made it this far, thanks for reading and happy hacking!</p>

<h1 id="attacking-and-defending-start-messages">Attacking and defending “start” messages</h1>

<p>We’re going to start this process of security analysis over again, walking gradually toward yet another important defense. As before, I want to emphasize that we’ve already discussed all of the tools you’d need to do this on your own and I want to encourage you to answer each question to yourself (even just in your head) before reading my answers.</p>

<p>Let’s start at the top.</p>

<blockquote class="aside">
  <p><strong>❓ What is the format of a start message?</strong></p>

  <p>What does a start message actually look like? What is actually being sent over the UART port when a fob is telling a car which features have been enabled?</p>

  <p><em>Hint</em>: You can find this information in either the <a href="https://github.com/nathancharlesjones/howHardwareGetsHacked/tree/main/application">repo README</a>, <a href="https://www.digikey.com/en/maker/blogs/2025/how-hardware-gets-hacked-part-1">HHGH (Part 1)</a>, or by reviewing <a href="https://github.com/nathancharlesjones/howHardwareGetsHacked/blob/681d8aad130e557a97590c9332e5b270947dbe88/application/include/dataFormats.h#L20">the code</a>.</p>
</blockquote>

<p>A start message consists of 17 bytes in TLV (tag-length-value) format:</p>

<pre><code class="language-ascii">                 Tag Len
       (START_MAGIC)  │
                  │   │
                  ▼   ▼
               ┌────┬────┬────────────────────────────┐
START MSG:     │0x57│0x0F│  Feature info (15 bytes)   │
               └────┴────┴─────────────┬──────────────┘
                                       │
                                       ▼
┌───────────────────┬───────────────────────────┬───────────────────────────┐
│ Car ID (11 bytes) │# active features (1 byte) │List of features (3 bytes) │
└───────────────────┴───────────────────────────┴───────────────────────────┘
</code></pre>

<blockquote class="aside">
  <p><strong>❓ How are active features sent from a fob to a car?</strong></p>

  <p>When are features sent to the car? What steps are taken by the car before it prints out the associated feature flags?</p>

  <p><em>Hint</em>: You can find this information in either the <a href="https://github.com/nathancharlesjones/howHardwareGetsHacked/tree/main/application">repo README</a> or <a href="https://www.digikey.com/en/maker/blogs/2025/how-hardware-gets-hacked-part-1">HHGH (Part 1)</a>.</p>
</blockquote>

<p>The fob sends active features to the car after it receives the ACK SUCCESS message from the car during the unlocking process.</p>

<div style="display:flex; gap:1em; justify-content:center;">
    <img src="/assets/hhghp9/hhghp9a_15_unlock_sequence_diagram_300.png" style="height:500px; width:auto;" />
    <img src="/assets/hhghp9/hhghp9a_16_unlock_flowchart_300.png" style="height:500px; width:auto;" />
  </div>

<p>The car checks only that the ID in the start message matches its own and that each active feature number is within a valid range (1-3 for us) before sending out the associated flag over its HOST UART port.</p>

<p>The same flag is at stake as before: the Feature 2 flag from Car #5 (which, again, doesn’t have a valid Feature 2 file for it to be enabled legitimately).</p>

<blockquote class="aside">
  <p><strong>❓ What security requirements do we need?</strong></p>

  <p>Which of the five (confidentiality, integrity, availability, authentication, and non-repudiation) do we need and might possibly be missing from our current implementation? (Hint: there may be more than one!)</p>
</blockquote>

<p>A car needs to know that the feature info it’s getting from a fob has come from a valid paired fob and was untampered-with before it got to the car. This means we need authentication and integrity.</p>

<blockquote class="aside">
  <p><strong>❓ What is an attack that could exploit our current system?</strong></p>

  <p>What, now, is the easiest possible attack you can think of to capture the “Feature 2” flag from Car #5 by exploiting our current system?</p>

  <p><em>Hint</em>: It specifically targets the start message.</p>
</blockquote>

<p>The easiest attack that could be conducted against a start message is still <strong>forgery</strong>, though forging a start message is a little more complicated than forging a feature file. The challenge is in the timing: we need Car #5’s paired fob to begin the unlock process, getting as far as issuing a correct response to the car’s nonce challenge, and then <em>we</em> need to send our own start message, instead of the one the fob wants to send.</p>

<p><img src="/assets/hhghp9/hhghp9a_17_mitm_start_message_300.png" alt="" /></p>

<p>This is a type of attack called <strong>“man-in-the-middle”</strong> (MITM), since the attacker literally sits in between the two devices trying to communicate, with the ability to modify the messages that are being passed back and forth.</p>

<p>Modifying (i.e. forging) a start message in this scenario is as easy as it was to forge a feature file (before we added the MAC, that is): all we have to do is change two binary values in the start message (the number of active features and the feature or features which is/are active). This means that this attack would allow us to, once again, enable <em>any</em> feature on <em>any</em> car.</p>

<p>We’ll call this <strong>“Attack #7: Forging a start message”</strong>.</p>

<blockquote class="aside">
  <p><strong>❓ What would a security test for this attack look like?</strong></p>

  <p>How would you write the test to go in <code class="language-plaintext highlighter-rouge">test_security.py</code> that implements this attack? Write it out in Python (or in pseudocode) and then run it to make sure that it fails, as expected!</p>
</blockquote>

<p>The biggest challenge facing us in implementing this attack is that our current hardware setup has no way to “insert” an attacker in between a fob and a car to modify any start messages; they’re directly connected to each other over UART. We don’t have a way to conduct a real MITM-type attack.</p>

<p><img src="/assets/hhghp9/hhghp9a_18_mitm_300.png" alt="" /></p>

<p>So it’s simulation to the rescue! To avoid needing to include a real “MITM” in our hardware setup, I’ve opted to create a new test command called <a href="https://github.com/nathancharlesjones/howHardwareGetsHacked/blob/7df95846a9ac86d8cced77fbf6b43c3018342bd2/application/source/fob.c#L301"><code class="language-plaintext highlighter-rouge">setStartMsg</code></a>, which will store a binary start message in the fob to be sent out <em>in place of</em> its normal start message the next time the fob tries to unlock a car.</p>

<p><img src="/assets/hhghp9/hhghp9a_19_mitm_security_test_300.png" alt="" /></p>

<ol>
  <li>Test triggers unlock and reads car message log to get the start message that was sent by the fob.</li>
  <li>Test modifies the start message to enable feature #2 and sets this as the stored start message on the fob.</li>
  <li>Test triggers unlock.</li>
</ol>

<p>The <a href="https://github.com/nathancharlesjones/howHardwareGetsHacked/blob/7df95846a9ac86d8cced77fbf6b43c3018342bd2/testing/test_security.py#L520">failing test that I wrote</a> first triggers an unlock and then extracts the last start message from the board message log (simulating a MITM attacker who can see a valid start message being transmitted by a fob). It then modifies the start message so that feature #2 is enabled and uses <code class="language-plaintext highlighter-rouge">setStartMsg</code> to save that modified message to the fob. Then it triggers an unlock, during which the fob uses the modified message.</p>

<p>To verify that the test currently fails, I’ve also added a car test command called <a href="https://github.com/nathancharlesjones/howHardwareGetsHacked/blob/7df95846a9ac86d8cced77fbf6b43c3018342bd2/application/source/car.c#L240"><code class="language-plaintext highlighter-rouge">getFeatures</code></a> which lists the features that were sent over by a paired fob during the last valid unlock attempt.</p>

<blockquote class="aside">
  <p><strong>❓ How can we defend against that attack?</strong></p>

  <p>What’s a way we can prevent an attacker from enabling a forged start message?</p>

  <p><em>Hint</em>: We’ve already talked about how a car can authenticate an unlock message from a fob (<a href="https://www.digikey.com/en/maker/search-results?t=Nathan%20Jones%20How%20Hardware%20Gets%20Hacked&amp;f=1981359301">Parts 5-7</a>); how could you adapt or modify that solution to the problem of authenticating a start message?</p>
</blockquote>

<p>Given the similarity of each of these attacks on the feature data, it will hopefully come as no surprise to learn that the easiest defense against this attack is to add a MAC to the end of each start message, which the car will also verify before it sends out any flags. If an attacker modifies even a single bit of the start message sent by the paired fob (or tries to forge their own), they won’t be able to correctly compute the MAC value without the “start message” key.</p>

<p><img src="/assets/hhghp9/hhghp9a_20_start_msg_mac_check_300.png" style="display: block; margin: 0 auto;" /></p>

<p>We’ll call this <strong>“Defense #7: Authenticating start messages with MACs”</strong>.</p>

<p>Implementing this requires <a href="https://github.com/nathancharlesjones/howHardwareGetsHacked/blob/7df95846a9ac86d8cced77fbf6b43c3018342bd2/tools/car_gen_secret.py#L56">creating a new “start message” key</a> to go into each fob/car (which will also need to be added to the pair packet when pairing a new fob) and adding <a href="https://github.com/nathancharlesjones/howHardwareGetsHacked/blob/7df95846a9ac86d8cced77fbf6b43c3018342bd2/application/source/car.c#L354">one more conditional check</a> during the car’s unlock process. Don’t forget to use <code class="language-plaintext highlighter-rouge">memcmp_ct</code>!</p>]]></content><author><name>Nathan Jones</name><email>contact@lightturtles.com</email></author><summary type="html"><![CDATA[A hands-on exercise in securing the eCTF key fob's feature-enabling process: review the design, identify the missing security requirements, forge a feature file, and defend against it with a MAC, before looking at per-device keys and digital signatures to shrink the blast radius.]]></summary></entry><entry><title type="html">How Hardware Gets Hacked (Part 8): Brute Force and Timing Attacks</title><link href="https://lightturtles.github.io/works/2026/09/23/how-hardware-gets-hacked-part-8-brute-force-and-timing-attacks/" rel="alternate" type="text/html" title="How Hardware Gets Hacked (Part 8): Brute Force and Timing Attacks" /><published>2026-09-23T00:00:00-05:00</published><updated>2026-09-23T00:00:00-05:00</updated><id>https://lightturtles.github.io/works/2026/09/23/how-hardware-gets-hacked-part-8-brute-force-and-timing-attacks</id><content type="html" xml:base="https://lightturtles.github.io/works/2026/09/23/how-hardware-gets-hacked-part-8-brute-force-and-timing-attacks/"><![CDATA[<p><em>Originally published on <a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-8-brute-force-and-timing-attacks">Maker.io (DigiKey)</a>.</em></p>

<p><img src="/assets/hhghp8/hhghp8_15_mac_pairing_pin_300.png" alt="An attacker sends a pairing pin guess to a fob, which compares a MAC of the guess against a MAC of the real pin; the error comes back after 34 ns, and the attacker wonders what the timing says about their next guess" /></p>

<p>With challenge-response and a properly seeded PRNG in place, the unlock process from Part 7 would take something like breaking AES-CMAC to defeat. So Part 8 does what a real attacker would do: it goes looking for the weakest part of the system that gives the same payoff. That’s the pairing process. A paired fob hands over the car ID, the unlock key, and the pairing pin to anyone who sends it <code class="language-plaintext highlighter-rouge">pair &lt;pin&gt;</code> with the right 6-hex-digit pin. There are only about 16.7 million possible pins, and at roughly 2.5 ms per guess a script can <strong>brute force</strong> its way through all of them in under 12 hours (about 6 on average). The payoff is the unlock key itself.</p>

<p>Brute force defenses come in three flavors: make each attempt harder (a fingerprint scan, or erasing the device after too many failures), make each attempt slower, or make any single guess less likely to succeed (a longer pin). The competition rules fix the pin format, and an attacker who can reset or re-flash the fob can wipe out any counter of failed attempts, so the defense here is a fixed delay added to every pairing attempt, no counter needed. A user never notices it, but it pushes the worst-case brute force time from 11.7 hours to 194 days.</p>

<p>That defense assumes a wrong guess tells the attacker nothing except that it’s wrong. It doesn’t. <code class="language-plaintext highlighter-rouge">memcmp</code> bails out at the first mismatched byte, so the error message comes back a few nanoseconds later for each leading digit the guess got right. Consistent differences that small are still measurable, and they turn the problem into a game of Wordle: guess all 16 values of the first digit, keep the slow one, repeat for the next digit. That’s at most <strong>96 guesses</strong> instead of 16.7 million, or an hour and a half even at a full minute per guess. This is a <strong>timing attack</strong>, one kind of <strong>side-channel attack</strong>, in which a device leaks information through how long it takes, how much power it draws, or even what its error messages say.</p>

<p>The fix is a constant-time comparison that XORs every byte pair together and checks the accumulated result only at the end, so the run time no longer depends on where the pins differ. Because C has no way to express “constant-time,” you have to check the generated assembly to make sure the compiler kept it that way. Comparing MACs of the pins would also work, since the avalanche effect makes a byte-by-byte timing leak useless to an attacker. The article also covers two tempting fixes that fall short. A random delay only adds noise, which averaging over many guesses (or watching the power trace) removes. Encrypting the pairing packet requires a key shared by every fob, and in this competition that key sits in a binary every attacker already has.</p>

<p><a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-8-brute-force-and-timing-attacks">Read the full article →</a></p>]]></content><author><name>Nathan Jones</name><email>contact@lightturtles.com</email></author><summary type="html"><![CDATA[Turning from the now-hardened unlock process to the eCTF key fob's pairing pin, which falls to a brute force attack in hours and to a timing attack in under a hundred guesses, then defending it with a per-attempt delay and a constant-time comparison.]]></summary></entry><entry><title type="html">Breadboarding an STM32 (Another Teaching Moment)</title><link href="https://lightturtles.github.io/works/2026/09/08/breadboarding-an-stm32-another-teaching-moment/" rel="alternate" type="text/html" title="Breadboarding an STM32 (Another Teaching Moment)" /><published>2026-09-08T00:00:00-05:00</published><updated>2026-09-08T00:00:00-05:00</updated><id>https://lightturtles.github.io/works/2026/09/08/breadboarding-an-stm32-another-teaching-moment</id><content type="html" xml:base="https://lightturtles.github.io/works/2026/09/08/breadboarding-an-stm32-another-teaching-moment/"><![CDATA[<div style="position:relative;width:100%;padding-bottom:56.25%;height:0;overflow:hidden;margin-bottom:1.5rem;">
  <iframe style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;" src="https://www.youtube.com/embed/M9FHGF2F6tE" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="">
  </iframe>
</div>

<p>This one covers the same territory as my <a href="/works/2023/06/25/teardown23-make-your-own-mcu-boards/">Make Your Own MCU Boards</a> talk, but live and on camera: how little you actually need to bring up a microcontroller. The chip in question is an STM32C071KBT3 — a 32-pin QFP part — paired with a breakout board from Adafruit. Solder the chip down, add header pins, and you’re most of the way to a working circuit.</p>

<p>What’s left is the same list of five things every MCU needs, no matter the vendor:</p>

<ol>
  <li><strong>Power</strong> — digital supply pins tied to the rails, with decoupling capacitors as a nice-to-have rather than a requirement. Separate analog power pins can share the digital rail too, at the cost of a noisier ADC.</li>
  <li><strong>Reset</strong> — the STM32C0 has an internal pull-up on NRST, so the pin needs nothing extra. A two-pin breadboard-mount button gives you a manual reset if you want one.</li>
  <li><strong>Clocking</strong> — the internal RC oscillator is enough for most projects. An external clock only earns its keep when you need precision timing, like USB or a real-time clock, and a packaged oscillator (rather than a hand-tuned crystal circuit) is the more reliable way to add one.</li>
  <li><strong>Programming</strong> — an ST-Link V2 debug adapter supplies SWDIO, SWCLK, and reset directly; no extra circuitry needed.</li>
  <li><strong>Specialty pins</strong> — BOOT0 controls where the STM32C0 starts executing after reset, and it’s disabled by default, so it’s another pin you can leave alone.</li>
</ol>

<p>Add power, wire up SWD, and drop in a quick CubeMX Blinky project, and that’s a working STM32 for only a few dollars in new parts — cheaper if you use a less expensive STM32C0 variant and make your own breakout boards.</p>

<p><strong>Related</strong></p>

<ul>
  <li><a href="https://www.st.com/resource/en/application_note/an5673-getting-started-with-stm32c0-mcu-hardware-development-stmicroelectronics.pdf">Getting started with STM32C0 MCU hardware development (AN5673)</a> — ST application note describing the minimal circuit for an STM32C0 in great detail.</li>
  <li><a href="/works/2023/06/25/teardown23-make-your-own-mcu-boards/">Make Your Own MCU Boards (Teardown 2023)</a> — the talk this video’s premise is drawn from, with more on sourcing, layout, and assembly</li>
  <li><a href="https://github.com/nathancharlesjones/STM32F103C8T6-breakout-board">STM32F103C8T6 breakout board (GitHub)</a> — my own minimal MCU board design, as a reference</li>
</ul>]]></content><author><name>Nathan Jones</name><email>contact@lightturtles.com</email></author><summary type="html"><![CDATA[How to build the minimal breadboard circuit for an STM32C0 microcontroller for only a few dollars in parts. Discusses the five key design considerations for any microcontroller circuit (power, reset, clocking, programming, and specialty pins) and how to account for them in your design.]]></summary></entry><entry><title type="html">Nothing Is Safe: An Introduction to Hardware (In)Security (Teardown 2026)</title><link href="https://lightturtles.github.io/works/2026/07/25/teardown26-nothing-is-safe/" rel="alternate" type="text/html" title="Nothing Is Safe: An Introduction to Hardware (In)Security (Teardown 2026)" /><published>2026-07-25T00:00:00-05:00</published><updated>2026-07-25T00:00:00-05:00</updated><id>https://lightturtles.github.io/works/2026/07/25/teardown26-nothing-is-safe</id><content type="html" xml:base="https://lightturtles.github.io/works/2026/07/25/teardown26-nothing-is-safe/"><![CDATA[<p><em>Talk given at <a href="https://www.crowdsupply.com/teardown/portland-2026/long-talk/nothing-is-safe-an-introduction-to-hardware-in-security">Teardown Portland 2026</a>, July 2026.</em></p>

<p><img src="/assets/teardown26_nothing_is_safe/summary_graphic.png" alt="Hand-drawn notes on fault injection techniques (clock glitching, voltage glitching, EMFI, laser fault injection) alongside a diagram of the cryptographic model extended with side-channel leakage paths" /></p>

<p>Every device that someone can physically get their hands on is a hardware security problem, whether or not it has a radio. The question worth asking isn’t “if” someone wants to get their hands on your device for malicious purposes — it’s “who are they, and what could they do once they had it?” The first question is specific to your product; the second is answerable once you know how attackers actually approach hardware, which is what this talk works through.</p>

<p><strong>Six bins, roughly by cost and invasiveness</strong></p>

<ol>
  <li><strong>Research</strong> — datasheets, third-party teardowns, extracted firmware, even social engineering against support staff. The goal isn’t just “how does this work” but “where did the designers make an assumption they never actually enforced” — every attack downstream starts by invalidating one of those.</li>
  <li><strong>Passive observation and logical attacks</strong> — probing exposed ports and internal signals, checking whether the debug port is locked, brute-forcing a short password, replaying a captured transaction, or just fuzzing the UART with garbage. The 2010 PlayStation 3 hack started here: watching signals to the console’s memory revealed a <code class="language-plaintext highlighter-rouge">free()</code> operation that could be glitched, resulting in a full compromise.</li>
  <li><strong>Physical modification</strong> — cutting a trace, swapping a strap resistor to change what hardware revision the firmware thinks it’s running on, injecting a false signal onto an analog sensor line, or pushing the device outside its rated environmental range to see what breaks.</li>
  <li><strong>Side-channel attacks</strong> — extracting secrets from what a device leaks just by operating, without any modification at all. Timing is the easiest to grasp: a naive <code class="language-plaintext highlighter-rouge">strcmp</code>-style password check returns faster on the first mismatched byte, which turns a billion-guess brute force into 36 guesses per digit (a real, shipped “secure” external hard drive was broken exactly this way through its error LED). Power analysis goes further — simple power analysis can read key bits directly off a single trace for algorithms like textbook RSA, and differential power analysis uses difference-of-means statistics across thousands of traces to recover an AES key one byte at a time, without ever attacking AES itself.</li>
  <li><strong>Fault injection</strong> — deliberately pushing a device outside its operating envelope to induce a skipped instruction, a corrupted fetch, or a flipped memory value: a crowbar glitch on the supply rail, clock double-pulsing, EM pulses, or a laser on a decapped die. This is exactly how the Xbox 360 was broken in 2011 — the second-stage bootloader’s kernel hash check was a single conditional, and glitching past that one branch let attackers load an unsigned kernel.</li>
  <li><strong>IC decapping and FIB editing</strong> — for a sufficiently determined, resourced attacker, exposing the silicon opens up optical side-channels, laser fault injection, and literal circuit edits with a focused ion beam. Most devices don’t need to defend against this tier — and part of good security engineering is deciding, deliberately, that they don’t.</li>
</ol>

<p><strong>The takeaway</strong></p>

<p>None of these attacks require a state-sponsored budget, and none of them are hypothetical — every technique in this talk has a real production device it’s been used against. The point isn’t to induce despair; it’s to work backward from “what am I actually protecting, from whom, and via which of these six avenues” and spend defensive effort accordingly. Sometimes that’s as simple as locking a debug port. Sometimes it’s a lot more.</p>

<p><strong>Additional Resources</strong></p>

<ul>
  <li><a href="/works/2025/10/10/jawncon26-nothing-is-safe/">Nothing is Safe (JawnCon 0x2)</a> — the same attack spectrum talk, with a full recording and a live DPA/fault injection demo on a ChipWhisperer Nano</li>
  <li><a href="/works/2025/12/01/all-your-bytes-are-belong-to-us/">All Your Bytes are Belong to Us</a> — the written companion on DigiKey/Maker.io, covering the same six levels in more depth</li>
  <li><a href="https://www.digikey.com/en/maker/search-results?t=Nathan%20Jones%20How%20Hardware%20Gets%20Hacked&amp;f=1981359301">How Hardware Gets Hacked →</a> — a multi-part series walking through a complete attack and defense, from start to finish</li>
</ul>

<p><a href="https://www.crowdsupply.com/teardown/portland-2026/long-talk/nothing-is-safe-an-introduction-to-hardware-in-security">Session page →</a></p>]]></content><author><name>Nathan Jones</name><email>contact@lightturtles.com</email></author><summary type="html"><![CDATA[Slides from Teardown Portland 2026: a tour of an attacker's menu of options against physical hardware, from a free datasheet read to focused ion beam edits on bare silicon.]]></summary></entry><entry><title type="html">How Hardware Gets Hacked: Authenticated Exchanges (Teardown 2026)</title><link href="https://lightturtles.github.io/works/2026/07/24/teardown26-how-hardware-gets-hacked-authenticated-exchanges/" rel="alternate" type="text/html" title="How Hardware Gets Hacked: Authenticated Exchanges (Teardown 2026)" /><published>2026-07-24T00:00:00-05:00</published><updated>2026-07-24T00:00:00-05:00</updated><id>https://lightturtles.github.io/works/2026/07/24/teardown26-how-hardware-gets-hacked-authenticated-exchanges</id><content type="html" xml:base="https://lightturtles.github.io/works/2026/07/24/teardown26-how-hardware-gets-hacked-authenticated-exchanges/"><![CDATA[<p><em>Workshop given at <a href="https://www.crowdsupply.com/teardown/portland-2026/workshop/how-hardware-gets-hacked-authenticated-exchanges">Teardown Portland 2026</a>, July 2026.</em></p>

<div style="position:relative;width:100%;padding-bottom:56.25%;height:0;overflow:hidden;margin-bottom:1.5rem;">
  <iframe style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;" src="https://www.youtube.com/embed/videoseries?list=PLIBdtC7CKWEM" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="">
  </iframe>
</div>

<p>Code and exercises for this workshop are on GitHub: <a href="https://github.com/nathancharlesjones/authenticated_exchanges">nathancharlesjones/authenticated_exchanges</a>.</p>

<p>Authentication is one of five foundational security principles — alongside confidentiality, integrity, availability, and non-repudiation — and it’s the one this workshop dug into: how do you prove a message or piece of data actually came from who it claims to have come from? The running example is a physical access control system: badges and a reader attached to a door. Each exercise breaks a specific assumption in the previous defense, and the fix that follows repairs it.</p>

<p><strong>Five defenses, each closing off the last attack</strong></p>

<ul>
  <li><strong>Baseline: the UID allowlist</strong> — A badge presents its ID number; the reader checks it against an allowlist and opens the door if it matches. Fine when an attacker can’t observe traffic or guess a UID — broken the moment one leaks from a log, a firmware image, or a public repo.</li>
  <li><strong>Challenge-response</strong> — The reader sends a nonce and the badge computes a <strong>message authentication code (MAC)</strong> over it with a shared key, proving knowledge of a secret without ever transmitting it. This raises new questions of its own, like where a good nonce comes from (PRNG vs. TRNG).</li>
  <li><strong>Per-device keys</strong> — A single MAC key shared fleet-wide means one stolen badge compromises everyone. Deriving a unique device key per badge — <code class="language-plaintext highlighter-rouge">HMAC(fleet_key, UID)</code> — caps the blast radius to one badge and makes key management (generation, storage, revocation) a first-class concern.</li>
  <li><strong>Mutual authentication</strong> — Nothing so far stops a rogue reader from “authenticating” a badge and reading its private data. The reader now has to prove itself too, using asymmetric cryptography and digital signatures so no shared secret sits on the reader side waiting to be extracted.</li>
  <li><strong>Session keys</strong> — Even after mutual authentication, an eavesdropper can still read the traffic that follows. A session key, derived the same way as the device key but tagged with an info string, encrypts post-handshake traffic without touching the long-lived device key or forward secrecy.</li>
</ul>

<p><strong>What’s still open</strong></p>

<p>The workshop closes on the questions it deliberately left out: authorization (what a badge is allowed to do once authenticated), what happens when a reader’s public key isn’t known in advance, resource constraints on real hardware, and how to handle failures — logging, lockouts, and lengthening timeouts — without those becoming a denial-of-service vector of their own. Every defense here closes off one avenue while relying on a fresh assumption of its own; the interesting part of security engineering is knowing what those assumptions are and deciding, deliberately, which ones you’re willing to make.</p>

<p><strong>Additional Resources</strong></p>

<ul>
  <li><a href="/works/2026/07/06/how-hardware-gets-hacked-part-6-authenticated-exchanges/">How Hardware Gets Hacked (Part 6): Authenticated Exchanges</a> — the eCTF key fob replaces its broken PRNG-based unlock with a rolling counter and AES-CMAC, the same MAC-based authentication idea applied to a real embedded target</li>
  <li><a href="https://github.com/nathancharlesjones/authenticated_exchanges">GitHub repo →</a> — all five exercises, tests, and the worksheet</li>
</ul>

<p><a href="https://www.crowdsupply.com/teardown/portland-2026/workshop/how-hardware-gets-hacked-authenticated-exchanges">Session page →</a></p>]]></content><author><name>Nathan Jones</name><email>contact@lightturtles.com</email></author><summary type="html"><![CDATA[Workshop materials from Teardown Portland 2026: building up a badge-and-reader access control system from a bare UID check to mutual authentication with derived keys and session keys, one broken assumption at a time.]]></summary></entry><entry><title type="html">How Hardware Gets Hacked (Part 7): Freshness and Randomness</title><link href="https://lightturtles.github.io/works/2026/07/22/how-hardware-gets-hacked-part-7-freshness-and-randomness/" rel="alternate" type="text/html" title="How Hardware Gets Hacked (Part 7): Freshness and Randomness" /><published>2026-07-22T00:00:00-05:00</published><updated>2026-07-22T00:00:00-05:00</updated><id>https://lightturtles.github.io/works/2026/07/22/how-hardware-gets-hacked-part-7-freshness-and-randomness</id><content type="html" xml:base="https://lightturtles.github.io/works/2026/07/22/how-hardware-gets-hacked-part-7-freshness-and-randomness/"><![CDATA[<p><em>Originally published on <a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-7-freshness-and-randomness">Maker.io (DigiKey)</a>.</em></p>

<p><img src="/assets/hhghp7/hhghp7_02_rolljam_diagram_300.png" alt="RollJam attack sequence: an attacker intercepts each unlock message from a fob, forwards the previous message to the car, and keeps one unused valid message in reserve to unlock later" /></p>

<p>Part 6 closed the door on naive replay with a rolling counter and an AES-CMAC. Part 7 shows the door was only half-shut: three more sophisticated attacks still let an attacker replay a stale message. <strong>RollJam</strong> intercepts every unlock attempt, forwards the second-most-recent message to the car while banking the newest one for later use. <strong>Forced rollback</strong> re-flashes the car’s firmware to reset its stored counter to zero, resurrecting an old captured message. <strong>Forced rollover</strong> just triggers enough unlocks (up to 65,536 for a 16-bit counter) to wrap the counter back around to a value an old message still satisfies. All three exploit the same gap: the scheme proves a message came from an authenticated fob <em>at some point in the past</em>, never that it was generated <em>right now</em>.</p>

<p>That distinction — between authenticity and freshness — matters differently depending on what’s being verified. A signed firmware update is fine no matter how old it is; the signature alone proves authenticity. A live “unlock” command isn’t, because an attacker watching the wire can capture and hold a genuinely valid message and nothing about it says when it happened. The fix is challenge-response: the car generates a random value (a “nonce,” a <strong>n</strong>umber used <strong>once</strong>) and asks the fob to compute a MAC over it. Since the fob can’t know the nonce until the car sends it, a correct response proves the fob is answering <em>this</em> conversation, not replaying pieces of a previous one.</p>

<p>That scheme is only as strong as the nonce generator behind it. A naive LCG-style <code class="language-plaintext highlighter-rouge">rand()</code> is out — Part 5 already showed one is invertible from three intercepted outputs. Instead, the article reuses AES-CMAC itself as a one-way PRNG (the same construction NIST recommends for CTR-DRBG), driven by a key kept separate from the unlock key through key separation. That still leaves one gap: if the PRNG always starts from the same internal state, an attacker who can reset the device sees the identical “random” sequence every time. Closing it means seeding the PRNG from actual hardware noise — RAM startup values, clock jitter, ADC readings on Vcc, temperature, and a floating pin — accumulated into an entropy pool and measured for real using NIST’s SP800-90B toolkit rather than assumed.</p>

<p>The article closes with a look at what else could get you there: TOTP (if fob and car can keep synchronized time), rate-limiting unlock attempts, a hardware anti-rollback counter, or an MCU with a built-in TRNG — each a different way of buying the same property the whole article is really about: proving a message is fresh, not just authentic.</p>

<p><a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-7-freshness-and-randomness">Read the full article →</a></p>]]></content><author><name>Nathan Jones</name><email>contact@lightturtles.com</email></author><summary type="html"><![CDATA[Breaking the eCTF key fob's rolling-counter-plus-MAC defense with RollJam, forced rollback, and forced rollover attacks, then closing the gap with a challenge-response nonce scheme and a PRNG seeded from real hardware entropy.]]></summary></entry><entry><title type="html">Comparing MicroPython, Zephyr, Lua, and Forth</title><link href="https://lightturtles.github.io/works/2026/07/13/comparing-micropython-zephyr-lua-and-forth/" rel="alternate" type="text/html" title="Comparing MicroPython, Zephyr, Lua, and Forth" /><published>2026-07-13T00:00:00-05:00</published><updated>2026-07-13T00:00:00-05:00</updated><id>https://lightturtles.github.io/works/2026/07/13/comparing-micropython-zephyr-lua-and-forth</id><content type="html" xml:base="https://lightturtles.github.io/works/2026/07/13/comparing-micropython-zephyr-lua-and-forth/"><![CDATA[<p><em>Originally published on <a href="https://www.digikey.com/en/maker/blogs/2026/comparing-micropython-zephyr-lua-and-forth">Maker.io (DigiKey)</a>.</em></p>

<p><img src="/assets/comparing_lua/summary_table.png" alt="Comparison table scoring MicroPython, Zephyr, Lua, and Forth (red/yellow/green) across getting started, C/C++ integration, code size, execution speed, and features" /></p>

<p>An interactive serial REPL is one of the fastest ways to prototype on an embedded system, inspect state, or tweak configuration without a debugger. The article puts four options for reaching one head-to-head on an STM32 Nucleo-F411: MicroPython, the Zephyr shell utility, Lua, and Forth — scored on how fast each reaches a working prompt (on supported hardware and on a brand-new device), how large the compiled binary is, how much slower than native C/C++ it runs, and how hard it is to call into (or be called from) existing C/C++ code.</p>

<p>MicroPython wins on capability — the full Python language plus its library ecosystem — but it’s also the heaviest: 277 kB compiled, 50–250x slower than native code, and porting it to an unsupported MCU can run from a few days to two months. The Zephyr shell sits at the opposite extreme: 44 kB, the fastest of the four since application code is just C, and trivial to interface with existing C/C++ — but it isn’t an interpreted language, so there’s no REPL in the “declare a variable and write a loop” sense. Lua (144 kB, 10–50x slower than native) lands in the middle: mature, genuinely interpreted, easy to embed, and consistently cheaper to port to a new MCU than either MicroPython or Zephyr. Forth is the outlier: only 25 kB and the smallest interpreted option by far, but its stack-based RPN syntax gets unwieldy fast once you’re juggling more than a couple of variables.</p>

<p>The article’s recommendation is Lua for most projects: capable enough for real prototyping, small enough for constrained devices, and — critically for anyone targeting more than one MCU — the one option where porting effort stays predictable instead of ballooning with each new device tier.</p>

<p><a href="https://www.digikey.com/en/maker/blogs/2026/comparing-micropython-zephyr-lua-and-forth">Read the full article →</a></p>]]></content><author><name>Nathan Jones</name><email>contact@lightturtles.com</email></author><summary type="html"><![CDATA[MicroPython, the Zephyr shell, Lua, and Forth, evaluated head-to-head on an STM32 Nucleo board for time to a working REPL, compiled footprint, execution speed, and how hard each is to interface with existing C/C++ code.]]></summary></entry><entry><title type="html">How Hardware Gets Hacked (Part 6): Authenticated Exchanges</title><link href="https://lightturtles.github.io/works/2026/07/06/how-hardware-gets-hacked-part-6-authenticated-exchanges/" rel="alternate" type="text/html" title="How Hardware Gets Hacked (Part 6): Authenticated Exchanges" /><published>2026-07-06T00:00:00-05:00</published><updated>2026-07-06T00:00:00-05:00</updated><id>https://lightturtles.github.io/works/2026/07/06/how-hardware-gets-hacked-part-6-authenticated-exchanges</id><content type="html" xml:base="https://lightturtles.github.io/works/2026/07/06/how-hardware-gets-hacked-part-6-authenticated-exchanges/"><![CDATA[<p><em>Originally published on <a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-6">Maker.io (DigiKey)</a>.</em></p>

<p><img src="/assets/hhghp6/hhghp6_05_unlock-message-with-MAC_300.png" alt="Unlock message built from a UNLOCK_MAGIC byte, length, fob ID, and counter, with an AES-CMAC computed over that payload and the secret key appended as the MAC" /></p>

<p>Part 5 ended with the fob’s PRNG-based defense broken by simple algebra — a linear congruential generator turned out to be invertible from just three intercepted messages. Part 6 replaces it with real cryptography: a secret key generated at build time and embedded in both car and fob firmware, combined with a <strong>rolling counter</strong> that increments on every unlock attempt. Before transmitting, the fob runs its ID, the counter, and the unlock command through AES-CMAC with the shared key and appends the resulting MAC to the message.</p>

<p>The car doesn’t need to predict anything; it recomputes the same MAC over the received fields and checks that it matches, and that the counter falls within an acceptable window of the last value it saw. A replayed message fails the counter check. A forged message fails the MAC check, because producing a valid one requires the secret key, which never travels over the wire in the clear. That’s the difference between <strong>authenticity</strong> and mere obfuscation.</p>

<p>The article closes by turning the “what’s still broken?” question back on the reader: counter rollover, flash wear from writing the counter on every unlock, revoking a lost or stolen fob, and capping how many fobs can pair to one car all need real answers before this ships to production.</p>

<p><a href="https://www.digikey.com/en/maker/blogs/2026/how-hardware-gets-hacked-part-6">Read the full article →</a></p>]]></content><author><name>Nathan Jones</name><email>contact@lightturtles.com</email></author><summary type="html"><![CDATA[Replacing the eCTF key fob's broken PRNG-based unlock scheme with a rolling counter and a real MAC — AES-CMAC — so the car can verify an unlock message came from a paired fob without the shared key ever crossing the wire.]]></summary></entry><entry><title type="html">Debug Better: Take a Break</title><link href="https://lightturtles.github.io/works/2026/07/01/debug-better-take-a-break/" rel="alternate" type="text/html" title="Debug Better: Take a Break" /><published>2026-07-01T00:00:00-05:00</published><updated>2026-07-01T00:00:00-05:00</updated><id>https://lightturtles.github.io/works/2026/07/01/debug-better-take-a-break</id><content type="html" xml:base="https://lightturtles.github.io/works/2026/07/01/debug-better-take-a-break/"><![CDATA[<p><em>Originally published on <a href="https://www.digikey.com/en/maker/blogs/2026/debug-better-take-a-break">Maker.io (DigiKey)</a>.</em></p>

<p><img src="/assets/take_a_break/hamilton.png" alt="Angelica singing &quot;Take a Break&quot; to Alexander Hamilton in the musical Hamilton" /></p>

<p>Your brain doesn’t stop working on a problem just because you’ve stopped looking at the screen. The default mode network — the same circuitry that’s active when you’re washing dishes, walking, or showering — keeps consolidating and recombining what you already know, often surfacing the fix that hours of focused staring couldn’t find. That’s not procrastination; it’s how insight actually works.</p>

<p>Lin-Manuel Miranda put it well: “A good idea doesn’t come when you’re doing a million things. The good idea comes in the moment of rest.” Next time a bug won’t budge, step away from the keyboard for a few minutes — the fix might be waiting when you get back.</p>

<p><a href="https://www.digikey.com/en/maker/blogs/2026/debug-better-take-a-break">Read the full article →</a></p>]]></content><author><name>Nathan Jones</name><email>contact@lightturtles.com</email></author><summary type="html"><![CDATA[Why stepping away from a hard bug — a walk, a shower, doing the dishes — often gets you to the fix faster than staring at the screen for one more hour.]]></summary></entry></feed>