bg-ctf

Challenge 9: The unverified

In this challenge, you are provided with a contract that has no verified source code.

Contract Overview

The contract is deployed using raw bytecode. By analyzing the bytecode and the deployment script, we can deduce:

Hints

Hint 1 This is what the deployment script looks like:
const challenge9BytecodeBase = "...";
const nftFlagsAddress = await nftFlags.getAddress();
const challenge9Bytecode = challenge9BytecodeBase + nftFlagsAddress.slice(2).padStart(64, "0");
const deployerSigner = await hre.ethers.getSigner(deployer);
const nonce = await deployerSigner.getNonce();

const feeData = await hre.ethers.provider.getFeeData();
const rawTx = {
    nonce: nonce,
    maxFeePerGas: feeData.maxFeePerGas,
    maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
    gasLimit: 800_000,
    to: null,
    value: 0,
    data: challenge9Bytecode,
    chainId: (await hre.ethers.provider.getNetwork()).chainId,
};

const txResponse = await deployerSigner.sendTransaction(rawTx);
const txReceipt = await txResponse.wait();
const challenge9Address = txReceipt?.contractAddress;

if (challenge9Address) await save("Challenge9", { address: challenge9Address, abi: [] });

console.log("🚩 Challenge #9 deployed at:", challenge9Address);
What do you notice? Who is deploying this contract?
Hint 2 The bytecode has the line PUSH18 0x424720435446204368616c6c656e67652039. I wonder what this might do?
Hint 3 The contract has a function selector 0x23cfec7e. Take a look at what it does - you can try using https://ethervm.io/decompile.

Solution

Click to reveal solution 1. **Find the Private Key**: The deploy script uses the default Hardhat/Foundry mnemonic: `test test test test test test test test test test test junk`. The derivation path used for the signer is `m/44'/60'/0'/0/12`. Get the private key for this address. 2. **Construct the Message**: The hex chars `0x424720435446204368616c6c656e67652039` decode to `BG CTF Challenge 9`. Further down in the bytecode, we see the signing format: `keccak256(abi.encode("BG CTF Challenge 9", msg.sender))` 3. **Sign and Mint**: - Sign the eth-prefixed message hash with the leaked private key. - Call the function `0x23cfec7e` with the signer's address and the signature.
(bool success, ) = challenge9.call(
    abi.encodeWithSelector(0x23cfec7e, signerAddress, signature)
);
Why this works: - The contract relies on a specific "trusted" signer to authorize mints. - However, the "trusted" signer's key was generated from a public, insecure mnemonic committed in the deployment script.

Why This Matters

Hardcoded Secrets & Test Mnemonics: Never use default mnemonics or commit .env files containing real private keys to version control. If a deployment script containing a mnemonic (or pointing to a public one) is pushed to GitHub, any address derived from it is compromised immediately.

Security Through Obscurity: Security through obscurity refers to the idea of making something secure by not revealing the implementation (in this case, the contract). However, any contract can be reverse engineered given enough of an incentive.

This is a variation of S1C4: Who Can Sign This?, but obscured by the lack of source code and the indirect way the key was leaked, similar to S1C8: The unverified.