智能合约审计系列————1、整型溢出
字数 1704 2025-08-22 12:22:30
智能合约审计:整型溢出漏洞详解
1. 智能合约基础概念
1.1 合约与智能合约
合约是双方当事人之间的"合意",是一种以发生、变更、担保或消灭某种法律关系为目的的协议。
智能合约由Nick Szabo在1993年提出,定义为:
- 一套数字形式定义的承诺
- 包括合约参与方可以执行这些承诺的协议
智能合约特点:
- 数字形式:写入计算机可执行代码
- 自动执行:当预设条件满足时自动执行
- 不可篡改:一旦部署到区块链上便无法修改
1.2 Solidity基础架构
智能合约使用Solidity编写,基本架构如下:
pragma solidity ^版本号;
contract 合约名称 {
// 状态变量
address public owner;
uint256 public balance;
// 构造函数
constructor() public {
owner = msg.sender;
}
// 函数
function 函数名(参数) public 修饰器 returns (返回值) {
// 函数体
}
// 修饰器
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
}
2. Solidity数据类型
2.1 基本数据类型
-
布尔型(bool)
- 取值:true/false
- 运算符:! && || == !=
-
整型
- 有符号:int/int8到int256
- 无符号:uint/uint8到uint256
- 默认:int256/uint256
-
地址(address)
- 20字节,160位
- 可转换为uint160
-
数组
- 定长字节数组:bytes1到bytes32
- 动态字节数组:bytes, string
-
字符串(string)
- UTF-8编码的动态尺寸字符串
- 无结束符
-
映射(mapping)
- 键值对存储结构
- 语法:mapping(_keyType => _valueType)
2.2 修饰器(Modifiers)
用于改变函数行为,常用于权限检查:
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
function close() public onlyOwner {
selfdestruct(owner);
}
2.3 函数结构
function 函数名(参数列表) 可见性 修饰器 returns (返回值类型) {
// 函数体
}
3. 审计工具介绍
3.1 Etherscan
- 以太坊区块链浏览器
- 通过合约地址查看源码
- 功能:
- 合约代码验证
- 交易记录查询
- 合约交互界面
3.2 Remix IDE
在线Solidity开发环境:
- 代码编辑
- 编译
- 部署
- 调试
3.3 Sublime编辑器
- 支持Solidity语法高亮
- 便于代码审计
4. 整型溢出漏洞详解
4.1 溢出原理
当算术运算结果超出数据类型表示范围时:
- 上溢:超过最大值 → 归零或变为极小值
- 下溢:低于最小值 → 变为极大值
以太坊虚拟机(EVM)为整数指定固定大小类型,如uint8范围[0,255],存储256会变为0。
4.2 乘法溢出案例
CVE-2018-10299漏洞代码:
function batchTransfer(address[] _receivers, uint256 _value) public {
uint cnt = _receivers.length;
uint256 amount = cnt * _value; // 未检查乘法溢出
require(cnt > 0 && cnt <= 20);
require(_value > 0 && balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
for (uint i = 0; i < cnt; i++) {
balances[_receivers[i]] += _value;
}
emit Transfer(msg.sender, _receivers, amount);
}
攻击方式:
- 传入_value = 2^255
- amount = 2 * 2^255 = 2^256 → 溢出为0
- 绕过余额检查,实现代币增发
4.3 减法下溢案例
function distribute(address[] addresses, uint256 _value) public onlyOwner {
for (uint i = 0; i < addresses.length; i++) {
balances[owner] -= _value; // 未检查下溢
balances[addresses[i]] += _value;
}
}
攻击方式:
- 转出总额 > owner余额
- balances[owner]下溢变为极大值
4.4 加法上溢案例
GEMCHAIN漏洞代码:
function mintToken(address target, uint256 mintedAmount) public onlyOwner {
balances[target] += mintedAmount; // 未检查加法溢出
totalSupply += mintedAmount;
emit Transfer(0, owner, mintedAmount);
emit Transfer(owner, target, mintedAmount);
}
攻击方式:
- 连续铸币使余额超过uint256最大值
- 余额变为极小值
5. 溢出漏洞防范措施
5.1 使用SafeMath库
OpenZeppelin提供的SafeMath库:
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) return 0;
uint256 c = a * b;
require(c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0);
return a / b;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a);
return c;
}
}
5.2 正确使用示例
import "./SafeMath.sol";
contract SafeContract {
using SafeMath for uint256;
mapping(address => uint256) balances;
function safeTransfer(address to, uint256 value) public {
balances[msg.sender] = balances[msg.sender].sub(value);
balances[to] = balances[to].add(value);
}
}
5.3 其他防护措施
- 输入验证:检查用户输入是否在合理范围内
- 使用较新Solidity版本(>=0.8.0内置溢出检查)
- 严格测试边界条件
- 限制循环次数和计算规模
6. 审计要点总结
- 检查所有算术运算:特别是涉及用户可控输入的运算
- 验证SafeMath使用:确认所有+-*/运算都使用SafeMath
- 检查边界条件:特别是最大值、最小值附近的行为
- 审查铸币函数:确认总量控制和溢出防护
- 测试极端情况:使用极大/极小值进行测试
7. 参考资料
- OpenZeppelin SafeMath: https://github.com/OpenZeppelin/openzeppelin-solidity
- 漏洞合约示例:
- https://github.com/sec-bit/awesome-buggy-erc20-tokens
- https://github.com/peckshield/vuln_disclosure
- https://github.com/BlockChainsSecurity/EtherTokens
- 以太坊浏览器: https://etherscan.io
- Remix IDE: https://remix.ethereum.org