Unleashing a Sybil Attack Against TimeLock 1.3 Vulnerability Writeup

Here we are, back again for my third bug bounty! It really is a good time trying to break an applications security, and especially so when there is some Bitcoin waiting as a reward.

As always, I was on Reddit and saw that u/cryptocomicon has made some changes to TimeLock, and is ready for them to be tested again.

reddit

u/cryptocomicon has acknowledged that writing secure software is extremely hard, and is absolutely correct in that statement. We also see that a new challenge is issued:

Designing an un-hackable TimeLock is challenging. This is my third version and the third challenge, with a 0.02 BTC reward.

Please give it a try.

Will do. Challenge accepted.

Reconnaissance

If you aren’t familiar with TimeLock and what it is, or my previous vulnerability disclosures, you should probably read my previous posts:

Right. Let’s go to the download page and fetch the updated version:

download page

I have mirrored the files if you want to follow along:

TimeLock_v1_3.msi SHA256 5a5172c1f48b26dcaeb58c5f68a298487db8f3c0d2a7c783c61eb10afa04f3de Challenge3.x SHA256 e8cd79d7738624047bf4f96a73cfbefac28a5eebf834cb4fdbc28855705c8ad2

The facts we know are similar to previous challenges:

  • The password to the LockBox is TimeLock
  • The answer to the question is 0.02
  • The earliest time it is available is 09/03/2019 00:00 UTC
  • The time of revocation is 10/03/2019 00:00 UTC

The scope is the same. Knowing the above information, attempt to defeat the TimeLock mechanism.

Analysis

The program has now changed, and no longer connects to the Bitcoin network at start-up. The button to “Open a LockBox” is now available immediately.

control panel

If we click this button, and enter the password and answer the question, the following window is spawned:

connect to node

This means that the peers are only connected to when required.

Dumping the strings of the binary produces a horrible sight - all the strings appear to have been removed. It looks like strings are stored encrypted and decrypted right before they will be used. It means advanced static analysis will be difficult.

Sybil Attack Planning

The plan for today is to execute a Sybil attack. A Sybil attack is where you introduce malicious nodes into a network who masquerade as legitimate nodes, and then get the program in question to trust your malicious nodes.

In this case, I will set up my own Bitcoin full nodes with their clock set to the future, and I will get TimeLock to connect to and trust those nodes. TimeLock will then use the Bitcoin network time, which is set by me, to a value in the future.

The Sybil attack will only work if ALL nodes are malicious nodes, since Bitcoin full nodes peer with each other, exchanging information such as the time. So I need an environment where I can control all the nodes that TimeLock is connecting to.

Determining How TimeLock Connects to the Network

The first thing to do is work out what TimeLock does to find the IP addresses of Bitcoin full nodes. Let’s open up Wireshark and start listening. Set the filter to “DNS” and have a poke around.

We find a request to dnsseed.bluematt.me:

dnsseed request

If we look at the reply, we get a list of IP addresses:

dns reply

Looking up what a “seed node” is, we find that they are used to bootstrap connections to the Bitcoin network. These nodes are custom DNS servers which serve out a long list of randomly chosen full nodes which are alive.

I have determined that TimeLock connects to the following DNS seed nodes:

  • seed.bitcoin.spia.be
  • bitseed.xf2.org
  • dnsseed.bitcoin.dashjr.org
  • dnsseed.bluematt.org
  • missionctrl.info

If we are able to get these DNS records to point back to a Bitcoin full node we control, we will be able to influence TimeLock.

Setting Up DNS Redirection

We need to control DNS. To do this, we will set up our own local DNS server, and tell Windows to use it.

I found a cool Python script called fakedns. It implements a ultra basic DNS server that you can hack on, and is exactly what we need.

Download Python3 for your Windows box and run the script. I just ran it in IDLE, nothing fancy here:

basedns

I changed the IP address to point to 127.0.0.1, since we want to serve out addresses for the local machine.

Next we need to tell Windows to change its default DNS server.

Right click the network indicator > Open Network and Connection Settings > Change Adapter Options > Ethernet Adapter > Properties > IPV4

You should get to this window:

dns settings

Change the adapter to use 127.0.0.1 and 127.0.0.2 as the DNS server, so it connects to the Python script.

