nullcon HackIM Crypto 1 writeup

This weekend was nullcon HackIM CTF and I wanted to post my writeup for the Crypto 1 challenge, because I found it particularly interesting.

Although crypto challenges aren't usually my favorite category, I often find them to be quite interesting and a good way to learn something new.
This particular challenge caught my attention, because it was about bitcoins:

nullcon Crypto 1 writeup

From looking at the picture we learn that:

  • It has something to do with BrainWallets
  • We have a public bitcoin address in the QRcode
  • We have to find the correct private key
  • We have some parts of the password
  • It has a hint which does not make sense yet

The first step I did was to extract the QRcode from the screenshot and use an online QRcode reader to the get public key:

The public address' QRcode
The resuling bitcoin address is:

17iUnGoZbFrGS7uU9z2d2yRT9BKgVqnKnn

But then I was confused how the partial password should reveal the private key or if we're supposed to brute-force the private key for the given public key. The latter, however, wouldn't make sense, because otherwise bitcoin would be broken.

After a bit of googling we learn that a brainwallet's private key is the sha256 of the password:

private_key = sha256(password)

That means we only have to find the correct password (the letters behind the red bar) to restore the private key and solve the challenge!

I've found a repo on GitHub which automatically brute-forces brainwallets and looks if there are unspent bitcoins on them. Make sure to have a long long long unique brainwallet password otherwise your bitcoins might get stolen!

For my solution I've adapted their Wallet-class because it takes care of the password-to-publickey computation and the final idea was to generate possible password combinations until the resulting public key matches our bitcoin address.

This is the point where the hint became helpful.
I first started the brute-force with combinations of length 1-7 with a charset made of all characters of scrambled egg and nullcon8itsgr8.

After a while my teammate Alex suggested to only use the characters from nullcon8itsgr8 and a guessed length of 8 characters. It turns out that he was right...

Here's the final script:

#!/usr/bin/python2

from coinkit import BitcoinKeypair
import logging
import itertools

PASSWORD = "8ln{{X}}nl8"
TARGETADDR = "17iUnGoZbFrGS7uU9z2d2yRT9BKgVqnKnn"

charset = []
for hint in ["nullcon8itsgr8"]: #,"scrambled egg"]:
    for c in hint:
        if not c in charset:
            charset.append(c)

#Those characters probably won't be in the password again
charset.remove("n")
charset.remove("l")
charset.remove("8")

# Source: https://github.com/dan-v/bruteforce-bitcoin-brainwallet/blob/master/lib/wallet.py
class Wallet:
    def __init__(self, passphrase, is_private_key = False):
        self.passphrase = passphrase
        self.address = None
        self.public_key = None
        self.private_key = None
        try:
            if is_private_key:
                keypair = BitcoinKeypair.from_private_key(self.passphrase.encode('ascii'))
            else:
                keypair = BitcoinKeypair.from_passphrase(self.passphrase)
            self.address = keypair.address()
            self.public_key = keypair.public_key()
            self.private_key = keypair.private_key()
        except Exception as e:
            logging.warning(u"Failed to generate keypair for passphrase '{}'. Error: {}".format(passphrase, e.args))
            raise

print "Charset: {}".format(charset)
for l in range(8,9):
    print "Testing...length {}".format(l)
    for comb in itertools.permutations(charset, l):
        pw = PASSWORD.replace("{{X}}", ''.join(comb))
        wallet = Wallet(pw)
        if wallet.address == TARGETADDR:
            print pw
            print wallet.address
            print wallet.public_key
            print wallet.private_key
            break

After a couple of minutes we get a result:

$> python2 solver.py 
Charset: ['u', 'c', 'o', 'i', 't', 's', 'g', 'r']
Testing...length 8
8lnustorcginl8
17iUnGoZbFrGS7uU9z2d2yRT9BKgVqnKnn
047663d087dd1da8315644e0800b7651e0763b5fbf9a2388834db0bbb282d5a1761fd8c993dd3ea7fa5cdb616b591fa391dc00bafce7e70feb1a7002a10e9ca152
fda5ca43c8573c06bc0f829f8cc5e4c3667c11388a87fa532d2669135866b2c4

With a bit of googling I found the brainwallet website used in the screenshot: https://brainwalletx.github.io/

Entering the password reveals the WIF-encoded private key: 5KjzfnM4afWU8fJeUgGnxKbtG5FHtr6Suc41juGMUmQKC7WYzEG

Final brainwallet

Flag: flag{5KjzfnM4afWU8fJeUgGnxKbtG5FHtr6Suc41juGMUmQKC7WYzEG}

This was quite cool and I most certainly know now why I'm not using a brainwallet :)

-=-