bank

by goulov and ice

Points: 94 (Dynamic, 56 solves)

Description:

You’ve earned a lot of cash from investing bitcoins! Why not try our coolest multi-signature bank?

nc tcp.realworldctf.com 20014

Given: bank.zip

Solution:

TLDR

  1. Check that to get the flag we must sign WITHDRAW in a multisignature together with the bank.

  2. The multisignature is done by computing an aggregated key from the composition of the two keys using the binary group operation.

  3. Use a so-called rogue-key attack to forge a signature that looks like it was signed by both users.

Attack

Description

The first thing we must do is to provide our public key to the server. Here we have a Schnorr signature scheme implemented using Elliptic Curves, so this is a curve point. At this step this does not really matter and we provide a random point just to make the program continue.

Then we are greeted by the following menu:

1. Deposit a coin into your account, you can sign a message 'DEPOSIT' and send us the signature.

2. Withdraw a coin from your account, you need to provide us a message 'WITHDRAW' signed by both of you and our RESPECTED BANK MANAGER.

3. Find one of our customer support representative to assist you.

From reading the code from multi-schnorr.py we can see that in order to get the flag, we must make a WITHDRAW, and for this we must sign a message in a multi-signature together with the bank.

Now, are going to forge this multisignature. We start by asking for option 3, which will provide us the public key of the bank.

At this instant, the most crucial detail takes place… We are asked again for our public key. The point is, since we now know the bank’s public key, we can choose our new key depending on the key of the bank. This is what’s called a rogue-key attack.

The way we forge our new key public key is as follows (- is the inverse of the point in the curve):

fake_key = our_pk - bank_pk

Since the multisignature implemented here combines the public key we provide (aka fake_key) with the bank’s public key, composing both using the group law, we get:

Verify(fake_key + bank_pk, sig, M) = Verify((our_pk - bank_pk) + bank_pk, sig, M) = Verify(our_pk, sig, M)

Execution

Finally, we execute the attack by setting our public key as our_pk, and making a DEPOSIT of a coin by sending Sig(our_sk, 'DEPOSIT'), which will increment the balance of our account. Our balance must be non-zero in order to call the WITHDRAW command.

Then, when asked again, set our public key as fake_key and send the signature of WITHDRAW computed as sig = Sig(our_sk, 'WITHDRAW').

As shown above, this will make the bank_pk cancel, and we are left with a signature verification that only uses our_pk to verify sig. Since we know the matching our_sk, we use exacly this secret key to compute sig, which we send to the server… And we are given the flag !!

rwctf{P1Ain_SChNorr_n33Ds_m0re_5ecur1ty!}

Resources:

Exploit in solve.py.