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

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

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

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

  • 攻击合约地址:Attacker Contract Address | Etherscan
  • 攻击者地址:Orion Protocol Exploiter 2 | Address
  • 攻击交易:Ethereum Transaction Hash (Txhash) Details | Etherscan
  • Phalcon调用序列分析:0xa6f63fcb6bec881886 | Phalcon Explorer (blocksec.com)

2. Orion Protocol攻击事件分析

攻击流程详解

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

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

image-20240116111931103

查看对应的phalcon调用序列:

image-20240116112006459

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

image-20240116112824996

  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));
    }
  1. 攻击者向orion Protocol合约转入对应数量的USDC,将该合约转账前后的代币余额,作为用户存款的数量,并调用generateDeposit函数,这一步USDC的存款是为后续的攻击做准备。
  2. 攻击者调用Uniswap V2: USDT的闪电贷函数,借出200多万个USDT

image-20240116113406567

  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);
  1. 进一步调用PoolFunctionality 合约中的 doSwapThroughOrionPool 函数,仔细看一下函数源码,该函数进一步调用了_doSwapTokens()函数

  2. 上述代码中_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[](1);
            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
        );
    }
  1. 这里进行相应的代币兑换,之前的兑换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

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

    image-20240116150016939

    image-20240116151407776

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

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

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

image-20240116151533445

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

image-20240116151634701

  1. 调用闪电贷,借出WETH,归还USDT,实现对应的套利离场

攻击事件发生的主要原因

  • doswapThroughOrionPool 函数,兑换路径攻击者可控,代币类型攻击者可控(恶意代币)
  • 兑换后更新账本的记账方式不正确,利用前后余额计算(×)
  • 合约兑换功能的函数没有做重入保护

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/331381.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

合并K个升序链表(LeetCode 23)

文章目录 1.问题描述2.难度等级3.热门指数4.解题思路方法一&#xff1a;顺序合并方法二&#xff1a;分治合并方法三&#xff1a;使用优先队列合并 参考文献 1.问题描述 给你一个链表数组&#xff0c;每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中&#xff…

GNSS数据下载软件 -- 武汉大学 Fast软件(体验感极佳~)

