Ethernaut: 18. 매직 넘버

Play the level

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

contract MagicNum {

  address public solver;

  constructor() public {}

  function setSolver(address _solver) public {
    solver = _solver;
  }

  /*
    ____________/\\\_______/\\\\\\\\\_____        
     __________/\\\\\_____/\\\///////\\\___       
      ________/\\\/\\\____\///______\//\\\__      
       ______/\\\/\/\\\______________/\\\/___     
        ____/\\\/__\/\\\___________/\\\//_____    
         __/\\\\\\\\\\\\\\\\_____/\\\//________   
          _\///////////\\\//____/\\\/___________  
           ___________\/\\\_____/\\\\\\\\\\\\\\\_ 
            ___________\///_____\///////////////__
  */
}


이 수준에서 우리는 10개의 opcode로 42를 반환하는 계약을 작성해야 합니다. 다음 계약서를 작성할 때:

contract Solver { 
  function whatIsTheMeaningOfLife() public pure returns (uint) {
    return 42;
  }
}


배포하고 함수를 호출하고 Remix IDE 디버거에서 opcode를 확인하면 10개가 넘는 opcode가 있음을 알 수 있습니다. 따라서 어떻게든 거기에 자체 어셈블리를 작성해야 합니다. 이를 위해 우리는 컴파일러가 되어 베어본 계약 생성을 수행합니다. 계약을 만드는 일부 데이터를 사용하여 처리할 트랜잭션0x0입니다! 계약 생성 코드는 초기화 코드와 런타임 코드의 두 부분으로 구성됩니다. 무엇을 해야할지 알고 있으므로 런타임을 먼저 실행해 보겠습니다. 어떻게든 42를 반환합니다!

런타임 코드



저는 학사 과정에서 어셈블리 x8086 수업을 들었을 때 모든 x8086 명령어가 포함된 많은 서류를 함께 가져와야 했던 일을 기억했습니다! 여기 opcode에 대한 문서는 https://www.ethervm.io/ 입니다. 확인할 수도 있습니다https://www.evm.codes/ .
  • CTRL+F를 눌러 "return"을 검색하고 먼저 RETURN opcode: RETURN <offset> <length>를 확인합니다. 분명히 메모리의 length에서 offset 바이트를 반환합니다. 따라서 먼저 메모리에 42를 저장해야 합니다.
  • CTRL+F "memory"로 related section 을 찾으면 3개의 명령이 있습니다. MSTORE 이 우리의 사용 사례에 적합하다는 것을 알았습니다. MSTORE <offset> <value> . 이제 이러한 명령어가 스택에서 읽는 실제 데이터를 제공해야 합니다. 참고: using MSTORE8이 작동하지 않았습니다.
  • CTRL+F "스택"하여 related section을 찾고 거기에서 PUSH1 이 우리에게 유용한 것을 찾습니다. 이 사람에게 논쟁을 제공하는 방법? 답은 다음과 같습니다.

  • Each opcode is encoded as one byte, except for the PUSH opcodes, which take a immediate value. All opcodes pop their operands from the top of the stack and push their result.



    계획은 다음과 같습니다.

    PUSH1 0x2A // our 1 byte value 42 = 0x2A
    PUSH1 0x80 // memory position 0x80, the first free slot
    MSTORE     // stores 0x2A at 0x80
    PUSH1 0x20 // to return an uint256, we need 32 bytes (not 1)
    PUSH1 0x80 // position to return the data
    RETURN     // returns 32 bytes from 0x80
    


    메모리 슬롯0x80은 매우 중요합니다. 나는 처음에 다른 더 작은 메모리 슬롯에 썼지만 내 솔루션은 받아들여지지 않았습니다. 처음 4개의 32바이트 슬롯이 예약되어 있는 것으로 나타났습니다! 자세한 내용은 https://docs.soliditylang.org/en/v0.8.13/internals/layout_in_memory.html을 참조하십시오.

    바이트코드 측면에서 우리는 명령 대신 실제 opcode와 함께 하나의 큰 덩어리로 연속적으로 작성된 이들 모두가 필요합니다. PUSH160이고, MSTORE52이고 RETURNF3입니다. 모든 것을 나란히 작성하면 다음과 같은 결과를 얻습니다. 60 2A 60 80 52 60 20 60 80 F3 ; 우리의 새로운 런타임 코드; 정확히 10바이트!

    초기화 코드



    그렇다면 위의 것을 런타임 코드로 사용하도록 EVM에 정확히 어떻게 지시합니까? 초기화 부분도 작성해야 합니다. contract creation section에서 다음을 볼 수 있습니다.

    The data payload of a transaction creating a smart contract is itself bytecode that runs the contract constructor, sets up the initial contract state and returns the final contract bytecode.



    아하, "최종 계약 바이트 코드를 반환"해야 합니다. 그래서 우리는 어떻게든 우리의 코드를 위와 같이 어떤 인덱스와 return 메모리에 넣어야 합니다. 이 지점에서:
  • CTRL+F를 "계약"하고 우연히 CODECOPY 명령을 찾았습니다. 메모리에 코드를 저장하는 것이 바로 필요한 것 같습니다. CODECOPY <destOffset> <offset> <length>offset 바이트가 있는 length의 코드를 destOffset의 메모리에 넣습니다. offset는 실제 바이트코드를 나타내므로 위 런타임 코드의 시작 인덱스가 됩니다. 그러나 초기화 코드 작성이 완료될 때까지는 런타임 코드가 뒤따르기 때문에 알 수 없습니다.
  • 반환 부분은 위와 동일합니다. RETURN <offset> <length> 여기서 offset는 런타임 코드의 인덱스이고 length는 길이이며 10바이트인 것으로 알고 있습니다.

  • 따라서 초기화 코드는 다음과 같습니다.

    PUSH1 0x0a // 10 bytes
    PUSH1 ;;;; // position in bytecode, we dont know yet
    PUSH1 0x00 // write to memory position 0
    CODECOPY   // copies the bytecode 
    PUSH1 0x0a // 10 bytes
    PUSH1 0x00 // read from memory position 0
    RETURN     // returns the code copied above
    


    이것을 바이트코드로 작성하면 12바이트인 60 0a 60 ;; 60 00 39 60 0a 60 00 F3가 됩니다. 따라서 더미;;;;는 12, 즉 0x0C이어야 합니다.

    계약 배포



    이더리움에서 0x0를 대상으로 하는 모든 트랜잭션은 계약 생성 트랜잭션이므로 다음과 같이 호출합니다.

    await web3.eth.sendTransaction({
      from: player,
      to: 0, // contract creation 
      data: '0x600a600C600039600a6000F3602a60805260206080F3' // bytecodes
    })
    


    콘솔에 반환된 객체는 모든 것이 잘 진행되면 contractAddress를 갖게 됩니다. https://rinkeby.etherscan.io/ 에서 확인하여 바이트코드가 올바른지 확인할 수 있으며 "계약"탭에서 "Switch To Opcodes 보기"버튼을 클릭하여 opcode를 볼 수 있습니다. 그런 다음 솔버를 이 컨트랙트 주소로 설정하고 제출하기만 하면 됩니다!

    좋은 웹페이지 즐겨찾기