TimeLock V3.2 Challenge — Vulnerability Report

pogo
pogo
Jan 6 · 6 min read

TimeLock (https://www.algomachines.com/) is a freeware program being developed in order to facilitate secure temporary locking of files up to 10KB in size, with the unlocking being possible only during a time window specified by the user during initial locking.

In it’s current iteration (V3.2) locking requires the user to provide an input file, a password and the start and end date during which time the lockbox can be open. The time check is based on the bitcoin blockchain, as the program connects to several bitcoin nodes and queries the header files for recent blocks.

The TimeLock Challenges have been launched by the software creator in order to test the time check part of the encryption algorithm for vulnerabilities. This means that a lockbox is supplied, together with the password, and the goal is to open it before the start time that it has been designed for.

Challenge V3.2

This challenge was the 10th to be launched (you can view all of them here, together with reports for the cracked challenges: https://www.algomachines.com/people).

The current iteration is identical to the previous version, with the notable difference that there’s a new feature defending the “magic_decode_function” (a misnomer). The bug with not being able to choose the start/end times on created lockboxes was also fixed. Yay!

Since the current version is almost identical to the previous one, I will reference this report several times: https://medium.com/@elronvhubbard/timelock-v3-1-challenge-vulnerability-report-2286a14f1f1f

The addresses referenced in this report are Virtual Addresses (VA), rebased at 0x0.

Anti-debugging

Let’s start with anti-debugging, because it’s quick: nothing has changed since the previous build, which means that the two file patches that worked previously will also get the job done here (see previous report). Exact patching locations are at File Offsets 23DDB8 (4 NOPs) and 4DBB1 (2 NOPs).

Analysis

The first thing I wanted to look at after opening the new version in IDA was the big decryption function which was at VA (virtual address) 44F40 in the previous build. Quick note: I use a neat little IDA plugin called Sigmaker in order to quickly create and search for patterns between different binary files. Using the plugin, I found the decryption function in the 3.2 binary at VA 4F8E0. Now, if we compare it with 3.1, it looks pretty much identical. But (see previous report) what we’re interested in is the function that compares the blockchain time to the lockbox time, which is located at VA 69A70 (3.1) and VA 75820 (3.2). This is where the new version’s changes went!

Let me show you a very cropped up version of the function (except for the “Decode_string” function, all the other functions are already named since they were exported)

The important bits of the “magic_decode_function”

So it seems like there’s a runtime interpreter session being created right at the start, a (whole bunch!) of strings get decoded and fed into a buffer, which all end up being run, and the result is recovered and returned.

All right that’s good enough for the static analysis part, let’s go debugging and see what those decoded strings are all about.

We can start by setting a breakpoint at VA 75820 in your favorite debugger (mine’s x64dbg) and trying to unlock any lockbox. Once the breakpoint is hit, stepping over functions (a lot) will reveal intelligible text in the RAX register from time to time, namely, after every call of the Decode_string function. Here’s an example:

Now, with a little more playing and stepping over we can make out the big picture: an entire function is being decrypted line by line and being fed into a buffer, which is then sent to the interpreter and run! And here’s the entire script that runs in the interpreter (retrieved line by line):

uint64 t0,t1,t2,t3,t4,TLc_e;
GetT(t0,t1,t2,t3,t4);
TLc_e = t0 << 42;
TLc_e += t1 << 21;
TLc_e += t2;
SE64VS(TLc_e, t3);
uint64 start = TLc_e & ((1 << 26) — 1);
uint64 duration = (TLc_e >> 26) & ((1 << 20) — 1);
uint64 c64_0;
GetPeerC(c64_0);
barray blk_hdr;
GetPeerBlockHeader(blk_hdr);
double C;
int i=0;
while (i < 4)
{
double a = blk_hdr[68 + i] << (8 * i);
C += a / 60;
i += 1;
}
C -= t4;
C /= duration;
double Ctarget = start;
Ctarget /= duration;
double diff = C — Ctarget;
uint64 c_64;
if (diff < 0)
{
c_64 = 1 — diff;
}
if (diff > 0)
{
c_64 = diff;
}
uint64 seed = t3 + c_64;
c_64 += t3 * t3;
uint64 c_ret;
Hash(c_64, seed, c_ret);

Wow! We now have the source code for the juicy bit! We can even go ahead and compare this source code to the IDA decompiled function from V3.1 (see previous report), and we’ll see that they’re very closely related.

Opening the lockbox

So… we can see the entire script that gets passed into the buffer and then run. Nice. What’s more, since we can stop and debug wherever we want, we can also control any part of the script. So let’s do just that. There’s obviously a million ways to get creative and mess around if you want to, but here’s what ended up opening the lockbox:

Clear the previous breakpoint (we don’t need it now that we understand the code) and set a breakpoint at VA 75FD1. Start unlocking the challenge lockbox, and once the breakpoint hits, the RAX register should be holding a pointer to the string “double diff = C — Ctarget;”. Now, from the V3.1 challenge, we know that the “magic” interval was 0–0.5, though not if it was positive or negative. Let’s just go with positive.

Follow the RAX pointer in memory and overwrite the string with another that says “double diff = 0.25;” and don’t forget to end the string with a \x00. In other words, we could overwrite with:

64 6F 75 62 6C 65 20 64 69 66 66 20 3D 20 30 2E 32 35 3B 00

which is just the hex representation of the above and a 00 terminator.

Before and after of the RAX register

Okay, now, same as in V3.1, this decryption function gets called 3 times, so let continue execution and we’ll end up at the same spot. The same process can be followed, we just have to remember that RAX has changed, meaning that we need to look at a different spot in memory. We overwrite using the same byte sequence. And then one more time. Boom. The interpreter compiles and runs the script and is oblivious to any changes, but the lockbox gets opened anyway.

Acknowledgement

This interpreter is a very cool and powerful feature, but it needs to be further helped by code obfuscation and some other tricks. Tweaks in this area might concievably make the whole code very hard to control from a debugger. I still believe that further post-build obfuscation (using some open-source/free obfuscators) would bring extra security to the table.

I would like to thank the author for this extremely interesting challenge. I didn’t expect an interpreter and it was really fun to mess with. Hopefully this report will help in making TimeLock secure and unbreakable, and, of course, I’ll be eagerly waiting for future challenges.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade