## 0x00:背景介绍 昨天,从DeFiHackLabs的[漏洞仓库](https://github.com/SunWeb3Sec/DeFiHackLabs/blob/014e23d0ebc9c8563e772d27672f05ed2063b36f/src/test/2024-10/AIZPTToken_exp.sol#L20)中,了解到BNB Smart Chain上的ERC314标准代币AIZPT的攻击事件。 攻击Tx:https://bscscan.com/tx/0x5e694707337cca979d18f9e45f40e81d6ca341ed342f1377f563e779a746460d 目前找不到关于AIZPT代币的相关背景,猜测是一个土狗项目。 <br> ## 0x01:攻击流程 ![image-20241007100651497](https://hackmd.io/_uploads/SkkC_MFi1x.png) <br> 首先,攻击者通过PancakeSwap利用闪电贷服务借了8000 WBNB,然后兑换成8000 BNB。 ![image-20241007094321454](https://hackmd.io/_uploads/B18GFGKi1e.png) <br> 随后,攻击者用8000 BNB调用AIZPT代币合约的`buy`函数去swap,攻击者得到805,227,658个AIZPT代币。(`buy`函数是通过`receive()`回调函数内部进行调用的) ![image-20241007094953854](https://hackmd.io/_uploads/Sk8QtfKsyl.png) <br> 接着,攻击者大量调用`sell`函数(由transfer内部调用),每次调用将一部分的AIZPT代币swap回BNB。 ![image-20241007095917533](https://hackmd.io/_uploads/H1HNKfYjJx.png) 调用`sell`函数swap成的BNB数量,会逐渐减少:`2431 -> 1473 -> 960 -> 660 ……` 但攻击者总共得到了8039个BNB,偿还8004.1个给闪电贷后,获利34.9个BNB。 <br> ## 0x02:漏洞分析 在上述过程中,攻击者用8000 BNB购买成AIZPT代币,再将AIZPT代币出售回BNB,最终多了39个BNB。这其中到底发生了什么?让我们深入合约代码的`buy`和`sell`函数去看看 #### buy函数: ```solidity function buy() internal { require(tradingEnable, 'Trading not enable'); // 用户发送的BNB数量 uint256 swapValue = msg.value; // 根据恒定乘积公式,计算用户得到的AIZPT代币数量 uint256 token_amount = (swapValue * _balances[address(this)]) / (address(this).balance); require(token_amount > 0, 'Buy amount too low'); // 用户获得1/2的AIZPT代币 uint256 user_amount = token_amount * 50 / 100; uint256 fee_amount = token_amount - user_amount; _transfer(address(this), msg.sender, user_amount); // 另外的1/2则是发送到feeReceiver, 应该是项目方的地址 _transfer(address(this), feeReceiver, fee_amount); emit Swap(msg.sender, swapValue, 0, 0, user_amount); } ``` 代码逻辑很简单,根据恒定乘积公式进行swap,然后转移代币。1/2的代币发送到项目方地址,这应该是项目方的盈利手段。 #### sell函数: ```solidity function sell(uint256 sell_amount) internal { require(tradingEnable, 'Trading not enable'); // 根据恒定乘积公式,计算用户得到的BNB数量 uint256 ethAmount = (sell_amount * address(this).balance) / (_balances[address(this)] + sell_amount); require(ethAmount > 0, 'Sell amount too low'); require(address(this).balance >= ethAmount, 'Insufficient ETH in reserves'); // 将输入的AIZPT代币分成两份 uint256 swap_amount = sell_amount * 50 / 100; uint256 burn_amount = sell_amount - swap_amount; // 一份转移回合约 _transfer(msg.sender, address(this), swap_amount); // 一份进行销毁 _transfer(msg.sender, address(0), burn_amount); // 发送BNB给用户 payable(msg.sender).transfer(ethAmount); emit Swap(msg.sender, 0, sell_amount, ethAmount, 0); } ``` 在sell函数中,会把1/2的AIZPT代币销毁,但是发送给用户的BNB数量却是以所有的AIZPT代币数量进行计算的。这就是攻击发送的根本原因,AIZPT数量随着调用变少,恒定乘积公式将不再恒定。以至于后续用AIZPT兑换成的BNB将比预期更多,直至把池子掏空。 <br> ## 0x03:总结 该攻击事件的根本原因是:AIZPT兑换BNB时,恒定乘积公式出现问题,导致可以获得更多的BNB。在对攻击事件分析时,当站在攻击者的角度,会发现更多的攻击细节和疑惑点: 1. 为什么是8000 BNB?是通过计算得来的吗?如何得到最优解呢? 2. 每次调用sell,为什么是发送3000k个AIZPT代币?是否是考虑迭代次数的问题,以至于可以在一次交易中执行完成?