KienDT

Talk is cheap. Show me the code.

The Ethernaut writeups: 18 - Magic Number

Hi mọi người!

Đã rất lâu rồi chúng ta mới có dịp quay lại với chuỗi bài Ethernaut, giờ đây đã có thêm 8 bài mới. Chúng ta hãy lần lượt đi tìm lời giải cho các bài toán mới nhé!

18. Magic Number

Nhiệm vụ: Viết một contract siêu ngắn tối đa 10 opcodes để trả về con số được mệnh danh là The Meaning of Life - con số 42.

Về con số 42, bạn có thể đọc thêm tại đây

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

contract MagicNum {

  address public solver;

  constructor() public {}

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

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

Phân tích

  • Đây là một bài tập rất khó nhằn nếu ta chỉ làm việc ở tầng high-level với solidity code.
  • Với giới hạn tối đa 10 opcodes, không có cách nào khác chúng ta sẽ phải viết contract bằng raw-bytecode.

Về bytecode và opcode

  • Khi ta viết contract thì viết bằng Solidity, nhưng khi compile ra thì sẽ ra bytecode, tức dạng 0x6080604052348015600f57600080fd5b5....
  • Smart contract chạy trên Ethereum Virtual Machine (EVM). EVM sẽ đọc bytecode - tức contract đã được compile.
  • opcodebytecode được biểu diễn dưới dạng assembly code để có thể dễ đọc & biểu diễn logic hơn. Mỗi opcode là một byte - tức 2 ký tự hexa. Ta có thể tham khảo bảng opcodes tại đây
  • Contract khi được deploy sẽ gồm có 2 phần: creation codesruntime codes.
    • creation codes chính là phần constructor, nó sẽ được thực hiện để khởi tạo contract và trả về địa chỉ của contract. Phần creation bytecode không lưu trên EVM.
    • runtime codes sẽ được copy vào memory và lưu trữ trên EVM tại địa chỉ đã được deploy bên trên.

contract-creation

Vậy tóm lại ta sẽ cần một chuỗi gồm:

  • runtime codes với tối ra 10 opcodes trả về giá trị 42
  • creation codes để deploy runtime codes bên trên

EVM = Stack Machine

EVM là một stack machine, các phép toán được đưa vào dưới dạng hậu tố và thực hiện theo quy tắc Last In First Out của stack.

Vì thế khi tạo chuỗi opcodes ta cũng sẽ đưa nó vào chuỗi theo cấu trúc stack.

Solution

Tạo runtime bytecode

Ta cần trả về giá trị 42 với không quá 10 opcodes.

# Stack OPCODE Ý nghĩa bytecode
00 <empty> PUSH1(60) 2a push 2a (hex) = 42 (dec) vào stack 602a
02 2a PUSH1(60) 00 push 00 vào stack 6000
05 00, 2a MSTORE(52) mstore(0, 2a), lưu trữ 2a = 42 vào vị trí 0 trong memory 52
06 <empty> PUSH1(60) 20 push 20 (hex) = 32 (dec) vào stack (vì mỗi slot nhớ là 32 bytes) 6020
08 20 PUSH1(60) 00 push 00 vào stack 6000
10 00, 20 RETURN(f3) return(0, 20), trả về 32 bytes ở vị trí 0 f3

Ta được chuỗi runtime bytecodes602a60005260206000f3 có độ dài 10 opcodes.

Tạo creation bytecode

  • Trong mỗi slot nhớ có 32 bytes, mà runtime codes của ta có độ dài 10 bytes, do vậy ta sẽ trả về 10 bytes kể từ vị trí 22 trong memory cho EVM lưu trữ.
Stack OPCODE Ý nghĩa bytecode
<empty> PUSH10(69) 602a60005260206000f3 push 10 bytes của runtime codes vào stack 69602a60005260206000f3
602a60005260206000f3 PUSH1(60) 00 push 0 vào trong stack 6000
00, 602a60005260206000f3 MSTORE(52) lưu trữ runtime codes vào vị trí 00 trong memory 52
<empty> PUSH1(60) 0a push 0a (hex) = 10 (dec) vào stack 600a
0a PUSH1(60) 16 push 16 (hex) = 22 (dec) vào stack 6016
16, 0a RETURN(f3) trả về 10 bytes từ vị trí 22 f3

Ta được chuỗi creation codes69602a60005260206000f3600052600a6016f3

Deploy & Set Solver

  • Trên chrome console chạy lệnh để deploy contract:
sendTransaction({ from: player, data: `69602a60005260206000f3600052600a6016f3` });

transction hoàn thành sẽ tạo ra một contract mới, trong trường hợp của mình là 0x0F9F67f93D846fb2406De62195A8A50bd901d548

  • kiểm tra xem có đúng contract trả về 42 không
parseInt(await web3.eth.call({ to: '0x0F9F67f93D846fb2406De62195A8A50bd901d548', data: '0x' }));
> 42
  • gọi hàm setSolver để set địa chỉ solver là contract ta mới tạo ra
contract.setSolver('0x0F9F67f93D846fb2406De62195A8A50bd901d548');
  • Submit & all done!

completed

Tham khảo