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 thatceil(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 ofx * 1e18
are all 0.1e18
already has 18 trailing 0s (remember that10**18 == 5**18 * 2**18
). So ourx
should be any number with256-18=238
trailing 0s in its binary representation.Sure enough, we can pass
2**238
for ournumTokens
, and do this challenge with no sent value.Answer in decimal/bin/hex:
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:
(I later called sell and retrieved my eth)