If we run nslookup, we now get redirected to 127.0.0.1:

nslookup 1

Great. But it’s not exactly what we want. We need to return a list of random seed nodes to fool TimeLock. We need to make some changes to the Python script.

If we look at RFC 1035, we see that for DNS, we get multiple addresses for each question by including more than one answer RR per packet:

rfc1035

If we tweak the script to add another for loop when we generate answer records, and randomise the IP addresses as we go, we get something a little like this:

changed

I have attached the final Python script if anyone wants it.

If we query with nslookup, we see:

nslookup

Just like a Bitcoin DNS seed node. Excellent.

Getting Bitcoin Core Running

Now we need to set up our malicious Bitcoin full nodes. Download Bitcoin Core and install it. The latest will do.

Before we run it, make sure to disconnect your computer from the internet. This is easy, since I’m working in a virtual machine.

We also need to change the time. Right click the time on the toolbar and click “Adjust Date/time”

Turn off automatic time sync and set the time to 9th March 2019, at some time during the day, such that it is after 00:00 UTC.

time

Time to start Bitcoind, the daemon behind all full nodes. Open a command prompt and enter: C:\Program Files\Bitcoin\daemon\bitcoind.exe.

The node will start-up.

node

bitcoind will bind to port 0.0.0.0:8333, so any loopback address in 127.x.x.x:8333 will automatically connect to our now malicious node. If you look at the timestamp on the left side of the picture, our node believes the time is 2019-03-09, in the future.

First TimeLock Attempt

Okay, head back to TimeLock and attempt to open Challenge3.x. We quickly find that TimeLock will happily make DNS requests to those five DNS seed nodes we saw previously, and connect back to our node, but it won’t make any further connections.

timelock attempt

How annoying. Time to fix that.

Exploitation

Load up TimeLock.exe into IDA Pro, and open it up in x64dbg. Look at the differences in address schemes, and rebase the program in IDA. My offsets this time around are 7FF613460000, so I rebased the program by that amount with Edit > Segments > Rebase Program…

We need to work out how these DNS seeds are used. We first try a quick string search:

search

We got lucky. These strings aren’t encrypted.

Double click the string and take the xref to the function it is used in:

xref

We end up at loc_7FF613464D98, and it is probably safe to assume from the string that this function is called AddSeedNode().

loc

Head up the top and rename the function, and have a look at the xref’s it provides:

AddSeedNode

AddSeedNode() is called in exactly five places. Interesting, seeing as we were only getting five connections to our malicious Bitcoin node.

calls

And there we have it. Five calls to AddSeedNode() in a row. I suppose that mov ecx, 5Dh instruction followed by a call sub_7FF613476C30 is where the string decryption happens.

My theory is that calls to AddSeedNode() is getting us connections to our Bitcoin network. If I patch one of the calls out with a jmp instruction, then TimeLock only connects to four nodes.

What we want to do is somehow get all 8 connections via these calls. Patching in new assembly code is out of the question - I attempted to insert three new blocks of code, but the alignment was all wrong and IDA would not let me assemble long function calls into small spaces.

So, instead, what we will do is attempt to execute these five sections twice, and we need a debugger to achieve this. Move over to x64dbg.

x64dbg

Place a breakpoint at the first block of code that references AddSeedNode(), which is 00007FF6134778D1.

close

Start the execution of TimeLock, and open up Challenge3.x, clicking resume on the debugger whenever you get trapped, until you hit the breakpoint we set. The password is TimeLock and the answer to the question is 0.02

Step over the first two instructions. We find a familiar sight - the string missionctrl.info

addresses

Step over all five blocks of code, but stop when you come to the final jne instruction:

end first block

What we want to do, is change the instruction pointer from the jnz short loc_7FF613477A06 instruction, back to the top of loc_7FF61347794E. This means we can re-execute this block twice, and we will be able to add 9 DNS seeds instead of 5. This is more than the required amount of 8.

ida

In the debugger, change RIP to 0007FF61347794E:

edit rip

Now we are back at the top of the second block of code. What we want to do this time, is change the strings of domain names passed to AddSeedNode(). AddSeedNode() rejects double ups, so each new strings must be unique. Decrypt the first string, and we see that RAX has a pointer to the heap where the string is stored:

