Challenge: RustRoll
Category: Crypto/Blockchain
A Rust-based rollup node is running as a public TCP service. To save block space, we optimized the address format. We have a Vault account with a huge balance. Can you drain the magic amount from the Vault?
Connecting to the service and sending HELP:
Commands:
HELP - show this help
INFO - show public parameters
LIST - show known accounts (debug)
TX <hex> - submit transaction
Transaction layout (hex, 120 bytes):
from_addr: u32 LE
to_addr: u32 LE
amount: u64 LE
nonce: u64 LE
pubkey: [32 bytes] (Ed25519)
signature: [64 bytes] (Ed25519)
The INFO command reveals:
vault_addr: 622488
magic_amount: 13371337
address_bits: 20
And LIST shows existing accounts:
addr=622488 balance=999919771978 nonce=6
addr=240940 balance=53485348 nonce=0
...
The key hint is in the challenge description: “To save block space, we optimized the address format” combined with address_bits: 20.
This tells us:
u32 (32 bits) for addressesThe vulnerability is a hash collision attack due to address truncation:
After extensive testing of various hash functions (SHA256, Blake2s, Blake2b, SHA3, etc.), I discovered the system uses Blake3:
import blake3
h = blake3.blake3(pubkey).digest()
addr = struct.unpack('<I', h[:4])[0] & 0xFFFFF # 20-bit mask
The attack is straightforward once the hash function is known:
blake3(pubkey)[:4] & 0xFFFFFimport blake3
from nacl.signing import SigningKey
import struct
target = 622488 # vault address
mask = 0xFFFFF # 20-bit mask
# Search for collision
for i in range(5000000):
sk = SigningKey.generate()
pk = sk.verify_key.encode()
h = blake3.blake3(pk).digest()
addr = struct.unpack('<I', h[:4])[0] & mask
if addr == target:
print(f"Found collision! PK={pk.hex()}")
# Craft exploit transaction
from_addr = target
to_addr = 999
amount = 13371337
nonce = 9 # current vault nonce
msg = struct.pack('<IIqq', from_addr, to_addr, amount, nonce)
signed = sk.sign(msg)
tx = msg + pk + signed.signature
# Submit TX
# ... send to server ...
break
The collision is found after approximately 500,000-1,000,000 iterations (expected ~2^19 = 524,288 on average for a 20-bit collision).
nexus{Th1s-Is_A-Hard#Fl4g!}