Ethernaut Level 18: MagicNumber

Solidity Code

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

contract MagicNum {

  address public solver;

  constructor() public {}

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

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

Requirements

  1. Create a contract that responds to whatIsTheMeaningOfLife() with the right number.
  2. The code needs to be 10 opcodes at most.

Concepts

Hack

There are 2 parts to creating the code, the initialization code and runtime code. Let's first solve the runtime code. Returning values is done by the RETURN opcode. It takes 2 arguments, position and length/size of data. This implies that we first store our data in memory before we can return it.

Solution

Runtime Code

OPCODEbytecodeexplanation
PUSH1 2a602aPush 2a (42) to stack
PUSH1 006000Push 00 to stack
MSTORE52Store 2a at memory position 0

Then, we can return the 2a value.

OPCODEbytecodeexplanation
PUSH1 206020Push 0x20 (32) to stack
PUSH1 006000Push 00 to stack
RETURNf3Return 32 bytes stored in memory position 0

The resulting opcode sequence is 0x602a60005260206000f3. This is exactly 10 bytes long.

Initialization Code

This sequence of opcodes is responsible for saving the runtime opcodes to memory and returning them to the EVM. Note that MSTORE stores the 10 bytes long runtime code as a 32 bytes long string. This means that the sequence is padded with 22 zeroes from the left.

OPCODEbytecodeexplanation
PUSH10 602a60005260206000f369602a60005260206000f3Push runtime code to stack
PUSH1 006000Push 00 to stack
MSTORE52Store runtime code at memory position 0
PUSH1 a600aPush a (10) to stack
PUSH1 166016Push 16 (22) to stack
RETURNf3Return 10 bytes at position 22

The complete opcode sequence for contract creation is 69602a60005260206000f3600052600a6016f3.

We then deploy the code using web3.js.

let bytecode = "69602a60005260206000f3600052600a6016f3"
let tx = await web3.eth.sendTransaction({from: player, data: bytecode})
let solverAddress  = tx.contractAddress

Finally,

await contract.setSolver(solverAddress);

Done!