Spatial TOTP (Insomni'hack CTF 2013)
Description
“I sealed my master phassphrase on this device and protected it using my own TOTP algorithm. Can you recover it ?
Once ready, come to the organizers desk to validate your solution on the device. (No connection to the device allowed)”
Solution
I didn’t solve this challenge on my own, but with nice team work with another team mate :)
Overview of the device
The device is a M5 Core (we used M5 Core Ink at Ph0wn CTF). By turning it to the left/right/up/down, you can enter numbers 0 to 3. The OTP code is a 6-digit code of numbers between 0 and 3. If you enter the correct code, you get the flag. If not, access is denied.
In theory, this is not a very secure password, but as we can’t script attempts, it’s still too long to bruteforce all 6-digit possibilities.
We could imagine connecting to the serial interface of the device and reading information, or dumping the entire firmware to retrieve the flag, but we are not allowed to connect to the device. The solution needs to come from reversing the challenge.elf
file which is provided in the description.
Reversing the Xtensa binary
The ELF file is an Xtensa binary. This is not supported by many decompilers, fortunately my team mate has already setup Ghidra with Xtensa support, so we don’t have to lose time setting it up.
|
|
In Arduino-like devices, the interesting main entry points are always named setup()
and loop()
.
The setup()
initializes the M5 Core, its screen and RTC. The most interesting part lies in loop()
.
Get the current timestamp and create a new OTP code based on the timestamp:
|
|
Tranform the 6-digit OTP code in a 6-digit code using only numbers between 0 and 3:
|
|
Compare the input sequence with the expected one. Display the ACCESS GRANTED image and the flag (from the EEPROM) if the code is correct:
|
|
Once again, if we had been authorized to physically connect to the device, we would have been able to retrieve the flag from the EEPROM. But we’re not allowed to, so the solution is
- Implement the OTP algorithm
- Implement the digit transformation
- Compile
- Go to the device, make sure our time is synchronized
- Run our program and get the correct code
- Enter it on the device to get the flag.
OTP implementation
We dig into TOTP::getCode
:
|
|
The functions calls getCodeFromSteps
with 2 parameters: the TOTP object, and a number of iterations.
If we decompile getCodeFromSteps
, we see it computes a HMAC-SHA1 over the number of iterations, using a HMAC key.
|
|
Then, there is some logic to truncate the output, but we don’t need to look into it now (and actually, we’ll see we don’t need to look into it at all).
|
|
Finding OTP configuration settings
So, it seems important to know
- What HMAC key is
- The number of steps which are used
Both information are part of the TOTP object: we see this->_hmacKey
in HMAC initialization, and this->_timeStep
in getCode()
.
In Ghidra’s Data Type manager, we search for the TOTP type.
It opens a structure editor where we see the fields of the object, including _hMacKey
and _timeStep
.
A right click on those lists the uses of the fields. We locate the instantiation of the TOTP object:
|
|
This sets the HMAC key in the TOTP object. It’s a 10 byte key. And 0x3c
is the number of iterations.
|
|
To find the value of hmacKey
, we click on it and go to the bytes view.
So, we now have all configuration settings for TOTP: the HMAC key (10 bytes) and the number of steps (0x3c).
How not to reinvent the wheel
We were about to reimplement the TOTP algorithm ourselves when we realized it was probably taken from the net. We searched for getCodeFromSteps
and HMAC and quickly found a C library on GitHub.
We cloned the library and confirmed it was exactly the code we had. So, no need to reimplement TOTP, we can just use it. Based on the README
, we created our TOTP solving program:
|
|
Then, we added the conversion to 0-3 digits only:
|
|
Our program finally compiled (see Troubleshooting section for more crunchy details). We went to the device, checked time synchronization between our laptop and the device (perfect - at most a few seconds difference), waited for the code to change, and then entered it and bingo!
Troubleshooting
Should talk about how much time we stupidly wasted trying to compile and link this silly program? Be kind with us, it was late, and we actually had to fix the library which was not taking care of multiple re-definitions. We added of couple of:
|
|
The other we ran into was the difference between our current local time and UTC time.
In the device’s code, you probably noticed timestamp = timestamp + 3600;
. We saw it too, and deduced that the TOTP code was based on UTC time. As we’re 1 hour ahead, we deduced we had to remove 3600 seconds from our timestamps in our own computation of the TOTP code. Unfortunately, the resulting code did not work. We checked our code, we checked the reverse and could not see any mistake, so we decided to also compute a code without removing 3600 seconds - because you know, it’s midnight and everybody is tired so maybe we just got it wrong. We did so, and we flagged. But without understanding why it worked without those 3600 seconds difference…