Ethernaut: 17. 복구

Play the level

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

import '@openzeppelin/contracts/math/SafeMath.sol';

contract Recovery {
  //generate tokens
  function generateToken(string memory _name, uint256 _initialSupply) public {
    new SimpleToken(_name, msg.sender, _initialSupply);
  }
}

contract SimpleToken {
  using SafeMath for uint256;
  // public variables
  string public name;
  mapping (address => uint) public balances;

  // constructor
  constructor(string memory _name, address _creator, uint256 _initialSupply) public {
    name = _name;
    balances[_creator] = _initialSupply;
  }

  // collect ether in return for tokens
  receive() external payable {
    balances[msg.sender] = msg.value.mul(10);
  }

  // allow transfers of tokens
  function transfer(address _to, uint _amount) public { 
    require(balances[msg.sender] >= _amount);
    balances[msg.sender] = balances[msg.sender].sub(_amount);
    balances[_to] = _amount;
  }

  // clean up after ourselves
  function destroy(address payable _to) public {
    selfdestruct(_to);
  }
}


내 초기 솔루션은 내 수준 인스턴스의 계약 생성 트랜잭션의 내부 트랜잭션을 확인하는 것이 었습니다. 거기에서 우리는 "잃어버린"계약 주소를 아주 잘 볼 수 있고 거기에서 destroy 함수를 호출할 것입니다. 인수가 있는 함수를 호출하려면 calldata를 제공해야 합니다(here 참조). 인수는 32바이트 단위로 제공되지만 calldata의 처음 4바이트는 호출할 함수를 나타냅니다. 이는 함수 표준 형식의 처음 4바이트로 계산됩니다. 여러 가지 방법으로 찾을 수 있습니다.
  • one I wrote과 같은 온라인 도구를 사용하십시오.
  • 약간의 Solidity 코드를 작성하고 계산합니다bytes4(keccak256("destory(address)")). 표준 형식을 손으로 작성해야 합니다.
  • 다음과 같이 소규모 계약을 작성하고 로컬에서 실행합니다(예: Remix IDE with VM).

  • contract AAA { 
      // this is the same function from ethernaut
      function destroy(address payable _to) public {
        selfdestruct(_to);
      }
    
      // we can directly find its selector
      function print() public pure returns (bytes4) {
        return this.destroy.selector;
      }
    }
    


    위의 방법 중 하나를 사용하면 함수 선택기가 0x00f55d9d 임을 알 수 있습니다. 그런 다음 다음과 같이 destroy 함수를 호출할 수 있습니다.

    const functionSelector = '0x00f55d9d';
    await web3.eth.sendTransaction({
      from: player,
      to: '0x559905e90cF45D7495e63dA1baEFB54d63B1436A', // the lost & found address
      data: web3.utils.encodePacked(functionSelector, web3.utils.padLeft(player, 64))
    })
    


    오리지널 솔루션



    내 솔루션을 Ethernaut에 보내면 나중에 메시지에서 실제 솔루션을 배웠습니다! 계약 주소는 결정적이며 keccack256(RLP_encode(address, nonce))로 계산됩니다. 컨트랙트의 nonce는 컨트랙트가 생성한 컨트랙트의 수입니다. 모든 nonce는 계약에 대해 0이지만 일단 생성되면 1이 됩니다(자신의 생성은 nonce를 1로 만듭니다).

    Ethereum 문서here에서 RLP 인코딩에 대해 읽어보십시오. 우리는 20바이트 주소의 RLP 인코딩과 [<20 byte string>, <1 byte integer>] 와 같은 목록에 해당하는 nonce 값 1을 원합니다.

    문자열의 경우:

    if a string is 0-55 bytes long, the RLP encoding consists of a single byte with value 0x80 (dec. 128) plus the length of the string followed by the string. The range of the first byte is thus 0x80, 0xb7.



    문자열과 nonce가 포함된 목록의 경우:

    if the total payload of a list (i.e. the combined length of all its items being RLP encoded) is 0-55 bytes long, the RLP encoding consists of a single byte with value 0xc0 plus the length of the list followed by the concatenation of the RLP encodings of the items. The range of the first byte is thus 0xc0, 0xf7.



    이것은 우리가 가질 것이라는 것을 의미합니다:

    [
      0xC0
        + 1 (a byte for string length) 
        + 20 (string length itself) 
        + 1 (nonce), 
      0x80
        + 20 (string length),
      <20 byte string>,
      <1 byte nonce>
    ]
    


    한마디로: [0xD6, 0x94, <address>, 0x01] . 우리는 다음을 통해 찾을 수 있는 이 배열의 압축된 버전의 keccak256를 찾아야 합니다.

    web3.utils.soliditySha3(
      '0xd6',
      '0x94',
      // <instance address>,
      '0x01'
    )
    

    soliditySha3가 아닌 sha3와 다른 점은 Solidity처럼 매개변수를 인코딩 압축한다는 점입니다. 나중에 해싱. 결과 다이제스트의 마지막 20바이트가 계약 주소가 됩니다! destroy 함수를 호출하는 방법은 위와 동일합니다.

    좋은 웹페이지 즐겨찾기