4700 万美元损失,Xn00d 合约漏洞攻击事件分析
基础知识
ERC777
ERC777 是 ERC20 标准的高级代币标准,要提供了一些新的功能:运营商及钩子。
- 运营商功能。通过此功能能够允许第三方账户代表某一合约或者地址
进行代币的发送交易 - 钩子功能。给该合约或者地址对其账户中的代币更多的控制权,防止运营商作恶,同时可以定义地址或者合约对某些代币进行控制以及提供拒绝接收某些代币功能
举个简单的栗子解释这两个功能(更多详细内容可阅读参考部分提供的链接)。
运营商功能:某区块链公司的老板使用某种基于 ERC777 的 A 代币作为工资发放,公司账户一共有 500 万的 A 代币,而经过人事的计算,本月共应发总工资为 200 万代币 A,那么公司老板就可以将财务的以太坊钱包地址作为运营商,并授权 200 万 A 代币的使用权限,那么财务便有权限使用公司地址 500 万中的 200 万 A 代币,并且对于公司来说,其余的金额是安全的。
钩子功能:可用来限制运营商的一些权限。如公司老板可以通过钩子规定运营商对公司账户中授权的 200 万 A 代币的走向做一个限制,规定只能转向某些已经标名备注了的地址,例如公司全体员工地址,以及限定单笔可发送的最大值等等。
基本知识
攻击的背景
攻击发生时间 2022-10-26 12:46:59
区块高度:15826380
攻击者: 0x8Ca72F46056D85DB271Dd305F6944f32A9870FF0
受害合约: 0x9C5A2A6431523fBBC648fb83137A20A2C1789C56
pair 合约: 0x5476DB8B72337d44A6724277083b1a927c82a389 为 n00dToken 与 WETH 的 pair 合约,为 uniswapV2 的 pair 合约。
SushiBar ERC20 合约地址:0x3561081260186E69369E6C32F280836554292E08
n00dToken ERC777 合约地址: 0x2321537fd8EF4644BacDCEec54E5F35bf44311fA
这两个合约是什么关系?SushiBar 合约中有个 sushi 的合约变量,指向的是 n00dToken,
contract n00dToken is ERC777 {
constructor(uint256 initialSupply, address[] memory defaultOperators)
ERC777("n00dle", "n00d", defaultOperators)
{
_mint(msg.sender, initialSupply, "", "");
}
}
攻击最终获利多少呢?
通过上图,从资产转移来看,最终的攻击效果是,黑客攻击了 SushiBar 合约,从 SushiBar 合约中提取了 42,752 价值的 n00dToken,用 42,752 的 n00dToken 在 Uniswap 中换了价值 29,388 的 WETH,最后将 29,388 的 WETH 换成了 ETH.
sushiBar 合约的 enter 函数代码
功能:将 n00dToken 兑换成 bar 币。
输入:要兑换的 n00dToken 的数量。
输出:兑换出 bar 币到 msg.sender 中。
函数执行过程中
mint 出凭证,得到发送者的 noodToken。(注意这里的顺序,是先 mint,后得到 noodToken,这也是漏洞出现的原因)
调用 enter 后,sushiBar 合约会 mint 出一定数量的凭证,然后从 noodToken 中转移_amount 数量的 noodToken 到 sushiBar 合约中。
// Enter the bar. Pay some SUSHIs. Earn some shares.
function enter(uint256 _amount) public {
uint256 totalSushi = sushi.balanceOf(address(this));
uint256 totalShares = totalSupply();
if (totalShares == 0 || totalSushi == 0) {
_mint(msg.sender, _amount);
} else {
uint256 what = _amount.mul(totalShares).div(totalSushi);
_mint(msg.sender, what);
}
sushi.transferFrom(msg.sender, address(this), _amount);
}
// enter 的计算方式: uint barSwapCount = enterAmount.mul(bar.totalSupply()).div(n00d.balanceOf(BARADDR));
假设输入的 n00dToken 币数量为 n,可兑换出的 bar 的数量为 b,bar 合约的 totalSupply 为 T,bar 合约的 n00dToken 余额(n00d.balanceOf(BARADDR))为 B。则有
b=n*T/B (1)
enter 完成后,T 的值和 B 的值都会发生变化,各自的变量函数如下:
T‘ = T + b (2)
B’ = B + n (3)