The method is the same for both challenges.
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.
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
.
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