after RIP

Use the address in RAX, and press ctrl-g in the bottom heap box, and paste the address in. We find the string in the heap:

heap

Modify the string such that it is different, by double clicking the characters and changing them:

change

And then keep stepping over the functions in the debugger. Each time a string is decrypted, modify it.

When you reach the final jne instruction that we stopped at last time, press resume on the debugger.

TimeLock will then go and connect to one of the IP addresses from each of the DNS seed nodes. Since we control DNS and redirect every single request back to localhost, TimeLock will talk to our malicious Bitcoin node.

The malicious Bitcoin node has the time set to the future, and passes all integrity checks, because it is a real, unmodified Bitcoind daemon.

Head back to TimeLock and watch the magic happen.

solved

This sure looks promising…

solved2

Yes, yes I do want to reveal the file…

solved3

YES YES YES! Oh yeah! YES! Completing challenges never gets old. What a feeling.

Thank you very much u/cryptocomicon for the challenge!

This took me 2.5 days to get here. I tried all sorts of things, but this is the method that worked. TimeLock is starting to get nice and hardened now.

Loot

The private key for the challenge reward, 34r4PbKUM2odwf1EV2Jnxx9d3k1rWKgAzD is p2wpkh-p2sh:Kx4TLBeaMLG19wkeocVX6YG63BTWErKvnTvnPfVgvXf5tD1U1Mij

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

reddit

And the reply:

reddit

As always, I will be interested in having a look at the next version.

How To Fix The Vulnerability

I successfully executed a Sybil attack against TimeLock. A Sybil attack is really as bad as it can get, when you think about it:

  • Every Bitcoin network node is attacker controlled
  • Every Bitcoin network node has it time set to the future
  • Internet access is disabled and no communication with legitimate nodes is possible
  • The attacker controlled Bitcoin nodes look exactly like real nodes

This makes defending against this attack very, very hard. The attacker literally controls everything.

Here is what I suggest:

Do More Validation Against Bitcoin Nodes

I managed to trick TimeLock into accepting a single bitcoind node, as 8 or more nodes. This is a problem.

First things first, you should probably make an attempt to block / ban connections to loopback addresses. That includes 0.0.0.0/24, 127.0.0.1/24, 10.0.0.1/24 and so on. Then an attacker would be forced to find these checks and have to remove them.

Next up, is that you may want to consider making the peering rules a little more concrete. Set up a few connections via seed nodes, five is probably okay. But the remaining connections MUST come from asking nodes for peers. I shouldn’t be able to just add 9 seed nodes and be done with it. Make fetching additional peers mandatory. I would then have to run at least 8 nodes, which is annoying, since I would need 8 virtual machines for that. Attacks get more expensive and difficult.

Try to implement a mechanism to ensure that all the nodes are different. Maybe they have a slight variation in timestamp. On a Bitcoind node hosted locally, there will not be enough latency to have a different returned timestamp in the version command. Over a internet connection there will be. Perhaps each node might have a slightly different view of the mempool.

Attempt to see if there is a live internet connection. Close if there is not. It is very hard to stop bitcoind nodes from connecting to the outside world if the computer has a network connection. If it does, then bitcoind will soon realise it has the wrong time, by talking to other nodes. Even if you control DNS, bitcoind still has fallback hard-coded IP addresses to connect to, and it will use these to connect to other nodes.

I think its worth trying to link block height to the current time. In this case, the hard coded block height in Bitcoin 0.17.1 is from a few weeks ago, and it is the most recent block the node knows about. If a new block is generated every ten minutes-ish, you can at least ballpark predict what the block height is for the current time. If a node says the time is in the future, but when you query the node, the block height is not very high, it can indicate that the node is lying about what the time is.

Continue to Remove Strings

Not all strings are encrypted yet. I recommend going through the rest of them and encrypting them. It managed to give me a leg up in solving this challenge.

Sybil attacks are tricky things to defend against. But if you can defend against one, then you can defend against most attacks. I will keep thinking about defences as well.

If you have any questions, email me. See my about page.

Thanks for reading!

Matthew Ruffell