Defi安全--Orion Protocol攻击事件分析

其它相关文章可见个人主页

1. Orion Protocol攻击事件相关信息

2023年2月2日,在ETH和BSC上的Orion Protocol项目被攻击,这里以ETH上攻击为例:

2. Orion Protocol攻击事件分析

攻击流程详解

Eth上的攻击交易Ethereum Transaction Hash (Txhash) Details | Etherscan

从中我们可以看出,input data为单纯的函数签名,没有参数,只是调用了一个攻击函数

![image-20240116111931103](https://img-
blog.csdnimg.cn/img_convert/82ac4ea059c817fef14c83f78d93e75e.png)

查看对应的phalcon调用序列:

![image-20240116112006459](https://img-
blog.csdnimg.cn/img_convert/6495a6ead1e910b5c994db19ae972ea0.png)

  1. 先进行了一系列基础操作,对Orion Protocol项目合约进行一系列的代币授权approve()操作,如USDT和USDC等。

![image-20240116112824996](https://img-
blog.csdnimg.cn/img_convert/1d7f643eb4ff4eeecf592ea5ed81ecea.png)

  1. 随后我们可以看到攻击者调用了Orion Protocol的depositAsset函数,看一下该函数的源码:

    function depositAsset(address assetAddress, uint112 amount) external {
    uint256 actualAmount = IERC20(assetAddress).balanceOf(address(this));
    IERC20(assetAddress).safeTransferFrom(
    msg.sender,
    address(this),
    uint256(amount)
    );
    actualAmount = IERC20(assetAddress).balanceOf(address(this)) - actualAmount;
    generalDeposit(assetAddress, uint112(actualAmount));
    }

  2. 攻击者向orion Protocol合约转入对应数量的USDC,将该合约转账前后的代币余额,作为用户存款的数量,并调用generateDeposit函数,这一步USDC的存款是为后续的攻击做准备。

  3. 攻击者调用Uniswap V2: USDT的闪电贷函数,借出200多万个USDT

