c0mpos3r

[Ethernaut] 18. MagicNumber WriteUp 본문

Web3/Hacking

[Ethernaut] 18. MagicNumber WriteUp

음대생 2025. 8. 9. 01:06

1. 문제 분석

To solve this level, you only need to provide the Ethernaut with a Solver, a contract that responds to whatIsTheMeaningOfLife() with the right 32 byte number.
Easy right? Well... there's a catch.
The solver's code needs to be really tiny. Really reaaaaaallly tiny. Like freakin' really really itty-bitty tiny: 10 bytes at most.
Hint: Perhaps its time to leave the comfort of the Solidity compiler momentarily, and build this one by hand O_o. That's right: Raw EVM bytecode. Good luck!

 

이 레벨을 해결하기 위해, 이더 러나에게 솔버에게만 제공하면 32 바이트 번호가있는 WhathisthemeaningofLife ()에 응답하는 계약입니다.

쉬운? 글쎄 ... 캐치가 있습니다솔버의 코드는 정말 작아야합니다. 정말로 작은. Freakin '처럼 정말로 정말 작은 비트 : 최대 10 바이트

힌트 : 아마도 Solidity 컴파일러의 편안함을 잠시 안락하고 O_O 로이 제품을 구축 할 시간입니다. 맞습니다 : RAW EVM 바이트 코드

1-1. Code

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

contract MagicNum {
    address public solver;

    constructor() {}

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

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

whatIsTheMeaningOfLife() 함수 호출 시 42를 반환하는 컨트랙트를 만드는 것입니다. 하지만 핵심 제약사항이 있다.

  • 런타임 바이트코드가 최대 10개의 opcode로 제한
  • Solidity 컴파일러 없이 Raw EVM 바이트코드로 직접 작성해야 함

1-2. Magic Number Contract 분석 

EVM ByteCode 구조 이해

Contract ByteCode의 구성

  • 배포 코드 (Creation Code): 컨트랙트 배포 시에만 실행되는 초기화 코드 (constructor)
  • 런타임 코드 (Runtime Code): 실제 컨트랙트에 저장되어 함수 호출 시 실행되는 코드

문제에서 제한하는 것은 런타임 코드opcode 개수입니다.

런타임 코드 작성 (42 반환)

필요한 작업 순서

  1. 42를 Stack에 Push
  2. Memory에 42 저장
  3. Memory에서 42를 반환

필요한 OPCode

1. PUSH1 0x2a      // 42(0x2a)를 스택에 올림
2. PUSH1 0x80      // 메모리 위치 0x80을 스택에 올림  
3. MSTORE          // 메모리[0x80]에 42를 저장
4. PUSH1 0x20      // 반환할 크기 32바이트를 스택에 올림
5. PUSH1 0x80      // 메모리 시작 위치 0x80을 스택에 올림
6. RETURN          // 메모리[0x80]부터 32바이트를 반환

 

Bytecode 변환

602a   // PUSH1 0x2a
6080   // PUSH1 0x80  
52     // MSTORE
6020   // PUSH1 0x20
6080   // PUSH1 0x80
f3     // RETURN

최종 런타임 코드: 0x602a60805260206080f3

해결 과정

  1. 런타임 코드 작성: 0x602a60805260206080f3 (10바이트, 6개 opcode)
  2. 컨트랙트 배포: Constructor를 사용한 간단한 방법 활용
  3. Solver 등록: 배포된 컨트랙트 주소를 setSolver(address) 함수에 전달

핵심 포인트

  • EVM opcode 직접 조작: Solidity 컴파일러 없이 Raw 바이트코드 작성
  • 메모리 구조 이해: MSTORE/RETURN의 오프셋과 크기 관리
  • 배포 vs 런타임 코드 구분: 제약사항이 런타임 코드에만 적용됨을 이해
  • Constructor 활용: 배포 코드를 직접 작성하지 않고도 런타임 코드만 남길 수 있는 방법

1-3. Reference 분석

배포 코드 (Creation Code)

  • Contract를 만들 때만 실행되는 코드
  • Constructor가 여기에 해당
  • 실행 후 사라짐 (블록체인에 저장되지 않음)

런타임 코드 (Runtime Code)

  • Contract 주소에 실제로 저장되는 코드
  • 함수를 호출할 때 실행되는 코드
  • 문제에서 제한하는 것이 바로 이것!

Creation Bytecode란:

  • SmartContract를 BlockChain에 배포할 때 사용되는 ByteCode
  • Contract의 생성자(constructor) Code와 Runtime Code를 포함
  • 배포 트랜잭션에서 data 필드에 포함됨

구성요소:

  1. Constructor bytecode: 초기화 로직 실행
  2. Runtime bytecode: 실제 컨트랙트 기능 코드
  3. Constructor parameters: 생성자 매개변수 (ABI 인코딩됨)

동작 과정:

  1. Creation bytecode 실행 → 초기화
  2. Runtime bytecode를 블록체인에 저장
  3. 이후 컨트랙트 호출 시 runtime bytecode 사용

2. Solving

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Script, console} from "forge-std/Script.sol";
import { MagicNum } from "../src/MagicNumber.sol";

contract Attack {
    constructor() {
        assembly {
            mstore(0, 0x602a60805260206080f3)
            return(0x16, 0x0a)
        }
    }
}

contract CounterScript is Script {
    address public instance = 0x3332959970ffD99F06fE8671f10021D79d6fCbB6;

    function run() public {
        vm.startBroadcast();

        MagicNum target = MagicNum(instance);

        Attack attack = new Attack();
        address payload = address(attack);

        target.setSolver(payload);

        vm.stopBroadcast();
    }
}

3. 결론

솔리디티 코드는 겉모습일 뿐이며, 스마트 컨트랙트의 실제 동작과 보안은 EVM 바이트코드 수준에서 결정된다.

따라서 이 저수준(low-level) 동작을 이해하는 것이 가스비 최적화, 숨은 취약점 발견, 그리고 EVM의 완전한 기능 활용을 위한 핵심이다.

'Web3 > Hacking' 카테고리의 다른 글

[Ethernaut] 21. Shop WriteUp  (0) 2025.08.20
[Ethernaut] 19. Alien Codex WriteUp  (2) 2025.08.09
[Ethernaut] 17. Recovery WriteUp  (1) 2025.08.08
[Ethernaut] 16. Preservation WriteUp  (1) 2025.08.08
[Ethernaut] 15. Naught Coin WriteUp  (1) 2025.08.07