Gold NFT

Idea

Contract bytecode:

Decompile it:

Here is the pseudocode I got:

function () public payable { 
    revert();
}

function 0x0daa5703(uint256 varg0, bool varg1) public payable { 
    require(4 + (msg.data.length - 4) - 4 >= 64);
    require(varg0 == varg0);
    require(varg1 == varg1);
    require(msg.sender == address(0x3));
    STORAGE[varg0] = varg1;
}

function read(bytes32 varg0) public payable { 
    require(4 + (msg.data.length - 4) - 4 >= 32);
    require(varg0 == varg0);
    return bool(STORAGE[varg0]);
}

// Note: The function selector is not present in the original solidity code.
// However, we display it for the sake of completeness.

function __function_selector__(bytes4 function_selector) public payable { 
    MEM[64] = 128;
    require(!msg.value);
    if (msg.data.length >= 4) {
        if (0xdaa5703 == function_selector >> 224) {
            0x0daa5703();
        } else if (0x61da1439 == function_selector >> 224) {
            read(bytes32);
        }
    }
    ();
}

The read() function actually reads from a storage slot and that slot number comes from our input. In the end bool(STORAGE[varg0]) will cast the content in that storage slot to boolean, anything nonzero suffices. So our objective is to find out a non-zero storage slot.

The other function with selector 0x0daa5703 is a setter with access control: only address(3) can call it.

In etherscan we can find the "Contract Creation" tx 0x88fc0f1dd855405d092fc408c3311e7131477ec201f39344c4f002371c23f81c. A lesser-known feature is the "State" tab:

It shows the storage slot 0x23ee4bc3b6ce4736bb2c0004c972ddcbe5c9795964cdd6351dadba79a295f5fe was updated from 0 to 1 during creation:

This is just what we want, so the "password" is just 0x23ee4bc3b6ce4736bb2c0004c972ddcbe5c9795964cdd6351dadba79a295f5fe.

After that, notice _safeMint() has reentrancy problem, so the rest is easy.

PoC

Last updated