by icemonster and majo
Points: 94 (Dynamic, 56 solves)
You’ve earned a lot of cash from investing bitcoins! Why not try our coolest multi-signature bank?
nc tcp.realworldctf.com 20014
Check that to get the flag we must sign WITHDRAW in a multisignature together with the bank.
The multisignature is done by computing an aggregated key from the composition of the two keys using the binary group operation.
Use a so-called rogue-key attack to forge a signature that looks like it was signed by both users.
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
Now, are going to forge this multisignature. We start by asking for option
3, which will provide us the public key of the
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)
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 !!
Exploit in solve.py.