Introduction
Ah the Phospholipid Badge Addon! This ended up being my favorite part of the CTF this year, and frankly the one that took up most of my time. A highlight for me every year at NSEC are the hardware tracks. I do not get many opportunities to dive into hardware-based, hands-on, CTF challenges remotely, so when I attend an event in person and I can do so I jump right in. NSEC has done a great job building on previous versions, such as The Horse Pt. 1 and The Horse Pt. 2, and this year frankly outdid themselves by essentially providing three separate experiences on the same badge. When it was originally whispered through the grapevine that the conference badge, which while had no embedded flags did provide an interesting social challenge, would be used for the CTF itself, I was curious how they would approach it. I am happy to say I was first in line to get the CTF firmware flashed as they opened, and outright floored when a further addon Phospholipid portion was dropped off at our team’s table.
This writeup will focus only on the Phospholipid addon of the badge. I plan on potentially adding other writeups focusing on the CTF firmware and conference social aspects afterwards. During the closing ceremony an overview of the completion rates was provided and it partially shocked me that while I had managed to solve ECDH
(which only two other teams completed), I woefully missed out on the another_128
during the CTF. That was rectified afterwards and frankly is a testament to the quality and enjoyment I had as even after the CTF I continued to hack away at the addon.
Lastly, before we dive in to the track, I want to thank all the NSEC staff, volunteers, and challenge designers for making the event as special as it always is. I’d also like to thank my teammates from Cyber Crew
, it would not have been the same experience without you all. Lastly, I want to explicitly thank Jonathan Marcil
for designing this Phospholipid addon track, as well as entertaining my questions after the close of the CTF. I learned a ridiculous amount as a result, and I hope that this writeup at least provides some justice to how good of an experience it was.
With that, let’s jump in!
Phospholipid Addon
This journey started off with a hand-delivery to the team’s table as well as a new forum post.
The spec sheet provided excellent information and forshadowed an overall theme of this track - reading the docs!
Ok good information on what we will be dealing with. I’ve learned my lesson the hard way once before! I decided to read fully through the datasheet before attempting to connect anything else.
Plug Me
The datasheet provided instructions on how to wire the addon to our conference badge, indicators to look at like the blue LED to make sure everything was correct, and an overview of the commands we could expect to see once the addon firmware was installed and activated. I thought this part was a smart way to introduce the concepts to anyone who had not worked with hardware before and provided just enough of a challenge that it was not trivial.
Once everything was connected cable wise, our serial connection was established, and we rebooted the badge, we were greeted with a few nice lines of debug, again foreshadowing for later, and our first flag!
Flash Dump
Almost immediately this is where I deviated from the intended path. Instead of diving into the challenges through the serial console, I looked at the addon and asked myself “how can I dump this firmware?”. My experience up to now had been with esp32-style chips and using esptool
so this was a new challenge for me. Looking for similar tooling for W25Q64JV
chips did not come up with anything useful, so I decided to take a more generic approach. Let’s see if I can get it using my Flipper Zero! Side note, I loved that the FZ made an appearance at the Hacker Jeopardy night.
After a quick bit of googling (specific verbiage for Jonathan Marcil
to cringe at) I quickly found SPI Mem Manager. With the wiring connection provided it should hopefully just be a matter of connect the dots. Small problem however… looking back at the datasheet provided with the addon, we see that the VCC/GND of the chip isn’t hooked up to the 2x4 connector on top of the addon.
In retrospect, despite the fog of the CTF I should have realized I could easily connect VCC/GND from the 2x3 SAO connector on the bottom of the addon without any more complicated cables. But where is the fun in doing things the simple way!
Ok, this might be a challenge (narrator voice But it shouldn’t have been - see note above). I doubt I can hold cables on the flash pins steadily enough to make it work. Thankfully my buddy Boschko hooked me up with a few extra connectors and it was stable enough to read properly!
After getting the firmware over to my VM a quick strings and we got the first actual flag, and a bonus (more on that at the end of the writeup).
Read First 128
Alright, we got this flag already from the dump, but what was the inteded way to do this? Going down this path is fundamental as it will teach us to work with the addon hardware more directly, and the knowledge will be required to do the second flag. So let’s step through that command and see if we can read?
Well that doesn’t look great… what can we do? If we go back to the datasheet we can see probably why we can’t write.
Ok, that seems interesting. Let’s go find the W25Q64JV vendor spec sheets as well if there is more information. Again, this will end up being useful moreso for the next flag, but let’s see what it says about the WP.
Interesting. If we go back to the provided Phospholipid datasheet there is one more clue.
Ok, ok. In theory, if we are able to pull up the WP, we might be able to successfully write. Let’s connect the WP pin to an SAO VCC pin and… well a new green led has lit up, so far so good.
But if we try to writing… still no luck. Oh wait, if we careful read the documentation above this only impacts the ability to write to Status Registers. Wait, we saw that in the initial recon. Let’s jump into SPI mode using raw_toggle
.
What does this even mean? Back to the datasheets! We seem to be able to control Register1 with the command raw_write_register1
so let’s focus on what that one does.
Since the register is currently 0x9C
, or 10011100
, we can tell that:
- SRP is 1
- Block protect bits are all set
- We understand by pulling up WP we moved it to
Hardware Unprotected
which hopefully confirms our suspicion with being able to write the register
Those block protect bits seem suspicious. Let’s try to remove those and see if we can then write our 0xAA
value in the regular mode. Removing those bits gives us the register value of 10000000
, or 0x80
. Here goes…something?!
Well at least it looks like it stuck! Let’s go ahead and toggle raw mode back over and see if we can get our flag the proper way!
Yes! I smell a flag.
Excellent. Alright that’s now two ways to get the first flag. Time to move on to the next one.
Read Another 128
With the modifications from the first flag in, let’s try the next command.
Well, we did just allow ourselves to write, so let’s go back and reset the register to see if that helps.
Closer! So we need to block writing at 0x050048
but be able to write at 0x080048
. Hum… up to now we have more or less turned on the ability to write or not. Back to the datasheets!
It seems we should be able to control arbitrary areas of memory protection. That seems exactly like what we need. We have our required addresses, so let’s find the combination that will work for us. It looks like there is only one.
Alright so the register should be 00101100
or 0x2C
. Let’s set the register and give the command another shot.
And there we go! Our knowledge of the W25Q64JV
chip has come in handy and we now know how to control registers and protect memory.
Crypto Read Zone
I have to admit I kind of stumbled onto this one. I started enumerating the slots and blocks I could read with the crypto_read_zone
command and ran into:
Well good thing we have a crypto_write32_from_hex
command as well. Let’s see if we can add a 0x42
as the first byte.
And sure enough, there’s our flag!
Crypto HMAC
Time for the next command!
Ok, if we are able to read the value of slot 9, we should be able to get the key to perform the arbitrary HMAC.
Excellent, we have everything we need now to perform the HMAC and use the first 32 bits as the flag.
Crypto ECDH
Time to step into the deep end! Crypto ECDH
ended up being my favorite challenge of NSEC2024. It required combining the experience we’ve gathered with commands on the addon and building on how real-world crypto operates. I have to admit when I finally understood what we needed to do with this challenge, created my own P256 keypair and was able to successfully trigger the comparison, it felt great. I’ll step through how I solved this, but know this was after hours of scratching my head, failed attempts, and a ridiculous amount of searching online. Alright, let’s start with what we know.
We know how to read, and there is a specific command for crypto_print_pubkey
, so let’s see what that looks like.
Well that looks like a public key alright. The command also mentioned a provided public key in slot 13. Let’s see what’s there.
Ok that looks less good. Probably why the command mentions user provided
. Last thing before we dive into some theory, let’s try submitting something.
Hum, well that’s not ideal. But let’s keep that filename in the back of our heads. Now let’s start with the basics. What are we even trying to do? What is a ECDH premaster secret
?
- During the TLS handshake, the client and server negotiate cryptographic parameters, including the choice of cipher suite.
- If ECDH is selected, both parties generate random key pairs (private keys and corresponding public keys).
- The client sends its public key to the server, and the server sends its public key to the client.
- Using the received public keys and their own private keys, both parties compute a shared secret (the ECDH premaster secret).
Ok, this is starting to make sense. We don’t have access to the private key on the badge, but we are able to print out the public key. Combining that with our research above, to calculate a premaster secret, we would need to do a few different things.
- Generate our own EC keypair
- Upload our public key to slot 13
- Use our private key with the badge’s public key to compute the ECDH premaster secret
- Call the
crypto_ECDH_premaster_secret
with our premaster secret value
What should happen then in theory is that badge computes its premaster secret value, using its own private key and the public key we provided at which point the value should match what we provided. Great, “in theory”, we know what to do. Now all that’s left is actually doing it…
Generate Keypair
To generate the proper keypair, we should probably understand what parameters are being used by the badge. I ended up finding a nice GitHub repo that looked quite useful - https://github.com/espressif/esp-cryptoauthlib. Recognize the name from the error message above? From the repo:
This is a port of Microchip’s cryptoauthlib for ESP-IDF. It contains necessary build support to use cryptoauthlib with ESP-IDF as well as esp_cryptoauthlib_utility for configuring and provisiong ATECC608A chip connected to an ESP module. Currently the utility is supported for ESP32, ESP32S3 and ESP32C3
If we start going into the various scripts we end up at the calib_ecdh.c
file responsible for ECDH calculations on the addon.
\param[in] public_key Public key input to ECDH calculation. X and Y
integers in big-endian format. 64 bytes for P256 key.
That’s not an outright smoking gun, but I decided to throw a few select attributes into copilot and see what it would come up with.
Ok, looks like we are working with P256 EC keys. Great, all that was left for this step was to use openssl to generate a keypair.
That is looking similar to what we got from the badge, so I’ll assume it is correct for the moment. Let’s see how we get that on to the badge now.
Compute ECDH premaster secret
Taking the badge’s public key from above and moving it to the VM it only takes a few lines of python to compute the premaster secret.
Assuming we didn’t royally screw something up, we have our value! Let’s see how we can get our public key on to the badge.
Upload public key
I originally asked myself “alright, now what” as I didn’t have a good idea how to start here. We know how to write to a specific slots in blocks, but we also have a limited amount of space to write to - 72 bytes. This seems to line up with the documentation above however as it states that the public keys should be 64 bytes. If we figure out what to write it should be block 13 slots 1 and 2.
The first assumption I made was that I could take out the header and footers and focus on the base64 encoded information only. Well that is still 124 characters and too large. Let’s decode the b64 and see if anything makes sense.
That’s not super useful, but, let’s see what happens when we do the same for our own public key.
Oh-ho! The first 27 bytes are the same? Let’s see if we can get some clarity on that. A quick search in the cryptoauthlib repo and I found this section in the pkcs11_key.c
file.
Alright that definitely makes sense. What solidified my thinking was that once you removed the header from the base64 decoded public key material… we are left with exactly 64 bytes!
With everything we’ve learned, that means the 64 bytes representing our public key that needs to be written to memory is:
b6b06eef2270a89b3ed2ee205763ef0e10aa7964685d53e1c032b49f9d7c4f47140fcc23a3441f70654433f62a305bf01fae312fd1aa5b0cb7dc7c5725a5dc62
We know how to do that, so let’s get it done.
And as a last note before we try with our computed premaster secret, during the CTF I noticed that once a valid public-key was added the crypto_ECDH_premaster_secret
command no longer errored out, further reinforcing that we were on the right path.
ECDH - Profit?
Well, let’s give it a shot with our premaster secret!
Crypto Bad Nonce
Our last challenge on the Phospholipid addon! The crypto_bad_nonce
starts with a nice little narrative on what we are trying to accomplish.
Key Generation
There are quite a few hints throughout this blurb. It did seem suspicious that certain parts were quoted and sure enough after the CTF there was confirmation that this was intended. What is an interesting side-note however was that not all search engines were equal and a nice post-NSEC blog post goes into more details - I was at least one of the people mentioned where nothing showed up on Google search.
With that said, I was still able to find the relevant documentation with a bit of sleuthing and the hints lead us to the Microchip CryptoAuthentication Library documentation. Specifically to start:
In the same documentation we were able to find the GenDig documentation as well.
At this point we understand a bit more of what is going on, but it isn’t really enough to start putting all the pieces together. I kept sleuthing around, and was jumping through the code references I could find.
Looking at the source repo I was able to find the code for atcab_nonce
.
And then similarly the code for atcab_gendig
.
While I was able to get some further details, like ATCA_GENDIG
and GENDIG_ZONE_DATA
definitions, it wasn’t until I came across the ATECC608-Tools repo that things really started coming together. Specifically the read_encrypted.sh file is where the initial GenDig logic was laid out for me.
Ok, let’s start putting what we know together and see if we can come up with the proper key. We can pull the serial of the chip from the console easy enough. Similarly, we know we are loading TEMPKEY
with a known value, so it simplifies our approach. I put everything together in a small script:
Which gives us:
Disclaimer: this is where I finished at the end of the CTF. I ended up getting the right key after all it seems, but I was convinced I had messed something up in the key generation, not in the decryption portion. It wasn’t until several days after the CTF closed, a lot more research, and a sanity check from Roujo that my mistake was realized. It is frustrating, but at the same time validating that I had actually gotten the logic correct, and doubles as a “check every part of your work” reminder.
Ciphertext Decrypt
Now that I know my key is actually correct, time to finish off the challenge, and subsequently the entire Phospholipid track. Looking back at where I went wrong, I think I was focusing too much on trying to use openssl
as that is what I leveraged during the ECDH portion. Either way, with a slight pivot in thinking, and a renewed sense of sanity, I took a different approach to see if it would go better.
With a hope and prayer I kicked it off…
And with that we have successfully completed the Phospholipid track. Wow, what a journey.
Summary
As always, this year’s NSEC CTF was a smashing success as far as I’m concerned. The leaning in to the hardware tracks, from the multiple badge tracks, RF, and other physical-based challenges, was very welcomed. I ended up learning a ridiculous amount about how to work with hardware, and at the end of the day this is exactly what I am looking for.
Follow-up next steps
For those who pay attention to detail, you may remember that there was an interesting message when submitting the Flash Dump Canary flag:
Your team submitted flag “Phospholipid 0 - FLASH dump” for 0 point! This flag was a canary.. for a special prize! (limited quantity, first solvers first)
What was this all about? Well Sunday morning during the CTF our table had a nice drive-by drop off!
So what I’m seeing is I’ll be able to build my own Phospholipid? Hell yeah! I’m going to leave this for a future exercise once I’ve had a bit of rest and I plan on looking through the source code once released, but this will be an excellent post-CTF continuation.
Commentary
At the end of the CTF when I approached Jonathan Marcil
to discuss a few things and I mentioned that this writeup would be happening he explicitly asked me to be critical. I will double down and say that I mostly have to struggle to find things to criticize! The evolution of the track itself I thought was well done. All the pieces build on each other, and the custom datasheet was fantastic as far as information to get things kicked off even if you had no idea where to start.
The Google search element was a small detraction sure enough, but not enough to completely derail me during the CTF. A few stubborn searches later and I did end up in the right place. I think if the explicit use of quotes was meant to be a quick pivot to the documentation then it might not have gone to plan, but these slight detractions are also part experiencing a CTF.
Lastly, the DIY pack for those who went down the rabbit hole was a fantastic motivator to continue learning well after the CTF itself. All I can say is chef’s kiss.
I’m already looking forward to next year and I am hoping to see even more hardware-based challenges. Here’s to the next several months’ worth of practicing to be ready!
Thanks folks, until next time!
COMMENTS
You can use your Mastodon or other ActivityPub account to comment on this article by replying to the associated post.