<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      【漏洞分析】Paraluni 安全事件分析

      Paraluni 被黑分析

      前言

      Paraluni (平行宇宙)是新加坡 Parallel Universe 基金會發布的一個 基于幣安智能鏈的 DeFi 項目,更多相關內容見此處。在 2022 年 03 月 13 日,Paraluni 遭受黑客攻擊,損失約 170 萬美元。

      本次復現是參考學習了各位大佬的復現教程一步一步摸索記錄下來的,更多的是對攻擊過程中每個步驟的求證(根據這個,我們可以得知它做了什么),而對于一些啟發性的思路還有所欠缺(你怎么就知道要看這里?),在文末的引用文章中可以學習一下大佬們的思路。

      相關地址

      攻擊者地址:https://bscscan.com/address/0x94bc1d555e63eea23fe7fdbf937ef3f9ac5fcf8f

      攻擊交易:https://bscscan.com/tx/0x70f367b9420ac2654a5223cc311c7f9c361736a39fd4e7dff9ed1b85bab7ad54

      攻擊合約:https://bscscan.com/address/0x4770b5cb9d51ecb7ad5b14f0d4f2cee8e5563645

      UBT(ukraine bad token)代幣合約:https://bscscan.com/address/0xca2ca459ec6e4f58ad88aeb7285d2e41747b9134

      UGT(russia good token)代幣合約:https://bscscan.com/address/0xbc5db89ce5ab8035a71c6cd1cd0f0721ad28b508

      Masterchef 合約:https://bscscan.com/address/0xa386f30853a7eb7e6a25ec8389337a5c6973421d#code

      ParaPair 合約:https://bscscan.com/address/0x3fd4fbd7a83062942b6589a2e9e2436dd8e134d4#code

      ParaProxy 合約(代理 Masterchef 合約):https://bscscan.com/address/0x633fa755a83b015cccdc451f82c57ea0bd32b4b4#code

      攻擊流程分析

      首先通過 tenderly 和 blocksec 分析交易過程,可以看到這筆交易的頭部和尾部有借款還款操作,可以判斷攻擊者利用了閃電貸進行攻擊。攻擊者通過閃電貸貸出了 15w 的 USDT 和 BUSD。

      交易的頭部,借出代幣

      image

      交易的尾部,歸還代幣

      image

      然后攻擊者調用 ParaRouter.addLiquidity 將 15w 的 USDT 和 BUSD 添加到對應的 ParaPair 合約中,獲取流動性代幣 LP ,并將其轉移到改寫了 transferFrom 函數的 ERC20 標準的 UBT 代幣合約(0xca2)中。

      image

      然后攻擊合約(0x477)利用 USDT-BUSD 的 pid 以及 UBT-UGT 代幣,通過 ParaProxy 代理的 MasterChef.depositByAddLiquidity 添加流動性。

      image

      調用 MasterChef.depositByAddLiquidity 函數時的參數輸入情況。

      image

      MasterChef 合約中的內部調用 depositByAddLiquiditydepositByAddLiquidityInternaladdLiquidityInternal

      image

      相關函數代碼

      // MasterChef
      function depositByAddLiquidity(uint256 _pid, address[2] memory _tokens, uint256[2] memory _amounts) external{
          require(_amounts[0] > 0 && _amounts[1] > 0, "!0");
          address[2] memory tokens;
          uint256[2] memory amounts;
          (tokens[0], amounts[0]) = _doTransferIn(msg.sender, _tokens[0], _amounts[0]);
          (tokens[1], amounts[1]) = _doTransferIn(msg.sender, _tokens[1], _amounts[1]);
          depositByAddLiquidityInternal(msg.sender, _pid, tokens,amounts);
      }
      
      function depositByAddLiquidityInternal(address _user, uint256 _pid, address[2] memory _tokens, uint256[2] memory _amounts) internal {
          PoolInfo memory pool = poolInfo[_pid];
          require(address(pool.ticket) == address(0), "T:E");
          uint liquidity = addLiquidityInternal(address(pool.lpToken), _user, _tokens, _amounts);
          _deposit(_pid, liquidity, _user);
      }
      
      function addLiquidityInternal(address _lpAddress, address _user, address[2] memory _tokens, uint256[2] memory _amounts) internal returns (uint){
          //Stack too deep, try removing local variables
          DepositVars memory vars;
          approveIfNeeded(_tokens[0], address(paraRouter), _amounts[0]);
          approveIfNeeded(_tokens[1], address(paraRouter), _amounts[1]);
          vars.oldBalance = IERC20(_lpAddress).balanceOf(address(this));
          (vars.amountA, vars.amountB, vars.liquidity) = paraRouter.addLiquidity(_tokens[0], _tokens[1], _amounts[0], _amounts[1], 1, 1, address(this), block.timestamp + 600);
          vars.newBalance = IERC20(_lpAddress).balanceOf(address(this));
          require(vars.newBalance > vars.oldBalance, "B:E");
          vars.liquidity = vars.newBalance.sub(vars.oldBalance);
          addChange(_user, _tokens[0], _amounts[0].sub(vars.amountA));
          addChange(_user, _tokens[1], _amounts[1].sub(vars.amountB));
          return vars.liquidity;
      }
      
      // ParaRouter
      function addLiquidity(
          address tokenA,
          address tokenB,
          uint amountADesired,
          uint amountBDesired,
          uint amountAMin,
          uint amountBMin,
          address to,
          uint deadline
      ) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
          noFees(tokenA, tokenB);
          (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
          address pair = ParaLibrary.pairFor(factory, tokenA, tokenB);
          TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
          TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
          liquidity = IParaPair(pair).mint(to);
          FeesOn(tokenA, tokenB);
      }
      
      // TransferHelper
      function safeTransferFrom(
          address token,
          address from,
          address to,
          uint256 value
      ) internal {
          // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
          (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
          require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FROM_FAILED');
      }
      

      而攻擊點就在于上圖框選出來的 safeTransferFrom 函數中。展開來看,UBT 在其 transferFrom 函數中調用了 MasterChef.deposit 函數。

      image

      從輸入可以得知,UBT 合約的transferFrom 函數通過調用 MasterChef.deposit 將其持有的 USDT-BUSD 的 LP(pid = 18) 全部存入。然后 _deposit 函數記錄 UBT 合約(0xca2)存入了 LP 代幣。【第一次記錄】

      image

      image

      相關的 deposit 代碼如下

      // MasterChef
      function deposit(uint256 _pid, uint256 _amount) external {
          depositInternal(_pid, _amount, msg.sender, msg.sender);
      }
      
      function depositInternal(uint256 _pid, uint256 _amount, address _user, address payer) internal {
          PoolInfo storage pool = poolInfo[_pid];
          pool.lpToken.safeTransferFrom(
              address(payer),
              address(this),
              _amount
          );
          if (address(pool.ticket) != address(0)) {
              UserInfo storage user = userInfo[_pid][_user];
              uint256 new_amount = user.amount.add(_amount);
              uint256 user_ticket_count = pool.ticket.tokensOfOwner(_user).length;
              uint256 staked_ticket_count = ticket_staked_count(_user, address(pool.ticket));
              uint256 ticket_level = pool.ticket.level();
              (, uint overflow) = check_vip_limit(ticket_level, user_ticket_count + staked_ticket_count, new_amount);
              require(overflow == 0, "Exceeding the ticket limit");
              deposit_all_tickets(pool.ticket);
          }
          _deposit(_pid, _amount, _user);
      }
      
      function _deposit(uint256 _pid, uint256 _amount, address _user) internal {
          PoolInfo storage pool = poolInfo[_pid];
          UserInfo storage user = userInfo[_pid][_user];
          poolsTotalDeposit[_pid] = poolsTotalDeposit[_pid].add(_amount);
          updatePool(_pid);
          if (user.amount > 0) {
              uint256 pending =
                  user.amount.mul(pool.accT42PerShare).div(1e12).sub(
                      user.rewardDebt
                  );
              _claim(pool.pooltype, pending);
          }
          user.amount = user.amount.add(_amount);
          user.rewardDebt = user.amount.mul(pool.accT42PerShare).div(1e12);
          emit Deposit(_user, _pid, _amount);
      }
      

      完成重入攻擊以后,函數返回到上一級調用 MasterChef.addLiquidityInternal 處,滿足了 _lpAddress 對應的流動性代幣增加這一條件。

      image

      然后返回到上一級函數, _deposit 函數根據返回的 liquidity 值記錄攻擊者(0x477)存入了 LP 代幣。【第二次記錄】

      image

      image

      最后就是將手頭上的 LP 換取等值的 USDT-BUSD 代幣。

      UBT 合約先調用 MasterChef.withdraw 函數將存入的 LP 取出,然后發送給攻擊合約(0x477)

      image

      攻擊合約同樣先調用 MasterChef.withdraw 函數將存入的 LP 取出,然后調用 ParaRouter.removeliquidity 將價值 31W 的 USDT-BUSD 取走。

      image

      最后,歸還閃電貸的 15W 代幣,然后把剩余代幣轉賬走。

      image

      攻擊流程

      先閃電貸貸 15w 的 USDT 和 BUSD,調用 ParaRouter.addLiquidity 添加流動性,并將獲得的 LP 轉移到改寫了 transferFrom 函數的 ERC20 標準的 UBT 代幣合約中

      然后利用 USDT-BUSD 的 pid 以及 UBT-UGT 代幣,通過 MasterChef.depositByAddLiquidity 添加流動性,內部調用 depositByAddLiquiditydepositByAddLiquidityInternaladdLiquidityInternal

      然后 addLiquidityInternal 函數在 paraRouter.addLiquidity 中調用 UBT 代幣的 transferFrom 函數進行重入,重入了同合約的 deposit 函數

      deposit 函數根據傳入的 pid ,將 UBT 代幣合約中 USDT-BUSD 的 LP 取走,并為 UBT 合約記錄對應的流動性(第一次添加 USDT-BUSD 的 LP)

      然后回到 addLiquidityInternal 函數,根據傳入的 pid 獲取對應的(USDT-BUSD) LP 代幣地址,檢測添加流動性前后的 LP 差值,并根據差值給攻擊合約記錄對應的流動性(第二次添加 USDT-BUSD 的 LP)

      然后 UBT 合約調用 MasterChef.withdraw 函數將存入的 LP 取出,然后發送給攻擊合約。攻擊合約同樣調用 MasterChef.withdraw 函數將存入的 LP 取出,然后調用 ParaRouter.removeliquidity 將價值 31W 的 USDT-BUSD 取走。

      最后,攻擊者歸還 15w 的閃電貸,并將剩余的 15W 代幣進行轉移。

      總結

      本次攻擊事件中被利用的漏洞有兩處:

      1. MasterChef 合約在涉及到流動性操作的時候沒有添加重入鎖,使得攻擊者可以在添加流動性的過程中進行重入攻擊。
      2. MasterChef.depositByAddLiquidity 函數中沒有檢測 _pid_tokens 兩個參數是否為對應關系,這使得攻擊者可以自定義 _token 中的函數進行重入攻擊。存入的是 _token ,但是計算的是 _pid 對應的池子的流動性。

      參考文章

      1. 慢霧@九九
      2. yudan
      3. tenderly
      4. blocksec
      posted @ 2022-03-15 17:39  ACai_sec  閱讀(512)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 久久精品国产熟女亚洲av| 亚洲无人区码一二三区别| 亚洲综合精品一区二区三区| 揭东县| 国产极品尤物粉嫩在线观看| 一卡二卡三卡四卡视频区| 最新国产精品亚洲| 少妇激情一区二区三区视频小说| 国产欧美一区二区日本加勒比| 亚洲国产精品一二三四五| 99久久99久久久精品久久| 青青草国产精品日韩欧美| 欧美一区二区| 久久精品国产国产精品四凭| 国产亚洲999精品AA片在线爽| 丁香婷婷在线观看| 国产91久久精品一区二区| 吉川爱美一区二区三区视频 | 在线 国产 欧美 专区| 么公的好大好硬好深好爽视频 | 人妻中文字幕亚洲精品| 免费无码无遮挡裸体视频在线观看| 天天做日日做天天添天天欢公交车| 日本一卡2卡3卡四卡精品网站| 欧美亚洲另类自拍偷在线拍| 大陆熟妇丰满多毛xxxx| 日本深夜福利在线观看| 成人福利一区二区视频在线 | 日韩有码中文在线观看| 亚洲一区二区精品极品| 亚洲免费观看一区二区三区| 天堂а√在线中文在线| 亚洲精品成人一二三专区| 久久国产精品无码网站| 亚洲国产精品综合久久2007| 韩国三级+mp4| 又爽又黄又无遮掩的免费视频| 欧美高清一区三区在线专区| 亚洲成av人片无码迅雷下载| 干老熟女干老穴干老女人| 丝袜老师办公室里做好紧好爽|