CTF/Ethernaut

[Ethernaut] 20. Denial

0xperiagoge 2024. 3. 4. 23:23

Ethernaut levevl 20의 요구사항

이것은 시간이 지남에 따라 펀드가 떨어지는 간단한 지갑이다. 출금 파트너가 되어 천천히 자금을 출금할 수 있다.
withdraw()를 호출할 때 owner가 자금을 인출하는 것을 거부할 수 있는 경우(계약에 여전히 자금이 있고 거래의 가스가 100만 개 이하인 경우) 이 레벨에서 승리하게 됩니다.

 

지금 컨트랙트에는 0.001ETH가 있다.

 

setWithdrawParter(player)로 설정. 

 

withdraw()를 보면 문제 컨트랙트 잔액의 1/100을 partner로 설정되어 있는 사람에게 주고 오너에게도 1/100을 준다. 

음 withdraw해서 받는 파트너를 컨트랙트로 정하고 그 컨트랙트에 이더를 받았을 때 (receive())를 이용해서 반복적인 withdraw를 실행하면 될 듯하다.  + out of gas가 뜨게 해야 한다. 

 

간단한 풀이

    fallback() external payable {
        while (true) {}
    }

 

우선 위 코드가 있는 컨트랙트를 배포해서 

 

문제 컨트랙트의 setWithdrawPartner에 넣어서 파트너로 설정해서 withdraw를 실행하면 

withdraw()에서 call 함수를 통해서 ETH를 컨트랙트에 보내는데 Attack컨트랙트에 receive함수가 없어서 fallback이 동작한다. 

그래서 while무한 루프를 돌게되면서 out of gas가 발생한다. 

 

level 20. Denial 코드 

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Denial {

    address public partner; // withdrawal partner - pay the gas, split the withdraw
    address public constant owner = address(0xA9E);
    uint timeLastWithdrawn;
    mapping(address => uint) withdrawPartnerBalances; // keep track of partners balances

    function setWithdrawPartner(address _partner) public {
        partner = _partner;
    }

    // withdraw 1% to recipient and 1% to owner
    function withdraw() public {
        uint amountToSend = address(this).balance / 100;
        // perform a call without checking return
        // The recipient can revert, the owner will still get their share
        partner.call{value:amountToSend}("");
        payable(owner).transfer(amountToSend);
        // keep track of last withdrawal time
        timeLastWithdrawn = block.timestamp;
        withdrawPartnerBalances[partner] +=  amountToSend;
    }

    // allow deposit of funds
    receive() external payable {}

    // convenience function
    function contractBalance() public view returns (uint) {
        return address(this).balance;
    }
}