Overflow and Underflow are the two forms of flow in blockchain. When a single numerical computation is performed, the output exceeds the maximum that a register or memory can store or represent. This is referred to as an “overflow.” In the Solidity programming language, uint8 may represent 256 values ranging from 0 to 255. When the uint8 type is used to compute 255 + 1 in the actual operation, an overflow occurs, and the calculated result is 0, i.e., the uint8 type’s minimal value. Underflow occurs when the computation result is less than the maximum capacity of the register or memory to store or represent. For instance, using the uint8 type to calculate 0–1 in Solidity will result in underflow, so the calculated value will be 255, which is the highest value that the uint8 type can represent.
Since a number does not always offer the right value in mathematical computations, the problem is with the assumption that it will. After executing a mathematical procedure that exceeds a value but does not verify if the value is valid in the context of the mathematical operation, comparing a variable with a required statement is not sufficiently accurate.
This will then result in an action being taken that is advantageous to the attacker, such as verifying a low value that is needed for fund validation but obtaining a very large value after the initial check. Let’s run through some illustrations.
When transmitting a large batch of values to several receivers, overflow scenarios frequently occur. If we compare the total value of all users’ tokens to the total amount of funds, while conducting an airdrop and giving tokens to a user with a cost and tip to pay the bill, each of whom receives a significant number of tokens, an overflow may occur. In case of overflow, the algorithm would compare a lower value of the overflowed tokens to the entire token value and make it appear as though we had enough tokens to fund the transaction.
For example, what would happen in the case below if the integer could only hold two digits?
The account contains 100 tokens.
We are sending 99 tokens as a cost and 1 token as a tip.
Your total would be 99 + 1 or 100.
So, in this example, the amount would reset back to zero as the integer can only hold two digits. This may cause problems if a needed statement is not handled using safe mathematical methods to sanitize the output.
Let’s take this example code snippet.
TimeLock is a contract where we can deposit Ether, and we have to wait at least one week before we can withdraw the amount of wallet that we deposit into this contract. The time at which we can withdraw is kept in a mapping called lockTime.
When we post ether into this contract, it does two things. It will first update your balance, and then it will update LockTime one week from now so that we won’t be able to withdraw from this contract for at least a week. We can see how this mapping is used in the withdrawal function.
So, let’s take a look at what happens when we deposit some Ether into this contract, wait for one week, and now, we want to withdraw from it.
The function “withdrawal” will first check that we have some Ether deposited into this contract, and this is done by checking that the balance mapping of your address is greater than 0.
Next, it checks to see if the current time is greater than the locktime, so if we try to call this function before a week has passed since the deposit, this check will fail and we won’t be able to withdraw.
After the two checks pass, the withdrawal function gets updated and then sends the ether back to the contract.
Now, if we want to increase locktime by, say, one month, we can do that by calling the function “increase locktime” and passing in the settings to increase locktime by one month.
This is how the contract time table works: it allows us to deposit funds, but we have to wait for a minimum of one week before we can withdraw, as well as increase the lock time by calling the “increased lock time” function.
This contract is vulnerable to overflow. We are going to show how we can withdraw from this contract without waiting for one week, and the reasoning behind this exploit is to update lockTime with a very huge number.
So, let’s create the contract function that’s going to exploit the contract and name it “attack.”
The contract that’s going to be exploited is stored in a state variable called TimeLock using a constructor. In order to launch the attack and obtain the Ether from the Timelock contract before the maturity period, we would need to set a target, which will be carried out in a function called “Attack.”
We’ll be sending some ether to the TimeLock contract, so we’ll declare this function as payable. When we call the Withdrawal function, it will send a call request to the TimeLock contract function to check for the time period since the deposit.
We’ll first deposit some ether into the TimeLock contract by calling timeLock.deposit. This will set the time to one week from now so that if we try to withdraw immediately, it won’t accept it. The unit should normally be 256.
Let’s see this in action by compiling the Overflow.sol smart contract and running it.
Now that we have deployed the TimeLock contract from overflow.sol, we will navigate to Deploy & Run Transactions, set the value to 1 Ether, select the TimeLock contract from the contract sections, and click deploy as shown in the screenshot below.
In order to deploy the “attacker” contract from overflow.sol, we need to copy the Deployed contract address of the “Timelock” contract, change the contract section to “attack,” and paste the address alongside the “attack” contract. Now we can deploy the contract.
Now that we have the timeLock contract and attack contracts deployed, let us see them in action.
Let’s say that the user is going to deposit one Ether into the TimeLock contract, so we’ll call the function “deposit” with one Ether. Now if we try to withdraw, we can see here that the transaction fails with a “long time” that has not expired, as shown in the screenshot below.
So, we would have to wait at least one week before we could withdraw from this contract.
However, we would be able to deposit and then instantly withdraw using the attack contract that we have. Using this, we would be able to send one Ether and then execute an attack to instantly withdraw it back.
So, we were able to withdraw without waiting for one week, and this is because we caused the overflow in the lab. We can check that the lockTime did indeed overflow by copying the address and then checking the length of that contract time.
Here we are passing the address of the “attack” contract and then hitting for a long time. We can see that the time has now been set to zero, and thus we have successfully executed the overflow attack.
In this blog, we discussed Integer Overflow. In the upcoming blog, we are going to explore more attack vectors. Also, refer to our previous blog posts – Blockchain 101 and Reentrancy Attacks.
By partnering with Redfox Security, you’ll get the best security and technical skills required to execute an effective and thorough penetration test. Our offensive security experts have years of experience assisting organizations in protecting their digital assets through penetration testing services. To schedule a call with one of our technical specialists, call 1-800-917-0850 now.
Redfox Security is a diverse network of expert security consultants with a global mindset and a collaborative culture. With a combination of data-driven, research-based, and manual testing methodologies, we proudly deliver robust security solutions.
“Join us on our journey of growth and development by signing up for our comprehensive courses, if you want to excel in the field of cybersecurity.”