![image-20240116113406567](https://img-
blog.csdnimg.cn/img_convert/24c6ef3b550ac8d1e8e3ddc17b5a2b3f.png)

  1. 调用uniswapv2的闪电贷函数,借贷对应的USDT,乐观转账,先将对应的USDT转账给了攻击者,后回调攻击者的uniswapV2Call函数

  2. 回调函数中,因为攻击者先前存入了USDC,现在攻击者调用了orion Protocol项目ExchangeWithAtomic合约中的一个函数swapThroughOrionPool,orion Protocol提供的代币交换函数,代币兑换路径为[USDC, ATK, USDT],其中ATK为攻击者提前创建的恶意代币,将USDC兑换成USDT

  3. 随后调用LibPool的doSwapThroughOrionPool的函数,再调用PoolFunctionality 合约中的doSwapThroughOrionPool函数

    function swapThroughOrionPool(
    uint112 amount_spend,
    uint112 amount_receive,
    address[] calldata path,
    bool is_exact_spend
    ) public payable nonReentrant {
    bool isCheckPosition = LibPool.doSwapThroughOrionPool(
    IPoolFunctionality.SwapData({
    amount_spend: amount_spend,
    amount_receive: amount_receive,
    is_exact_spend: is_exact_spend,
    supportingFee: false,
    path: path,
    orionpool_router: _orionpoolRouter,
    isInContractTrade: false,
    isSentETHEnough: false,
    isFromWallet: false,
    asset_spend: address(0)
    }),
    assetBalances, liabilities);

  4. 进一步调用PoolFunctionality 合约中的 doSwapThroughOrionPool 函数,仔细看一下函数源码,该函数进一步调用了_doSwapTokens()函数

  5. 上述代码中_doSwapTokens()函数时进行相应的输入,输出代币数量的计算,跟进该函数的实现

    function _doSwapTokens(InternalSwapData memory swapData) internal returns (uint256 amountIn, uint256 amountOut) {
    bool isLastWETH = swapData.path[swapData.path.length - 1] == WETH;
    address toAuto = isLastWETH || swapData.curFactoryType == FactoryType.CURVE ? address(this) : swapData.to;
    uint256[] memory amounts;
    if (!swapData.supportingFee) {
    if (swapData.isExactIn) {
    amounts = OrionMultiPoolLibrary.getAmountsOut(
    swapData.curFactory,
    swapData.curFactoryType,
    swapData.amountIn,
    swapData.path
    );
    require(amounts[amounts.length - 1] >= swapData.amountOut, “PoolFunctionality: IOA”);
    } else {
    amounts = OrionMultiPoolLibrary.getAmountsIn(
    swapData.curFactory,
    swapData.curFactoryType,
    swapData.amountOut,
    swapData.path
    );
    require(amounts[0] <= swapData.amountIn, “PoolFunctionality: EIA”);
    }
    } else {
    amounts = new uint256;
    amounts[0] = swapData.amountIn;
    }
    amountIn = amounts[0];

       {
           uint256 curBalance;
           address initialTransferSource = swapData.curFactoryType == FactoryType.CURVE ? address(this)
               : OrionMultiPoolLibrary.pairFor(swapData.curFactory, swapData.path[0], swapData.path[1]);
    
           if (swapData.supportingFee) curBalance = IERC20(swapData.path[0]).balanceOf(initialTransferSource);
    
           IPoolSwapCallback(msg.sender).safeAutoTransferFrom(
               swapData.asset_spend,
               swapData.user,
               initialTransferSource,
               amountIn
           );
           if (swapData.supportingFee) amounts[0] = IERC20(swapData.path[0]).balanceOf(initialTransferSource) - curBalance;
       }
    
       {
           uint256 curBalance = IERC20(swapData.path[swapData.path.length - 1]).balanceOf(toAuto);
           //计算转账前的余额
           if (swapData.curFactoryType == FactoryType.CURVE) {
               _swapCurve(swapData.curFactory, amounts, swapData.path, swapData.supportingFee);
           } else if (swapData.curFactoryType == FactoryType.UNISWAPLIKE) {
           //这里的swap函数完成相应的代币兑换
               _swap(swapData.curFactory, amounts, swapData.path, toAuto, swapData.supportingFee);
           }
           //将账户余额与转账前余额相减,得到新增的金额
           amountOut = IERC20(swapData.path[swapData.path.length - 1]).balanceOf(toAuto) - curBalance;
       }
    
       require(
           swapData.amountIn == 0 || swapData.amountOut == 0 ||
           amountIn * 1e18 / swapData.amountIn <= amountOut * 1e18 / swapData.amountOut,
           "PoolFunctionality: OOS"
       );
    
       if (isLastWETH) {
           SafeTransferHelper.safeAutoTransferTo(
               WETH,
               address(0),
               swapData.to,
               amountOut
           );
       } else if (swapData.curFactoryType == FactoryType.CURVE) {
           IERC20(swapData.path[swapData.path.length - 1]).safeTransfer(swapData.to, amountOut);
       }
    
       emit OrionPoolSwap(
           tx.origin,
           convertFromWETH(swapData.path[0]),
           convertFromWETH(swapData.path[swapData.path.length - 1]),
           swapData.amountIn,
           amountIn,
           swapData.amountOut,
           amountOut,
           swapData.curFactory
       );
    

    }

  6. 这里进行相应的代币兑换,之前的兑换path为[USDC, ATK, USDT],这里通过PoolFunctionality合约中的_swap()完成相应的兑换,跟进 _swap()函数的源码

    function _swap(
    address curFactory,
    uint256[] memory amounts,
    address[] memory path,
    address _to,
    bool supportingFee
    ) internal {
    for (uint256 i; i < path.length - 1; ++i) {
    (address input, address output) = (path[i], path[i + 1]);
    IOrionPoolV2Pair pair = IOrionPoolV2Pair(OrionMultiPoolLibrary.pairFor(curFactory, input, output));
    (address token0, ) = OrionMultiPoolLibrary.sortTokens(input, output);
    uint256 amountOut;

           if (supportingFee) {
               (uint reserve0, uint reserve1,) = pair.getReserves();
               (uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
               uint256 amountIn = IERC20(input).balanceOf(address(pair)).sub(reserveInput);
               amountOut = OrionMultiPoolLibrary.getAmountOutUv2(amountIn, reserveInput, reserveOutput);
           } else {
               amountOut = amounts[i + 1];
           }
    
           (uint256 amount0Out, uint256 amount1Out) = input == token0 ? (uint256(0), amountOut) : (amountOut, uint256(0));
           address to = i < path.length - 2 ? OrionMultiPoolLibrary.pairFor(curFactory, output, path[i + 2]) : _to;
    
           pair.swap(amount0Out, amount1Out, to, new bytes(0));
       }
    

    }

![image-20240116142931330](https://img-
blog.csdnimg.cn/img_convert/189a1441c3a4b270496b7c7eca359cec.png)

  1. path序列中的[USDC, ATK, USDT],每两个代币对之间存在一个pair合约,即USDC转到ATK,ATK转到对应的USDT,实现对应的代币兑换,攻击者创建的pair对合约,这里通过相应的计算金融模型,得到对应的转账金额,调用pair合约中的swap函数,实现相应的代币转移。

![image-20240116150016939](https://img-
blog.csdnimg.cn/img_convert/91e6b0b0c1d7c291dd05f5f0f8bfb74c.png)

![image-20240116151407776](https://img-
blog.csdnimg.cn/img_convert/f3f1bf976f4048c868b8f7cc548a03c6.png)

  1. 由于pair对中的swap函数,进行相应的转账,需要调用ATK代币的转账函数,ATK是攻击者部署的恶意代币,攻击者可控,攻击者这里调用自身的deposit()函数,调用ExchangeWithAtomic合约的depositAsset函数,并将闪电贷得到的200多万USDT全部转进Orion Protocol的depositAsset()函数中

  2. 这时攻击者在ExchangeWithAtomic 合约中USDT的存款被记账为了200多万,原来ExchangeWithAtomic 合约的余额为200多万,两者数值相近(攻击者设计的)

  3. 而通过swapThroughOrionPool函数中攻击者USDC兑换出多少的USDT最终是通过ExchangeWithAtomic 合约兑换前后的USDT余额计算的,相当于存入的200万USDT被认为是USDC兑换出来的,最后通过creditUserAssets 函数来更新ExchangeWithAtomic 维护的adress-balance的账本,攻击者被认为是存入了200+200万

![image-20240116151533445](https://img-
blog.csdnimg.cn/img_convert/cde557d0299656b81bb5651a5af3e9bf.png)

  1. 攻击者进行相应的闪电贷还款,归还借出的200多万,获利200多万

![image-20240116151634701](https://img-
blog.csdnimg.cn/img_convert/3da15b7533de67573fd9420503721589.png)

  1. 调用闪电贷,借出WETH,归还USDT,实现对应的套利离场
攻击事件发生的主要原因
  • doswapThroughOrionPool 函数,兑换路径攻击者可控,代币类型攻击者可控(恶意代币)
  • 兑换后更新账本的记账方式不正确,利用前后余额计算(×)
  • 合约兑换功能的函数没有做重入保护

3. 分析Orion Protocol攻击事件所需信息

  1. 最关键的一点,重入的发生回调不在这个攻击合约之中,在攻击者创建的恶意代币合约之中(可能是这个案例的特殊情况)
  2. 普适的一点:触发恶意合约回调的功能,是在经过5次外部函数调用后,才最终调用到攻击者的恶意代币合约中的函数,在我们的工具中是无法获得这样的调用过程,全路径覆盖不太现实。

学习计划安排


我一共划分了六个阶段,但并不是说你得学完全部才能上手工作,对于一些初级岗位,学到第三四个阶段就足矣~

这里我整合并且整理成了一份【282G】的网络安全从零基础入门到进阶资料包,需要的小伙伴可以扫描下方CSDN官方合作二维码免费领取哦,无偿分享!!!

如果你对网络安全入门感兴趣,那么你需要的话可以

点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

①网络安全学习路线
②上百份渗透测试电子书
③安全攻防357页笔记
④50份安全攻防面试指南
⑤安全红队渗透工具包
⑥HW护网行动经验总结
⑦100个漏洞实战案例
⑧安全大厂内部视频资源
⑨历年CTF夺旗赛题解析

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值