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
- Create a contract that responds to whatIsTheMeaningOfLife() with the right number.
- 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
OPCODE | bytecode | explanation |
PUSH1 2a | 602a | Push 2a (42) to stack |
PUSH1 00 | 6000 | Push 00 to stack |
MSTORE | 52 | Store 2a at memory position 0 |
Then, we can return the 2a value.
OPCODE | bytecode | explanation |
PUSH1 20 | 6020 | Push 0x20 (32) to stack |
PUSH1 00 | 6000 | Push 00 to stack |
RETURN | f3 | Return 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.
OPCODE | bytecode | explanation |
PUSH10 602a60005260206000f3 | 69602a60005260206000f3 | Push runtime code to stack |
PUSH1 00 | 6000 | Push 00 to stack |
MSTORE | 52 | Store runtime code at memory position 0 |
PUSH1 a | 600a | Push a (10) to stack |
PUSH1 16 | 6016 | Push 16 (22) to stack |
RETURN | f3 | Return 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!