Exercise 2: CTF#

CTF Flags: UUlm Capture the Flag Dashboard

Blocksec CTF

Containing 3 Challenges

Requirements#

Prog. Lang.:

  • Python 3

Python libraries:

  • blockcypher

  • binascii

  • mnemonic

  • bip-utils

  • hashlib

  • ecdsa

Tools:


CTF 1. Unlocking a bitcoin transaction — 100#

We are given the Bitcoin testnet transaction hash: 23c9470a2269cf6430569afdd3e2e8f35f633b6cdbc34107ddbab932c4146ba3

  • Goal: We want to know how to spent this tBTC output.

  • Hint: The unlocking script (scriptSig) can be find in 8 Byte of HEX and can be converted to Cleartext ASCII.

Hints:

  1. Find the SCRIPTPUBKEY that locks the output value

  2. Understand how the input must look like, to unlock this locking script.

  3. Find a way and search for hints to unlock the transaction with the (scriptSig)

"""
Helpful functions for the challenge.
# Alternative https://www.rapidtables.com/convert/number/hex-to-ascii.html
"""
import binascii

hex_string = "68656C6C6F776F726C64"
decoded_string = binascii.unhexlify(hex_string).decode('utf-8')
print(decoded_string)
helloworld

CTF 2. Dark Net Money — 200#

You are given the following mnemonic phrase:

play fever bullet unlock error palm insect pottery tower torch memory liquid

Your task is to find how much tBTC this wallet holds unspent.

Hint: The correct derivation path follows the classic BIP44 scheme. You need to find the receiving address of the first account at a specific index.

Use the mnemonic phrase on a well-known BIP39 tool.

This could be helpful:

  1. You need to select Bitcoin Testnet as the coin type.

  2. The derivation path is m/44’/1’/0’/0/137. Documentation.

For the developers:

from mnemonic import Mnemonic
from bip_utils import Bip39SeedGenerator, Bip44, Bip44Coins, Bip44Changes

Alternatively for the tool users:


CTF 3. ECDSA Nonce Reuse - 200#

In this challenge, you are given two ECDSA signatures that have reused the same nonce. Your task is to recover the private key used to generate these signatures. This is a classic cryptographic vulnerability that occurs when the same nonce is used in multiple ECDSA signatures.

r: 0x5d66e837a35ddc34be6fb126a3ec37153ff4767ff63cbfbbb32c04a795680491
s1: 0x1a53499a4aafb33d59ed9a4c5fcc92c5850dcb23d208de40a909357f6fa2c12c
message1: "what up defcon"
s2: 0xd67006bc8b7375e236e11154d576eed0fc8539c3bba566f696e9a5340bb92bee
message2: "uh oh this isn't good"
n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
  • s1 and s2 are the signature parameters.

  • h1 and h2 are the message digests.

  • r is the same nonce for both signatures due to nonce reuse.

  • n is the order of the elliptic curve group.

Task:

  • Use the Python script for performing a ECDSA Nonce Reuse and recover the private key using the provided information and hints.

Once the private key is recovered, we as a attacker can use it to sign new transactions, effectively allowing them to transfer Bitcoin from the compromised wallet to our own addresses. This can lead to unauthorized spending of the victim’s Bitcoin.

Hint:

  • \( d_A = \left( \frac{s_2 \cdot h_1 - s_1 \cdot h_2}{r \cdot (s_1 - s_2)} \right) \mod n \). With this equation an attacker can obtain two different ECDSA signatures that reused the same nonce, they can use this equation to recover the private key \( d_A \). This allows the attacker to potentially forge signatures or decrypt messages that were intended to be secure.

The original equation is:

\[ d_A = \left( \frac{s_2 \cdot h_1 - s_1 \cdot h_2}{r \cdot (s_1 - s_2)} \right) \mod n \]

To express the denominator of the fraction as an inverse modulo equation, we need to find the modular inverse of \( (s_1 - s_2) \pmod{n} \). The modular inverse of \( (s_1 - s_2) \) is a value \( k \) such that:

\[ k \cdot (s_1 - s_2) \equiv 1 \pmod{n} \]

This can be written as:

\[ k = (s_1 - s_2)^{-1} \pmod{n} \]

So, the original equation can be rewritten using the modular inverse as:

\[ d_A = \left( (s_2 \cdot h_1 - s_1 \cdot h_2) \cdot (r \cdot (s_1 - s_2))^{-1} \right) \mod n \]

Where:

\[ (r \cdot (s_1 - s_2))^{-1} \pmod{n} \]

is the modular inverse of \( ( r \cdot (s_1 - s_2) ) \pmod{n} \).

from hashlib import sha256
from ecdsa import SECP256k1, SigningKey
from ecdsa.util import sigdecode_string
from ecdsa.numbertheory import inverse_mod

def hash_message(message):
    """Hash the message using SHA-256."""
    message = 0

    assert len("{0:b}".format(message)) == 255, f"Expected binary length of 255, but got {len("{0:b}".format(message))}" # Helpful assertion
    return None

def recover_private_key(h1, h2, s1, s2, r1, r2, n):
    """Recover the private key via nonce reuse.

    Recover the private key from two different signatures
    that use the same random nonce `k` during signature
    generation. Note that if the same `k` is used in two
    signatures, this implies that the secp256k1 32-byte
    signature parameter `r` is identical.

    Parameters
    ----------
        h1: int
            The 32-byte message digest of the message `m1`.
        h2: int
            The 32-byte message digest of the message `m2`.
        s1: int
            The secp256k1 32-byte signature parameter `s1`.
        s2: int
            The secp256k1 32-byte signature parameter `s2`.
        r1: int
            The secp256k1 32-byte signature parameter `r1`.
        r2: int
            The secp256k1 32-byte signature parameter `r2`.
        n:  int
            The 32-byte integer order of G (part of the public key).

    Returns
    -------
        pk: int
            Return a 32-byte private key.

    """
    assert r1 == r2, "No ECDSA nonce reuse detected."
    return None

if __name__ == "__main__":
    # Provided values
    r = 0x5d66e837a35ddc34be6fb126a3ec37153ff4767ff63cbfbbb32c04a795680491
    signature1 = 0x1a53499a4aafb33d59ed9a4c5fcc92c5850dcb23d208de40a909357f6fa2c12c
    signature2 = 0xd67006bc8b7375e236e11154d576eed0fc8539c3bba566f696e9a5340bb92bee
    n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141

    # Messages
    message1 = "what up defcon"
    message2 = "uh oh this isn't good"

    # Step 1: Hash the messages
    z1 = hash_message(message1)
    z2 = hash_message(message2)

    # Step 2: Solve for the private key
    recovered_private_key = recover_private_key(z1, z2, signature1, signature2, r, r, n)

    print(f"Recovered private key: {recovered_private_key}")
  Cell In[2], line 10
    assert len("{0:b}".format(message)) == 255, f"Expected binary length of 255, but got {len("{0:b}".format(message))}" # Helpful assertion
                                                                                               ^
SyntaxError: f-string: unmatched '('