In this post, I will describe the solution for 3 challenges I was able to solve.
The related solution files and support binaries are available here.
PicoHSM Speed Dating
This challenge was presented as an ARM ELF binary (provided in the Git repository), as well as a photo of the target we are exploiting:
A special note should be made here, to commend the Ledger Donjon team for having this challenge available throughout the CTF duration, particularly given the frequency of crashes and the fact that multiple people were attempting to exploit a fixed number of targets. Great work!
Inspecting the binary in Ghidra, we note that a stack overflow is present within the handle_client function:
My first instinct was to wrangle this into a ROP chain, using gadgets to overwrite debug_enabled and the debug_key, which are at static locations.
Unfortunately, I couldn’t get the ROP mechanism itself to work (entering more than a single return address would cause a crash). As part of debugging this, I built an emulator using Unicorn Engine, as suggested in the Donjon CTF discord (emu.py, emu2.py): while ROP worked in the emulator, it refused to work on the live target.
Eventually, I realized that a ROP chain wasn’t required, I could simply return to the executable stack. From there, it was smooth sailing to use ARM binutils to craft appropriate shellcode, and write a loader to insert a nop sled of the appropriate length, and take a wild guess at where the actual return address was (the emulator gave some clues in that regard).
The final solution is in fuck2.py:
Super Public Algorithm
This challenge was presented as a single power trace in npy format, representing power consumption over an ECC signature execution. Our goal was to retrieve a private scalar and therefore, decrypt a known ciphertext.
I began by plotting the trace in numpy. It is immediately apparent that there are differences in “rounds” of the algorithm, which should theoretically correspond to different operations (point multiplication?) depending on the bits of the key:
I then automated a simple power analysis attack, using a sliding window to detect the rounds of the ECC algorithm, and then checking the magnitude of leaked energy during the first spike to distinguish between the two separate waveforms.
The solution is in solver.py, outputting the correct scalar in base10:
We can then use the provided flag decryptor, to retrieve the flag:
This challenge was presented as source code, and a remote service which could provide a fault (and power trace) for the verify_flag function. The source code is presented below:
My initial approach was to study the SPECK algorithm, but I quickly realized that we could skip the algorithm entirely using a fault attack, and then perform a DPA against the result of the OR operation in each round of the “password check”.
We can first skip the fault check, by simply incremending the fault offset and looking for a low sample count trace. It turns out for a 16-byte password, the correct offset is 286. The difference between a correctly glitched trace, and trace executing all rounds of SPECK is below:
The next step was to generate a large amount of random traces with the glitch applied with corresponding plaintexts. The idea is that the OR operation will leak a different amount of energy depending on the difference between the provided password, and the true key (the provided password is directly
Interestingly, the correlations are not particularly strong (but not incorrect, for the most part). The first two bytes are however incorrect, regardless of the particularly strong correlation.
Upon further review, it becomes apparent that the correlation will be particularly weak because of the subtraction operation, that is, assuming the true key is 0xAA, and the test key is 0xAB, the leaked energy is only 1 bit. However, if the true key is 0xAA and the leaked key is 0xA9, then the leaked energy is tremendous (number is -1, so 64 1’s). A better way to review this would be simply to view the power traces manually, changing one byte at a time:
A short amount of brute forcing later (two key bytes from the DPA result), revealed the true key, and the flag. Note the significantly lower energy consumption from all key bytes towards the right, indicating the correctness of the key (also the valid flag, indicating the same).
Once more, thankyou to the Donjon CTF team for putting together this challenge, this was a lot of fun and a great learning experience!