目录 一、简介与下载地址 1.介绍 2.软件特点 3.下载地址 4.以github下载链接为例 二、下载方法(三种方法&#xff0c;以windows系统为例) 1.双击"Fast.exe"根据提示引导下载 2.手动输入"cmd"进入命令行界面&#xff0c;通过输入相关命令进行下载 …

【GAMES101】Lecture 07 着色(shading)

目录 着色 Blinn-Phong反射模型 漫反射 光衰减 着色 这个着色&#xff08;shading&#xff09;就是将不同的材质应用到不同的物体上&#xff0c;像一个物体&#xff0c;它可以是木头的、金属的、塑料的…… Blinn-Phong反射模型 我们来看一个简单的着色模型&#xff0c;…

机器学习--人工智能概述

人工智能概述 入门人工智能&#xff0c;了解人工智能是什么。为啥发展起来&#xff0c;用途是什么&#xff0c;是最重要也是最关键的事情。大致有以下思路。 人工智能发展历程机器学习定义以及应用场景监督学习&#xff0c;无监督学习监督学习中的分类、回归特点知道机器学习…

NoClassDefFoundError: org/mybatis/logging/LoggerFactory

NoClassDefFoundError: org/mybatis/logging/LoggerFactory 问题描述问题分析问题解决 问题描述 org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name userServiceImpl: Unsatisfied dependency expressed through field baseM…

QT 绘图与重绘事件

代码实现仪表盘 .cpp #include "widget.h" #include "ui_widget.h"#include <QPainter> #include <QPen> #include <QBrush>#include <QDebug> Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->…

【网站项目】基于springboot与vue的电子商城项目

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

C++设计模式(李建忠)笔记3

C设计模式&#xff08;李建忠&#xff09; 本文是学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。 参考链接 Youtube: C设计模式 Gtihub源码与PPT&#xff1a;https://github.com/ZachL1/Bilibili-plus 豆瓣: 设计模式–可复用面向对象软件的基础 文章目录 C设计模…

STL常用容器—vector容器

STL常用容器—vector容器 vector基本概念容器的基本操作容器的常见方法容器迭代器&#xff08;遍历&#xff09;容器的插入与删除容器的嵌套及存放自定义数据容器的嵌套容器存放自定义数据 vector基本概念 功能&#xff1a; vector数据结构和数组非常相似&#xff0c;也称为单…

Chrome 开发者工具

Chrome 开发者工具 介绍控制面板时间线下载信息概要请求列表单个请求时间线优化时间线上耗时项 lighthouse 插件Performance&#xff08;性能指标&#xff09;Accessibility&#xff08;可访问性&#xff09;Best Practices&#xff08;最佳实践&#xff09;SEO&#xff08;搜索…

Iris微服务框架_golang web框架_完整示例Demo

Iris简介 Iris是一款Go语言中用来开发web应用的框架&#xff0c;该框架支持编写一次并在任何地方以最小的机器功率运行&#xff0c;如Android、ios、Linux和Windows等。该框架只需要一个可执行的服务就可以在平台上运行了。 Iris框架以简单而强大的api而被开发者所熟悉。iris…

寒武纪显卡实现softmax的pingpong流水并行

在上一篇文章添加链接描述中我们介绍了寒武纪显卡实现基本的softmax代码&#xff0c;这里我们借助于寒武纪的流水并行来编写进一步的策略。 pingpongGDRAM2NRAM流水 仅仅计算max和sum使用流水 我们先考虑不使用SRAM的流水&#xff0c;我们设置两个NRAM上的长度为maxNum上的数…

STM32标准库开发——USART串口外设

USART外设介绍 USART (Universal Synchronous/AsynchronousReceiver/Transmitter&#xff09;通用同步/异步收发器USART是STM32内部集成的硬件外设&#xff0c;可根据数据寄存器的一个字节数据自动生成数据帧时序&#xff0c;从TX引脚发送出去&#xff0c;也可自动接收RX引脚的…

WebGL中开发AR应用

WebGL在本质上是用于在浏览器中进行3D和2D图形渲染的技术&#xff0c;而增强现实&#xff08;AR&#xff09;通常需要与现实世界的环境进行交互。要在WebGL中开发AR应用&#xff0c;您可以采取以下步骤&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专…

Arm Generic Interrupt Controller v3 and v4(GICv3v4)学习(一)

提示 该博客主要为个人学习&#xff0c;通过阅读官网手册整理而来&#xff08;个人觉得阅读官网的英文文档非常有助于理解各个IP特性&#xff09;。若有不对之处请参考参考文档&#xff0c;以官网参考文档为准。 Arm Generic Interrupt Controller v3 and v4学习一共分为三章&…

RHEL8 Samba服务器详细配置用户模式

任务&#xff1a; 配置server01为samba服务器&#xff0c;samba服务器的/companydata/sales为共享目录&#xff0c;共享名为sales&#xff0c;里面创建测试文件test_share.tar&#xff0c;创建用户组sales&#xff0c;创建组内用户sale1&#xff0c;要求配置用户模式访问&#…

Uniapp多选Popup(弹出层)

uniapp中多选组件很少&#xff0c;故个人简单开发了一个&#xff0c;可简单使用&#xff0c;也可根据个人需求稍微改进 支持的功能 单选多选&#xff08;默认&#xff09;限制选择数量默认选中禁用选项 属性说明 属性默认值说明singlefalsetrue为开启单选&#xff0c;否则为…

无需信用卡注册美区Apple ID指南

第一步 准备工作 1、一个没有注册过AppleID的邮箱&#xff0c;建议最好是Gmail邮箱 2、一个苹果手机&#xff0c;当然这个是必须的 3、需要科学上网 第二步 苹果网站注册 为了避免cookie的干扰&#xff0c;最好是在无痕模式下打开以上网页&#xff0c;创建你的AppleID&#…

rabbitmq-java基础详解

一、rabbitmq是什么&#xff1f; 1、MQ定义 MQ&#xff08;Message Queue&#xff09;消息队列 主要解决&#xff1a;异步处理、应用解耦、流量削峰等问题&#xff0c;是分布式系统的重要组件&#xff0c;从而实现高性能&#xff0c;高可用&#xff0c;可伸缩和最终一致性的架…

Spring+SpringMVC+Mybatis进行项目的整合

Spring SpringMVCM Mybatis 整合 一、 通过idea创建maven工程 二、 引入依赖项以及导入mybatis逆向工程的插件 将如下的文件替换所在工程的pom文件 <?xml version"1.0" encoding"UTF-8"?><project xmlns"http://maven.apache.org/POM/4…
最新文章