复盘aishiba发币漏洞——项目方的低级bug,科学家的又一次狂欢

AIshiba作为arb上一个比较早期的土狗NFT,毫不夸张的说引燃了arb上的NFT潮流。floor price曾一度最高达到0.04e,但是现在已经跌倒了0.004e(大概8刀)。

这就是妥妥的土狗项目,官网做的也是极其简陋,连白皮书都没有,只有路线图。但是没办法,赶上了风口,猪都能飞上天,赶上arb的aidog热度。

并且在今天凌晨进行nft空投发币,空投规则是按照快照时的地址NFT个数计算,即NFT拥有数量越多,空投claim的代币越多,nft空投上限10个,超过按照10个计算。

谁知道发币出现bug,导致科学家发现漏洞,单号直接mint最大量的token 17700000000000 * 10 ** 6 。最高点卖出大概1e+。 代币k线

核心原因是项目方利用Merkle Tree发放 空投token,但是本应该在后台的保存了全量地址被项目方放在了前端,并且未做任何混淆代码的保护处理。导致proof被轻易计算得到。(了解原理可参阅文末参考文献)

只需要在官网按 F12 整个代码一览无余:

merkleProof.js

const { WHITELIST_ADDRESS } = require("./whitelist_Address")
const keccak256 = require("keccak256")
const { MerkleTree } = require("merkletreejs")


const addressLeaves = WHITELIST_ADDRESS.map(x => keccak256(x))
const merkleTree = new MerkleTree(addressLeaves, keccak256, {
    sortPairs : true
})

const rootHash = merkleTree.getHexRoot()

module.exports = { merkleTree, rootHash }

并且调用合约的方法,传入参数也非常清晰:

index.js
 
const handleClaimForNfts = async () => {
    try {
      const signer = await getProviderOrSigner(true);
      const userAddress = signer.getAddress();
      const aiShibaContract = new Contract(
        AISHIBA_CONTRACT_ADDRESS,
        AISHIBA_CONTRACT_ABI,
        signer
      );
 
    
      const txn = await aiShibaContract.claimTokensForNft(merkleProof, nftBalance);
      await txn.wait();
      

      console.log("txn", txn);
      console.log("txn", "successful");

    } catch (error) {
      console.error(error);
    }
  };

可以看到调用了合约的 claimTokensForNft() 方法,传入了merkleProofnftBalance 两个变量,nftBalance 代表着快照拥有NFT的数量。

其中merkleProof 只需要 用下列方法 merkleTree.getHexProof(_leaf) 即可获取

index.js

const getProof = async () => {
    const signer = await getProviderOrSigner(true);
    const _userAddress = await signer.getAddress();
    const _leaf = keccak256(_userAddress);
    const _merkleProof = merkleTree.getHexProof(_leaf);
    setUserAddress(_userAddress);
    setMerkleProof(_merkleProof);
  };

再看一下领取的合约代码

    function claimTokensForNft(bytes32[] calldata proof, uint256 _amount) public claimIsLive {
        bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
        require(MerkleProof.verify(proof, merkleRootNFT, leaf), "Wallet not eligible to claim");

        require(_amount > 0, "You do not own any tokens");
        require(!hasClaimedNFT[msg.sender], "You have claimed your Tokens");

        uint256 amountOfTokensToClaim;
        if(_amount<= 2) {
            amountOfTokensToClaim = tier1Claim;
        } else if( _amount > 2 && _amount <= 4){
            amountOfTokensToClaim = tier2Claim;
        } else if(_amount > 4 && _amount <= 9) {
            amountOfTokensToClaim = tier3Claim;
        } else if(_amount >= 10) {
            amountOfTokensToClaim = tier4Claim;
        }

        totalTokensClaimed += amountOfTokensToClaim;
        hasClaimedNFT[msg.sender] = true;

        require(tokenContract.transfer(msg.sender, amountOfTokensToClaim), "Transfer failed");
        emit HasClaimedNFT(msg.sender, amountOfTokensToClaim);
    }

_amount 即为nft的数量,合约按照 _amount 数量进行空投token。

  • _amount<=2 空投代币 1475000000000 * 10 ** 6

  • 2<_amount<=4 空投代币 3375000000000 * 10 ** 6

  • 4<_amount<=9 空投代币 7750000000000 * 10 ** 6

  • _amount>=10 空投代币 17700000000000 * 10 ** 6

合约中没有对_amount的校验操作,即使你的钱包地址拥有1个nft(即拥有白名单权限),只要Merkle tree 验证成功,_amount传入10或者更大值也能mint 到单地址最高数量代币,即 17700000000000 * 10 ** 6 。

大概有1800个地址领取了空投,绝大部分都是科学家

项目方发现了不对劲,在凌晨的00:45已经关闭了合约,并在接下来转走了合约中所有token。

据传言(道听途说):背后的项目方是大学生,整个寝室一起出动一起赚钱,如果真是这样,现在这大学生还真他娘的敢想敢干,割起韭菜来也是毫不手软。

可惜 螳螂捕蝉,黄雀在后,项目成了科学家的提款机,最可怜的还是那些真金白银在DEX和CEX中买Token的韭菜们。

不玩土狗的我 看着大佬们的卖币截图和聊天记录,又一次感慨道:

别人赚钱如呼吸般简单,自己赚钱如吃屎般困难。

本人推特:@coolberwin_eth

文章参考:

Merkle tree的 原理: WTF Ethers极简入门: MerkleTree脚本

Subscribe to berwinYes
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.