1

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?

2
  • What version of solidity are you using ? If it’s post 0,8.0, overflows behave differently
    – 0xsegfault
    Commented Jul 10, 2022 at 14:09
  • The challenge contract is on 0.4.21, but I found out what my issue here is, I'll post a full answer detailing it. In short, I was not considering what python does in the background with integer divisions, prior to the ceil function kicking in, 2**256 and 1e18 are already float, I completely forgot about this. Also, I'm still learning solidity, so I was not aware that in every operation solidity will round down integer operations.
    – dreth
    Commented Jul 10, 2022 at 14:31

2 Answers 2

1

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)

2
  • Thank you! this makes a lot of sense. Very interesting approach. I was satisfied with my answer but I really appreciate that you still took the time to write yours on the thread, I really wanted to solve it sending 0 wei but didn't know how to.
    – dreth
    Commented Jul 14, 2022 at 9:59
  • No worries! Feel free to mark it as answered if you feel like it's satisfactory.
    – Usmann
    Commented Jul 14, 2022 at 15:41
1

I have found my mistake after some pointers given to me in the Cryptodevs discord #solidity channel. Thanks to Doggo and Jas.

I was not aware of (or forgot) a few things

  • Floating point numbers do not exist in Solidity.
  • Operations in Solidity between whole numbers which would yield a floating point number are immediate rounded down at every step, so an arithmetic equation consisting of multiple operations would be applied a floor function at every step of the operation, i.e. $2 \div 3 * 7 \div 2 \rightarrow \lfloor\lfloor\lfloor 2 \div 23 \rfloor * 7 \rfloor \div 2 \rfloor $.
  • It is extremely difficult to get a perfect 0 after an integer overflow using products, therefore, in order to solve this problem, it's nearly impossible that the price for which I'll be able to buy the tokens (each worth 1 ether) will be exactly zero wei.
  • I completely forgot that python will do things in the background with math, like for example: 1e18 is immediately considered a float, even though it's a whole number, therefore, to have 1e18 as an integer, I should be writing int(1e18).

Now to solve the problem

All that's needed to be passed to numTokens is a value that when multiplied by $10^{18}$ will yield a number that we can:

  1. Pay for in wei that is lower than $10^{18}$ itself
  2. As low as possible (ideally) as long as condition 1 holds

For this you can create a simple function that will be the base to the overflow exploit, for example:

enter image description here

Then optimize for $min(f(x))$ while $x > 0$. I did this by bruteforce and made a simple neat table with the best numbers I got:

f(x) x
265665118208 549972
531330236416 1099944
730579075072 1512423
996244193280 2062395
1261909311488 2612367

In this case, I'd simply choose the lowest one for f(x) and make the function calls:

  1. Call buy() passing in $(\frac{2^{256} * 549972}{10^{18}} + 1)$ as numTokens.

  2. Call sell() passing in 1 as numTokens.

  3. Check if the challenge is complete by calling isComplete() or just clicking on Check Solution on the Capture The Ether site.

I'd love to be corrected if I've made any conceptual mistake here, thanks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.