ethernaut icon indicating copy to clipboard operation
ethernaut copied to clipboard

Level 13 of ethernauts is not verified on etherscan, making it harder than intended

Open kylebakerio opened this issue 3 years ago • 5 comments

official solution shows a verified contract, which can be emulated on remix in a straightforward fashion, which is relatively necessary to predict gas costs during operation.

I was not quite able to verify, but almost. Getting OZ's safemath 0.6.0, and pasting it in place of the import statement, got identical bytecode except for this part dangling at the end I wasn't able to account for yet:

a26469706673582212202d807b96389b246b6031859c4c828eb95e688157a516ac849532642a70ebfd3d64736f6c63430006030033

Anyways, that should still be close enough to estimate gas costs, but it does add some indirection that may not (?) be desired. I do find it odd that this is a 5/10 and privacy is an 8/10--privacy felt easy, solving the gas calculations felt trickier to me, but I dunno. Maybe storage is just something I was personally more familiar with.

kylebakerio avatar Mar 07 '22 14:03 kylebakerio

My path to solving it literally involved me sitting down and converting the on-chain bytecode to opcodes and manually calculating gas usage of each executed bytecode up through the GAS call and tracing the path of call manually, guided by watching remix debug follow the opcodes of the similar (but not identical) bytecode from my local-compiled-near-clone of the guessed solidity. This worked fairly well and was not quite as bad as it sounds, but I think decompiling bytecode and working with raw opcodes is... not an insubstantial bar.

Alternative is to brute force the difference, which is the only existing solution I've seen out there after checking.

Not sure if the update to 0.6.0 was intended to raise the bar a bit or not, anyways. I'd suggest raising the difficulty rating above 5/10 to something closer to the privacy one, at least.

kylebakerio avatar Mar 09 '22 12:03 kylebakerio

Hello @kylebakerio have you considered using this repo ? You can clone it and compile contracts in local with same settings as they are deployed in Rinkeby. In this way you might have anything you need.

In any case you are right that contract was verified in the past and now it is not anymore. We will consider either adjusting difficulty or adding a verification.

xaler5 avatar Mar 23 '22 09:03 xaler5

have you considered using this repo ?

did you forget to add a link? Not sure what repo you're referring to.

In any case you are right that contract was verified in the past and now it is not anymore. We will consider either adjusting difficulty or adding a verification.

sounds good! I kind of like it the way it is unverified, but does raise the bar. It looks like remix should be able to do this kind of step through even on unverified contracts, but for some reason that functionality is unreliable right now and doesn't seem to work on this problem (for me and others, have seen other option issues on remix about this problem). (I've also had issues using the debugger with remix on many other transactions, too, but remix can debug this fine when I put up my own verified version of it.)

kylebakerio avatar Mar 24 '22 16:03 kylebakerio

did you forget to add a link? Not sure what repo you're referring to.

I meant this repo where are we discussing. The ethernaut one https://github.com/OpenZeppelin/ethernaut

xaler5 avatar Sep 19 '22 08:09 xaler5

Here's how I solved it. First I deployed a modified version of GatekeeperOne.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import "@openzeppelin/contracts/math/SafeMath.sol";

contract MOD_GatekeeperOne {
    using SafeMath for uint256;
    address public entrant;
    uint public gasLeft;

    modifier gateOne() {
        require(msg.sender != tx.origin);
        _;
    }

    modifier gateTwo() {
        uint gl = gasleft();
        // require(gasleft().mod(8191) == 0);
        gasLeft = gl;
        _;
    }

    function enter(bytes8 /*_gateKey*/)
        public
        gateOne
        gateTwo
        // gateThree(_gateKey)
        returns (bool)
    {
        entrant = tx.origin;
        return true;
    }
}

I found a best guess using that modified version and then attacked the original version by brute forcing around that guess (0, -1, +1, -2, +2, ...): 50 attempts were enough. I wrote a JS script, of course.

mtomassoli avatar Sep 26 '22 15:09 mtomassoli

The game now should verify the contract after creation, let me know if it works well on your side :)

xaler5 avatar Dec 20 '22 10:12 xaler5