如果你是开发者,想学习智能合约攻击的原理,不必先把它理解成“黑客技巧”。更合适的理解是:
智能合约其实就是一个公开 API、状态机和金融逻辑系统。攻击者不是破解系统,而是在寻找开发者没有定义清楚的状态。
这和普通 Web 后端漏洞很像。
Web:用户请求 -> API -> 数据库状态变化 -> 返回结果
DeFi:钱包地址(EOA)-> 调用合约函数 -> 修改链上状态 -> 资产变化
区别在于,Web 后端通常还能回滚、管理员也可以修复;区块链上的合约部署后,大部分逻辑不可改变,资产还直接由程序管理。所以一个数学错误有时就等于一条错误的提款规则。
本文只讨论公开案例中的开发原理、审计视角和防御思路,不提供可用于真实协议的利用代码。安全复现应限制在本地分叉、测试网或取得书面授权的环境中。
为什么普通钱包也能触发漏洞
很多人会问:攻击者不是应该先拿到“黑客权限”吗?
以太坊的基本模型不是这样。普通钱包是 EOA,它可以向合约的公开函数发送交易:
function deposit() external {
}
只要函数没有限制调用者,例如:
require(msg.sender == owner);
那么用户、机器人和攻击者都能调用它。这并不代表设计有问题,存款、兑换、提款等核心功能本来就必须向用户开放。
真正的问题不是“攻击者进入了系统”,而是系统公开允许某件事,开发者却没有预料到某一组输入、调用顺序或状态组合会破坏业务规则。
用 ATM 理解边界条件
把协议想成一台只有存钱、取钱和查余额功能的 ATM。
正常情况下:
余额 = 100
取出 100
余额 = 0
如果程序只写了:
balance = balance - amount
却没有检查 balance >= amount,就可能让异常输入进入开发者没有定义好的状态。智能合约漏洞中,攻击者往往不是破解 ATM,而是在正常按钮上输入了系统没有正确处理的数据,或以系统没有证明安全的顺序连续按下多个按钮。
三类最值得开发者关注的问题
1. 数学错误:整数并不会自动等于“安全”
旧版 Solidity 的无符号整数会发生回绕。以 8 bit 为例:
255 + 1 = 0
在旧编译器中,类似 quantity * price 的价格计算一旦超过类型上限,结果可能从极大值变成极小值。Truebit 公开事件的核心教训之一,就是旧版算术回绕可以使原本应昂贵的铸造路径产生错误价格。
现代 Solidity 0.8.x 默认会检查大多数算术溢出,但这不意味着数学风险消失。开发者仍要审查 unchecked、类型转换、乘除顺序和边界值。
2. 状态机错误:主状态和缓存状态没有一起重置
Yearn yETH 很适合用传统后端的缓存不一致来理解。
想象数据库里账户余额已经清零,但缓存里仍保留旧余额。下一次用户开户并存入极小金额时,程序错误地读取旧缓存,账户价值就可能被放大。
类似地,yETH 事件涉及总供应量归零后,内部虚拟余额缓存没有与主状态同步清理。问题不在于某个变量“写错了一行”,而在于协议没有证明完整生命周期都一致:创建、更新、归零、再初始化,每一步都必须维护同一组不变量。
这类问题在传统软件里常被叫作:
- stale state
- cache invalidation
- inconsistent state
3. 精度错误:舍入方向会变成经济规则
链上金融计算通常使用整数单位处理小数。例如显示为 1.234567 的金额,底层可能表示为 1234567 个最小单位。
乘法、除法、比例和份额计算必然会遇到不能整除的结果:
100 / 3 = 33.333...
整数计算后可能只留下 33
一次舍入的误差似乎很小,但如果每一步都偏向同一方,循环兑换、存取或批量交易会把偏差放大。Balancer V2 的公开分析提醒我们,风险不只是“数学算错”,而是舍入方向在复杂调用组合中长期偏向攻击者。
闪电贷为什么常出现在攻击新闻中
很多经济攻击需要很大的临时本金,例如操纵池子价格。闪电贷把借入、操作、还款放进同一笔交易:
借入临时资金
-> 进行一组公开调用
-> 偿还借款
-> 保留剩余收益
如果中途失败,整笔交易会回滚。闪电贷只提供临时资金和原子执行条件,它不授予管理员权限,也不是漏洞本身。协议的数学和状态转换正确时,资金再多也无法越过检查。
攻击者真正寻找的三个地方
边界条件
审计时不能只测试普通金额,还要覆盖:
amount = 0
amount = 1
amount = max(uint256)
balance = 0
totalSupply = 0
状态转换
给协议画出完整状态图,而不是只看单个函数。例如一个池子从 Active 到 Closed 后,是否存在重新初始化路径?关闭后再次开启时,所有关联缓存、份额和权限是否同步恢复到正确状态?
不变量
金融协议最重要的是不变量。例如 AMM 常见的简化表达为:
x * y = k
实际协议的不变量会复杂得多,但核心问题不变:一次交易或一组交易后,资产守恒、份额价格、赎回上限和抵押率是否仍成立?如果不成立,真实资产就可能按错误账本流出。
现代审计为什么更重视组合与经济模型
以前审计常优先寻找重入、权限控制和整数溢出。现在简单漏洞越来越少,更多风险出现在:
- 数学模型与精度边界;
- 多函数、多协议和多笔调用的组合;
- 归零、暂停、升级和再初始化等状态机路径;
- 经济激励是否允许小误差被重复放大。
自动化工具和 AI 可以帮助定位可疑的算术、状态路径和未验证合约,但它们不能替代对协议不变量和经济模型的证明。工具找到的是候选路径,开发者仍要回答:这条路径执行后,哪些资产规则会被破坏?
面向开发者的学习路线
第一阶段:理解 EVM 的数据与调用边界
先掌握 Solidity、calldata、storage、delegatecall 和 proxy。重点不是记语法,而是弄清数据在哪里、调用上下文如何切换、升级和委托调用会改变哪些假设。
第二阶段:阅读真实事件,但只在安全环境复现
DeFiHackLabs 收集了不少公开事件的 Foundry 分叉复现。阅读时不要只看最后一笔交易,而要倒推:协议原本想保证什么、攻击输入进入了哪个边界、哪一步状态没有同步、哪一个不变量先被打破。
第三阶段:把防御写成可执行检查
常见工具包括:
- 静态分析:Slither、Mythril;
- 模糊测试:Echidna;
- 形式化验证:Certora。
更关键的是把协议自己的规则写成属性测试:无论公开函数以什么顺序、由谁、在什么边界输入下调用,核心资产不变量都不能被破坏。
最后总结
智能合约攻击通常不是“黑客突破了区块链”,而是攻击者利用公开代码允许的行为,把协议推到了开发者没有证明安全的状态。
对开发者来说,智能合约安全可以理解成:
金融系统 + 分布式系统 + 数学证明 + 软件工程
代码可能只有几百行,却承载很大价值;真正困难的往往正是那些归零、舍入、缓存和组合调用的边界状态。
公开资料
- CertiK:Truebit Incident Analysis
- Yearn:yETH Exploit Discussion
- OpenZeppelin:Understanding the Balancer V2 Exploit
- Chainalysis:Unverified Smart Contracts Are a Preferred Target
- a16z crypto:Runtime enforcement
- DeFiHackLabs:公开事件复现索引
本文资料核验于 2026 年 7 月 21 日。公开事件的损失、追回进展和技术归因可能随着官方复盘继续更新。




