1/Intro

The 29 December 2022 a challenge went public on Twitter

Untitled

The goal is to steal the 0.1 ETH.

Obviously, the contract is not verified so we have to dive into the bytecode time to use decompilers lads!

Untitled

(Here, we have to go fast and the contract is small) let’s use dedaub this time (but heimdall looks so nice).

We will have this kind of code:

**function __function_selector__() public payable { 
    v0 = BASEFEE();
    if (msg.value) {
        require(msg.value > 'iiiii' * (0x63b0beef - block.timestamp)RETURNDATASIZE(), RETURNDATASIZE());
        STORAGE[msg.sender] = 4095 + block.timestamp;
        return MEM[(RETURNDATASIZE()) len (RETURNDATASIZE())];
    }
		else {
        require((block.timestamp > STORAGE[msg.sender]) & (RETURNDATASIZE() < STORAGE[msg.sender])RETURNDATASIZE(), RETURNDATASIZE());
        MEM[0] = (msg.data.length << 232) + 0x61000080600a3d393df300000000000000000000000000000000000000000000;
        CALLDATACOPY(10, RETURNDATASIZE(), msg.data.length);
        v1 = v2 = 1 + (msg.data.length - 1 >> 5);
        do {
            MEM[10 + (v2 - v1 << 5)] = (msg.sender | msg.sender << 160) ^ MEM[10 + (v2 - v1 << 5)];
            v1 = v1 - 1;
        } while (!v1);
        v3 = create.code(RETURNDATASIZE(), msg.data.length + 10).value(RETURNDATASIZE());
    [1] v4 = v3.delegatecall(MEM[(RETURNDATASIZE()) len (RETURNDATASIZE())], MEM[(RETURNDATASIZE()) len (RETURNDATASIZE())]).gas(msg.gas);
        return MEM[(RETURNDATASIZE()) len (RETURNDATASIZE())]; 
  }
}**

Oh crap! How could we steal the 0.1 eth without transfer, or .call? Oh, we got a delegatecall() at the end!

So we need to somehow find a way to trigger the Delegatecall on the v3 contract with a code that permits emptying the contract.

When we are looking closely V3 is being deployed using the memory. Hmm.. time to make some bytecode to execute a malicious contract here!

So we probably need to figure out, how could we manage to deploy something with a malicious code to steal the eth (SelfDestruct with delegatecall() → perfect combo ref to Wormhole Uninitialized Proxy Bugfix Review)

2/ Bypass require

So for now we need to bypass the first require() .

  **require((block.timestamp > STORAGE[msg.sender]) & (RETURNDATASIZE() < STORAGE[msg.sender])RETURNDATASIZE(), RETURNDATASIZE());**

For this, we just need to have the storage[msg.sender] > returndatasize() which will always be 0 in our case for the returndatasize()