开发者怎样理解智能合约攻击:公开函数、状态机与金融不变量

4次阅读
没有评论

如果你是开发者,想学习智能合约攻击的原理,不必先把它理解成“黑客技巧”。更合适的理解是:

智能合约其实就是一个公开 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。

更关键的是把协议自己的规则写成属性测试:无论公开函数以什么顺序、由谁、在什么边界输入下调用,核心资产不变量都不能被破坏。

最后总结

智能合约攻击通常不是“黑客突破了区块链”,而是攻击者利用公开代码允许的行为,把协议推到了开发者没有证明安全的状态。

对开发者来说,智能合约安全可以理解成:

金融系统 + 分布式系统 + 数学证明 + 软件工程

代码可能只有几百行,却承载很大价值;真正困难的往往正是那些归零、舍入、缓存和组合调用的边界状态。

公开资料

本文资料核验于 2026 年 7 月 21 日。公开事件的损失、追回进展和技术归因可能随着官方复盘继续更新。

正文完
 0
bdspAdmin
版权声明:本站原创文章,由 bdspAdmin 于2026-07-21发表,共计2972字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)