Skip to content

Instantly share code, notes, and snippets.

@lightdiscord
Last active July 7, 2021 19:38
Show Gist options
  • Save lightdiscord/d8b087c469b65f67f0762bfee2d48320 to your computer and use it in GitHub Desktop.
Save lightdiscord/d8b087c469b65f67f0762bfee2d48320 to your computer and use it in GitHub Desktop.
Write-up for the Bitcoinminer-{med,hard} challenge available during the CYBERTF

Bitcoinminer-{med,hard}

The method is the same for both challenges.

Identification

When we access the challenge we get two cookies key and password.

  • The password seems random and does not correspond to any known hash format.
  • The key is two integer separated with an @.

We've a form asking for a valid bitcoin address and a password, giving the password inside the password cookie does not work.

There is a page under /algo which gives information about an algorithm being used, we see the code of a xor function and a flowchart.

The flowchart describe the process of transforming cleartext into an output.

key = [number_one, number_two]

output = cleartext
output = xor(output, key[0])
output = xor(output, key[1])
output = output[::-1]

The xor function is the annoying part of the challenge. It takes an alphabet, map each letter of the first argument to an index in the alphabet then map it to its binary representation and xor each bit with each bit of the key. The problem is we can loose information such as leading zeroes. This means multiple character can map to the same one.

Fortunately, each character is individual and always map to the same character. This means if we use xor on the alphabet with the key we know we can create a mapping and find the antecedant of each character of the password.

Unfortunately each character of the password can have multiple antecedant and if we want to try all password we need to keep the number of possibilities as low as possible.

Finding a good combo

The first task will be to ask the server for a key and a password that generate a mapping with the lowest number of possibilities.

def check(password, key_one, key_two):
  mapped_alphabet = xor(xor(alphabet, key_one), key_two)

  # indices is a function that returns all the index of a value in an array.
  return [[alphabet[i] for i in indices(mapped_alphabet, c)] for c in password[::-1]]

r = requests.get(ADDRESS)

# We get the informations we want from the request
key = [int(x) for x in r.cookies["key"].replace('"', "").split('@')]
password = r.cookies["password"].replace('"', "")

c = check(password, key[0], key[1])

total = 1
for value in c:
  total *= len(value)

We can see total to know the number of possibility, we can put this in a loop and wait to see a total < 2000.

Finding the correct password

Now that we've a good combo, all we need is to generate all the password possible and complete the website's form with a valid bitcoin address and our guess.

All these passwords are valid but the real password is stored inside a database and that's why we need to test each one.

If we test something but the password is not in their database we get an error otherwise we get a flag.

# c is from the step before.
possibilities = itertools.product(*c)

for index, guess in enumerate(possibilities):
  r = requests.post("http://144.217.73.235:8260/", data={
    # Not important, we can use a site like bitaddress.org to generate one.
    'wallet_key': "1Eite2Q6WqEgQiqx8QpgbyTHfsnKPht5n6",
    'password':''.join(guess)
    }, cookies={
    # key and password from the step before
    'key': '@'.join([str(x) for x in key]),
    'password': password
  })

  cond = "Password not in the db" not in r.text
  print(cond, index, guess)
  if cond:
    print(r.text)
    break
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment