I’m back again for my second bug bounty! Searching for bugs is actually pretty fun, especially when it comes with a generous reward in my favourite cryptocurrency, Bitcoin!
I was scrolling Reddit like usual, and u/cryptocomicon has returned with a new version of TimeLock! That’s great news.
If we remember back to last week, I solved the original TimeLock challenge, and wrote a detailed writeup.
u/cryptocomicon issued a new challenge:
I’m so confident in this technology that I’ve created a challenge LockBox file which holds the private key to an address with 0.02 BTC.
Please give it a try.
NOTE: This is going to be much harder than last time.
Much harder than last time? Sounds interesting.
Challenge accepted.
Reconnaissance
You should probably go and read my previous post to find out about what TimeLock does, and how it works. This writeup is going to skip that information, since I don’t need to duplicate things.
Head on over to the download page at algomachines.com, and see:
I have again mirrored the files, so you can follow along:
TimeLock_v1_2.msi SHA256 330201c2ffb199e7f3d0f482f62cab3770fb952c8b832584f908ff42e87e9208 Challenge2.x SHA256 329f13a85610f35ec90eb752733e9b6efb7bc0588cbb8165e0ac6b355ba58190
I messaged u/cryptocomicon on Reddit, and asked what had changed from last time. The reply was actually extremely useful, but not until I had spent several hours reverse engineering.
From what can gather, the following has changed:
- The data file is further encrypted with something time related
- To decrypt the data, we need the password, answers and the available time range
- After each stage of decryption, the decrypted payload is verified
- The verification determines if the correct keys were used
- The final stage of decryption depends on current BTC network time
There is also a note about encryption cycle counts being implemented, but this did not hamper me in discovering the vulnerability in any way. It simply just made decryption take longer. Note, this new feature replaces the timer counting down from 120 seconds, and this is a more robust way to implement a wait. I can easily patch out a counter by setting it to 1 second, but I am forced to sit through all rounds of encryption in order to get to the data file.
Analysis
The scope of the bounty is the same as last time. Given the password, answers to questions, and now the date of availability and time of revoke, defeat the TimeLock mechanism.
From the download page, we know:
- The password is TimeLock
- The answer to the question is 0.02
- The date the file is made available is 22/02/2019 00:00 UTC
- The date of revocation is 23/02/2019 00:00 UTC
TimeLock tells the time via Unix timestamps, which it obtains from the Bitcoin network, by communicating with full nodes on port 8333, and sending the version command. This is pretty clear from wireshark traffic.
So, we need to understand how this new encryption stage utilises Unix timestamps, and how decryption is done.
First off, install and launch TimeLock, and attempt to decrypt Challenge2.x
For the first try, we will enter the correct password and answer the questions correctly. After some time for decryption to run, we receive this message:
Now, try again, this time entering the correct password, and give an incorrect answer. What do we see?
This is because TimeLock stores and encrypts everything in stages. From what we can see, getting the answers wrong, and getting the answers correct and getting the time wrong are two very different things.
Going back to what u/cryptocomicon said on Reddit, we can speculate:
- When we enter the password the questions and payload are decrypted
- When we enter the answers to the questions, the program somehow validates that the answer was correct, and resulted in successful decryption
- When the final decryption stage is run, the program validates that the time was correct by somehow validating that the decryption was successful
So what we need to do now, is check to see how the validation routines are implemented. To do that, we will take advantage of the create lockbox functionality.
Open notepad.exe and make a new file. Fill it with ‘A’ characters, maybe even on separate lines. Save it. In this case, I saved it as secret2.txt
In TimeLock, click “Create or Edit a LockBox”. Use secret2.txt as the data file, and set the password to “TimeLock” and add a question “How much BTC is in the wallet” with answer “0.02”. Set the time to be available five minutes from now, and revoking availability at some later time.
Make sure that the time is five minutes from now, or the third stage of encryption will not happen.
Now, open up IDA and load TimeLock.exe in. Once the program has finished disassembly, head on over to the strings window, and press ctrl-f to search. We are after the two strings “Incorrect answers.” and “Incorrect answer(s) or current time is not within the TimeLock window.”. Typing in “answer” will bring up both.
Double click “Incorrect answer(s) or current time is not within the TimeLock window.” to head to .data, and press x on the variable name to bring up the xref window.
From here, click OK, and we are taken to where the dialog box is spawned:
Right. We want to see where this came from, since it is the error message for having incorrect time. Double click the green line above to be taken to the conditional jump.
So, we see that if the cmp [rax+rbx], sil
is not satisfied, then we take the jnz down to the error message. This also means, that anything to do with checking the time will be above loc_7FF60681CC96.
Great. Time to go from the other direction. Let’s find out where the “Incorrect answers” prompt is, since that will be before anything to do with TimeLock decryption. From there we can limit the blocks of assembly code to review to find out how to defeat the mechanism.
Head to the strings view, and then use xref to jump to “Incorrect answers”. This is what we find:
Follow the red line upwards, and double click it to be taken to the conditional jump.
Right. Now we know that the TimeLock decryption mechanism is between these two points. What we need to do now is search around for some clues. There is already a clue above that we are around the right place, with the string “No connection to the BTC Network” appearing. The program will be getting the time somewhere. We just need to find it.
After looking at the assembly in between, it really did not make a lot of sense, so we will need to examine what the program is doing during runtime.
Open up x64dbg, and lets start debugging the program. Load in TimeLock.exe and press run.
Now, if you are following along, you will need to rebase the addresses in IDA to what is shown on the debugger. Look at where the entry functions are at the first breakpoint, and use that address for the new base address in IDA. Go to Edit > Segments > Rebase program… and enter in the new address.
Note, I had already done this, since I am pretty deep in analysis and I really do not want to start from scratch.
Now, we need to work out how this program verifies decryption. The easiest way to do this is to analyse the final decryption stage, where we have a file that we can open. We are mostly interested in the code block before we jump to “Incorrect answer(s) or current time is not within the TimeLock window.”
In x64dbg, set a breakpoint at 00007FF6D681CC63, at the mov [rsp+11380h+var_11348],1
instruction, and also at 00007FF6D681CC94 at the mov eax, esi
instruction. This will stop us from falling too far if we press resume by accident.
Press resume in x64dbg if you haven’t already, and let TimeLock sync to the Bitcoin network. Once that has done, press “Open a LockBox”. We will be opening secret2.x we made just before.
Enter in the password “TimeLock” and “0.02” for the answer. x64dbg should eventually bring you to a breakpoint we set after some decryption rounds.
Step through the instructions with the F7 key, and stop just before the call instruction. Lets examine the registers.
RBX and RCX are set to 000001C12E04DEB0, which is an address on the heap. Double click the register and copy the address. Head to the bottom window where it says “Dump” and click, then press ctrl-g.
Paste in the address and click OK.
I have a hunch this is the encrypted data file, stored on the heap. We can verify this by stepping over the call sub_7FF6D68183A0
instruction by pressing F8, and looking at the same address on the heap.
We now see 64 zeroes, followed by the data file that we encrypted earlier. Ah, 0x41 what a calming number. Anyway. We now have found where the decryption takes place. Now we just need to find out how the time is involved.
As a side note, it seems the program verifies that decryption was successful by examining the decrypted file and making sure there are 64 zeroes prepended. We can see the verification take place here:
The program loops by using RAX as a counter, and compares it to 0x40 == 64, which makes a lot of sense.
Now, to further limit the assembly we need to read, we can look upwards slightly and find where the encrypted data file is validated after the answers were entered.
We end up with this gnarly loop:
Now we know that getting the time, and using it for decryption must happen between the two verification loops. This limits the code quite a lot.
Now, just under the verification loop for the answer decryption, we find some logic:
What is especially interesting is the cmp eax, 258h
instruction. 0x258 == 600, and we also know that TimeLock does not like it if your computer’s clock is off by more than 10 minutes. 10 * 60 seconds = 600. We can confirm this by looking at the string, “Clock on this computer is off by more than ten minutes”.
Let’s put a breakpoint at 00007FF6D81CB03, and see what data is being examined. Maybe this is an opportunity to overwrite the time to the future.
Exploitation
So we need to be a little more clear about what we are looking for. We want an opportunity to set the clock to the future. We do this by entering in a Unix timestamp that represents the future.
We know the two times that the data is available between: 22/02/2019 00:00 UTC and 23/02/2019 00:00 UTC.
We can use a website such as epochconverter.com to change the dates to timestamps:
- 22/02/2019 00:00 UTC becomes 1550793600. Hex: 0x5C6F3B80
- 23/02/2019 00:00 UTC becomes 1550880000. Hex: 0x5c708D00
We just need to keep a look out for something that resembles these numbers, but slightly different since it will be the current BTC network time.
This time, open up Challenge2.x and we will break open the lockbox.
Enter in the password “TimeLock” and the answer to the question, “0.02”. We should hit our breakpoint at 00007FF6D81CB03.
Step through to cmp eax, 258
and look at the registers.
What on earth is going on here? RAX is 0! Looks like we do not have a current version of the time. Instead, it probably means the difference between BTC network time and our computer’s clock is 0. Not quite what we want.
Let’s follow through the execution one instruction at a time, by pressing F7 to step, and F8 to step over function calls.
Now, something very, very special happens between 00007FF6D681CB63 and 00007FF6D681CB6A. Lets have a look:
These two instructions load two separate addresses on the stack and add them together, storing them in RAX. But what is stored?
0x5C4E2C15 is stored in RAX! That number feels familiar. It is very close to the hex Unix timestamps we found before. Converting this to current time, we get a Unix timestamp of 1548626965, which comes out to be 28/01/19 11:09:25, which is right now!
Looks like we have found our winner!
Lets prepare a new timestamp. It must be between the two times mentioned, so I will go with 22/02/19 07:35:00 which comes to 1550820900, which in hexadecimal is 0x5C6FA624.
Lets replace RAX with 0x5C6FA624, by double clicking the register and entering the number in:
And after:
Press resume, and let x64dbg hit the next breakpoint.
Back to where we were before. Step through the instructions like last time, and get the pointer to the encrypted data file on the heap, and view it. The pointer is in RBX and RCX.
The heap before the call. Press F8 to step over the decryption function:
My my my! Isn’t this exciting! Now is the time you start to get hyped… So press resume in your debugger and keep pressing resume. Let’s get this file safely extracted.
Yes, I would very much like to save this file.
Yes, come on, let’s see some private key action…
YES YES YES! That did it! Yes! I have successfully completed Challenge2.x.
Now, this took me quite a long time to find. I kept getting sidetracked and stuck on things that did not even matter. I must have spent like 5+ hours just analysing the decryption function alone, because I was adamant that I would find something in there. In the end, it was in a completely different place, slightly above.
I spent a total of about 20 hours working on this, with about 45 mins to an hour on doing the above. This really should have taken me less time, but I am still learning reverse engineering.
Loot
The private key for the challenge reward, 3NuEijXKmnRUeri9DfvDZ2f5RDkHLUBgNS is p2wpkh-p2sh:KyYvEbvjFFGyHGQcRHYNvASQAMmNMtgcqHsgmoLTW62iY4rimcUV
I quickly imported the key into Electrum and sent the funds into a wallet I control. Thank you very much to u/cryptocomicon for this interesting bug bounty, and for the funds.
I hope this writeup is sufficiently detailed in order to qualify for the additional 0.02 BTC reward.
This can be payable to the address which I swept the other BTC into: 19KuusbvKQrGLDTCGChysGe1fYTkUznXYf
Notification of Vulnerability
I notified the author of TimeLock by again posting to the Reddit thread:
And the reply:
Here’s to version 1.3! I will be interested in testing the security when it becomes available.
How To Fix The Vulnerability
Again, the most important part.
So, there really is not a lot that you can do to stop an attacker from finding what the current time is, and changing the result to a different value by modifying the values in a debugger.
Things you can do:
Remove Debugging Information
You can make reverse engineering much more difficult by removing debugging information. Leaving strings in the binary that are not meant for users, but are meant for debugging can give attackers the same amount of information that you have. What I recommend is something like this:
#define DEBUG 1
void error() {
printf("Something went wrong");
}
void someFunction() {
void* somePointer = malloc(someSize);
if (!somePointer) {
#ifdef DEBUG
printf("Problem #4 reading LockBox file:\n");
printf("malloc failed");
#else
error();
#endif
}
}
Now, when you are developing the program, make a #define that you can use #ifdef on, that is meant for debugging.
You can still have all of your debugging output when you are developing, just place it within the debugging enabled section.
But when it is time for release, try setting the #define DEBUG to 0, or commenting the #define out. This will unset the #define, and when you build, none of the debugging strings will be included in the binary, which will leave me in the dark. All the attacker will see is a call to the same function, “Something went wrong”. User’s will not see any difference, since an error is an error to them.
You Need a Safer Mechanism Of Loading Timestamps
The major vulnerability in TimeLock v1.2 is that the place where the timestamp is loaded in can be easily found, since it is near the decryption code, and is only relied on in one place.
If you can load in the time at various places in the program, and do checks more often, it means more work for an attacker, since they have to track down all instances of reading the time.
Implement Anti-Tampering and Anti-Debugging Measures
There is plenty of ways to implement anti-tampering and anti-debugging measures. They can still be defeated with a lot of effort, but the idea is to make the cost of attacking higher, and make it take more effort to break.
Implementing a Trusted Third Party
I still think that implementing a trusted third party is the solution, much to what I explained in the previous post. I know that you want a standalone program, but this is the real, unbreakable solution.
Read this stackoverflow for more information about trusted third parties.
If you wish to contact me, email me. See my about page.
Good luck.
Matthew Ruffell.