MITRE ECTF 2025
MITRE, a leading organization in the field of computer security, hosted a nation-wide security competition
called the ECTF (Embedded Capture the Flag) in 2025 that I participated in as part of the University of Michigan's cyber security team.
This competition focused specifically on the subject of device security in the context of embedded systems, and featured two main phases:
In the first phase, from January through February, all of the over 100 participating teams were tasked with designing the same product (a device performing DRM for a
described satellite TV subscription service)
In the second phase, from February through April, all of the teams received all other team's designs, and were tasked with violating various security properties in them.
The unique feature of this competition is that all implementation was all done on real hardware (the MAX78000 system-on-chip), and consequently
attacks done in the attack phase could exploit this fact, leveraging physical access to the devices to do sophisticated attacks like voltage glitching
that aren't possible remotely.
We placed 3rd nation-wide in the competition, and it is the opinion of me and the other leading team members that I played a crucial role towards making this possible :)
Our design did not have any discovered security vulnerabilities for it over the course of the entire competition, making us one of nine teams with a perfect defense!
The competition details can be seen on MITRE's ECTF website.
UART Timing Side Channel
HMACs (Hash-based Message Authentication Codes) are a key primative of cryptographic security, as they provide a integrity check that a given message
was not tampered with after being created or approved by some trusted party.
Conceptually they are quite simple: I take my message, some arbitrary collection of bytes, and perform a series of computational steps upon the entirety of it to generate a
digest code, also utilizing a fixed secret key attackers cannot know in the process.
If I, as an attacker, receive this joint combination of the message and its hmac, it is cryptographically impossible for me to modify the
message in any way and produce the correctly modified hmac that will make the recipient accept it despite my manipulation;
Assuming a cryptographically-strong hmac protocol is used, the attacker does not know the secret key, and no other information is leaked,
this provides a cryptographic strong integrity check; as an attacker I would have no better way to forge a modified message than by
trying all possible hmac values, something that would take an infeasibly long time to do.
This however, is all predicated on a very important assumption, which is that the attacker learns no information regarding the
check of the hmac performed by the victim other than if it passed or failed. Several teams's designs from the competition violated this crucial
requiremnt in a subtle but ultimately devastating fashion!
In order to check if a hmac is valid, the recipient takes the message contents and calculates what the corresponding hmac should be for it,
utilizing the secret key it (and not the attacker) knows. It then checks whether the hmac actually sent matches the calculated hmac,
and rejects the message as fradulent if it does not. In psuedocode,
message, hmac = receive_message()
correct_hmac = calculate_hmac(message, secret_key)
if (hmac differs from correct_hmac){
reject_message()
}
The correct way to perform the above calculation is with a so-called constant-time comparison, which is a function which always
takes the same amount of time to check equality regardless of if and where/how the two hmacs differ or not.
Several teams did not do this, as seen in the below code summarized from the actual design of one participating team from the competition.
main(){
...
read_packet(&cmd, uart_buf, &pkt_len);
...
switch (cmd) {
// Handle subscribe command
case SUBSCRIBE_MSG:
STATUS_LED_YELLOW();
update_subscription(pkt_len, (subscription_update_packet_t *)uart_buf);
break;
...
}
}
int update_subscription(pkt_len_t pkt_len, subscription_update_packet_t *update)
{
...
// Extract subscription data and signature
const uint8_t *sub_data = (uint8_t *)update;
const uint8_t *received_sig = sub_data + sizeof(subscription_update_packet_t);
// Verify signature before processing
if (!verify_signature(sub_data, sizeof(subscription_update_packet_t), received_sig))
{
STATUS_LED_RED();
print_error("Invalid subscription signature\n");
return -1;
}
}
bool verify_signature(const uint8_t *data, size_t data_len, const uint8_t *signature)
{
Hmac hmac;
uint8_t computed_sig[32];
int ret;
ret = wc_HmacInit(&hmac, NULL, INVALID_DEVID);
if (ret != 0)
return false;
ret = wc_HmacSetKey(&hmac, WC_SHA256, SIGNING_KEY, 32);
if (ret != 0)
{
wc_HmacFree(&hmac);
return false;
}
ret = wc_HmacUpdate(&hmac, data, data_len);
if (ret != 0)
{
wc_HmacFree(&hmac);
return false;
}
ret = wc_HmacFinal(&hmac, computed_sig);
wc_HmacFree(&hmac);
if (ret != 0)
return false;
return memcmp(signature, computed_sig, 32) == 0;
}
Trimmed and paraphrased down to the most important bits,
read_packet(&cmd, uart_buf, &pkt_len);
STATUS_LED_YELLOW();
const uint8_t *sub_data = (uint8_t *)uart_buf;
const uint8_t *received_sig = sub_data + sizeof(subscription_update_packet_t);
uint8_t computed_sig[32] = calculate_hmac(secret key, sub_data)
if hmac_calculation_failed(){
error()
}
if(!memcmp(received_sig, computed_sig, 32)){
STATUS_LED_RED();
print_error("Invalid subscription signature\n");
}
In English, whenever we receive a new subscription update message, we
0) Receive the message over UART
1) Turn a Yellow LED on
2) Do miscellaneous error checking (omitted) which takes a constant amount of time
3) Compute the expected hmac of the received message
4) Check with memcmp to see if it's the same as the hmac actually sent
5) If it differs, turn the Red LED on
The problem with this code is that memcmp (memory compare, from the cstdlib) does not generally provide constant time comparison, and on this platform
its implementation compares larger messages a byte at a time, returning immediately if any pair differs.
Although the actual implementation has a few details I'm omitting here (it actually compares 4 bytes at a time until a difference is found and only then drops into a byte at a time to locate where the difference occurred),
the general takeaway is that if two messages differ near their end (e.g. ABCDEFGH and ABCDEFGI) memcmp will take slightly longer to return than if they differ near their start (e.g. ABCDEFGH and ZBCDEFGH)
This subtle timing difference (only a few instructions, on the order of microseconds!) is then transmitted into something we can actually observe via the LEDs;
if the two compared hmacs first differ in their second byte, the Red LED will take slightly longer before turning on than if they differed in the very first byte!
By soldering a wire onto the SMD resistor connected to the LED on the board and monitoring its voltage, we can very precisely time the interval of the LED changing to red from yellow.
We can then take a given modified message we'd like the device to accept as valid, start with an hmac consistingly entirely of zeros, and then permute the first byte of the hmac until we observe the timing interval slightly increase for one value.
At this point, we know the first byte of the sent hmac correctly matches the calculated one, and we move onto the second byte, repeating until all 32 bytes of the hmac are known.
After doing this, we have the correct hmac for our forged message, and our victim device will happily accept it as valid!
(Of note, this is why it's generally a good idea to encrypt the underlying message before providing integrity with an hmac; even if the attacker can somehow bypass the integrity check via this or other techniques (e.g. voltage glitching to skip past the comparison instruction), the actual message accepted as valid can't be directly set by the attacker, because it will be decrypted first with a key the attacker does not know!)
Using this neat voltage-based timing side channel, we were able to defeat the security of one team, and through use of a slightly more sophisticated variant
(timing off of the UART acknowledgement edges, substantially noisier due to the lower clock speed of the UART controller reducing our timing resolution and thus requiring spicy statistics and many more samples to observe true differences),
we defeated another three more! :)
Voltage Glitching Attempts (and Some Beautiful Oscilloscope Captures!)
Under Construction
This part of my site isn't complete yet. Sorry about that!
Eventually I'm going to add a nice writeup here documenting what voltage glitching is, how I helped our team get a ChipWhisperer working on a practice board, and our attempts to get it working on the competition board (which we unfortunately did not finish before competition end)