[Note] — If you’re directly reading this blog without actually working on the challenge, It doesn’t help you. I request you to try your best and come back if you face any difficulties.
Introduction:
Hello Hackers🥰, Welcome back to our world of Web3 Security. Today We’ll discuss the first challenge from the “Math Section” — Token Sale. This Challenge is rated as Low-Medium in my perspective. However, It may differ from person to person.
What do you need to know before attempting this challenge?
My First and Foremost suggestion is to go through this video from “Smart-Contract-Programmer” who explained the “Overflow and Underflow” vulnerability in detail.
Now let’s jump into our Challenge :
The Question Says, “This token contract allows you to buy and sell tokens at an even exchange rate of 1 token per ether.
The contract starts off with a balance of 1 ether. See if you can take some of that away. ”
To Explain in Simple Terms, This is an Exchange contract where we can sell and buy the tokens at some specific ratio(1:1).
“If you clearly observe on the first line it says that, For uint32 — the maximum value it can hold upto is 2³² — 1.”
Contract Review:
pragma solidity ^0.4.21;contract TokenSaleChallenge {
mapping(address => uint256) public balanceOf;
uint256 constant PRICE_PER_TOKEN = 1 ether;
function TokenSaleChallenge(address _player) public payable {
require(msg.value == 1 ether);
}
function isComplete() public view returns (bool) {
return address(this).balance < 1 ether;
}
function buy(uint256 numTokens) public payable {
require(msg.value == numTokens * PRICE_PER_TOKEN);
balanceOf[msg.sender] += numTokens;
}
function sell(uint256 numTokens) public {
require(balanceOf[msg.sender] >= numTokens);
balanceOf[msg.sender] -= numTokens;
msg.sender.transfer(numTokens * PRICE_PER_TOKEN);
}
}
[Note] — EVM(Ethereum Virtual Machine) converts any value to its low-level value which means “1 eth is converted to a low-level value of 10¹⁸”
: 2²⁵⁶ — 1 / 10¹⁸ + 1 = 115792089237316195423570985008687907853269984665640564039458
On Passing this value on buy() function, it multiply again by 10¹⁸ which results in overflow issue. But here, We need to send the exact amount of eth in wei to make this successful which is 415992086870360064 wei(0.4 eth)
Time to Exploit:
115792089237316195423570985008687907853269984665640564039459
and send msg.value as 415992086870360064 wei
.Any specific reason?
You may ask why should we pass the exact value. How did you get that specific value? Why it won’t work even if you give a number that is less than 1? — Here is the answer
“ A uint256 variable has a maximum value of 2**256–1. So to make msg.value overflow, we need to ensure that numTokens multiplied by 10**18 is greater than 2**256–1. To get that value, we divide 2**256–1 by 10**18, giving us: 115792089237316195423570985008687907853269984665640564039458(round value) ”
“We need to figure out the amount that msg.value overflows so we can send the correct amount of ether with the call. To calculate the amount of it overflows, subtract 2**256–1 from the incredibly large number we just calculated. Doing so leaves us with 415992086870360064 wei, the amount of wei we need to send when we call buy(). “