Overflow – Integer Overflow Not Occurring as Expected in Capture the Ether Token Sale Challenge

overflow

I've been working on the capture the ether challenges for a few days now and I'm sort of stuck at the token sale challenge. I know that the exploit for this consists of an integer overflow bug (the compiler version is 0.4.21) in this specific line of code:

require(msg.value == numTokens * PRICE_PER_TOKEN);

Where PRICE_PER_TOKEN is 1 ether, so basically 1e18, and numTokens is a uint256 param that the function containing this require statement takes.

It seems obvious to me, that in order to cause an integer overflow here in order to get the statement to check for msg.value == 0 is to pass ceil(2**256 / 1e18) (I'm using brownie, so I'm passing them like this in python), so when this value is multiplied by 1 ether, the result is 2**256 which is the max uint256 + 1 which should overflow to 0.

This, however, does not seem to work. The tx reverts.

I also tried going on remix, deploying the contract as it shows on the capture the ether site and passing 115792089237316203707617735395386539918674240093853421928448 as numTokens, but this also didn't work. When I go and debug the tx, in the MUL opcode (when the multiplication happens), it's returning 0x0000000000000527b98ba3300000000000000000000000000000000000000000 which in decimal is 8284046750386698632065404255428212857888990415992086870360064, not the number I expect after the overflow (0)

What could I be doing wrong here?

Best Answer

The problem is that 2**256 / 1e18 is not a whole number. ceil(2**256 / 1e18) * 1e18 does not equal 2**256. This is for the same reason that ceil(5/3)*3 != 5.

When a computation overflows in the EVM, the lowest 256 bits of the result are taken. So if our computation ends up with any number such that the lowest 256 bits are all 0, we can send 0wei to our target contract.

A little more formally: We want to construct some value x such that the lowest 256 bits of x * 1e18 are all 0.

1e18 already has 18 trailing 0s (remember that 10**18 == 5**18 * 2**18). So our x should be any number with 256-18=238 trailing 0s in its binary representation.

Sure enough, we can pass 2**238 for our numTokens, and do this challenge with no sent value.

Answer in decimal/bin/hex:

441711766194596082395824375185729628956870974218904739530401550323154944
0b10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0x400000000000000000000000000000000000000000000000000000000000

I redid this capture the ether challenge to demonstrate.

Deployed the challenge contract here: https://ropsten.etherscan.io/address/0x4c66fe83e25249e32C56f385E8d51Aa13Bdee85a

Called buy with 0wei here (press "Click to see more" and inspect the input data): https://ropsten.etherscan.io/tx/0xc93985e0293f103d42753a530bc9b5c86ba3864ca83e131a22d256eab798b97e

Then confirmed my balance is 2^238: bal

(I later called sell and retrieved my eth)