| timezone | Pacific/Auckland |
|---|
请在上边的 timezone 添加你的当地时区,这会有助于你的打卡状态的自动化更新,如果没有添加,默认为北京时间 UTC+8 时区 时区请参考以下列表,请移除 # 以后的内容
timezone: Pacific/Honolulu # 夏威夷-阿留申标准时间 (UTC-10)
timezone: America/Anchorage # 阿拉斯加标准时间 (UTC-9)
timezone: America/Los_Angeles # 太平洋标准时间 (UTC-8)
timezone: America/Denver # 山地标准时间 (UTC-7)
timezone: America/Chicago # 中部标准时间 (UTC-6)
timezone: America/New_York # 东部标准时间 (UTC-5)
timezone: America/Halifax # 大西洋标准时间 (UTC-4)
timezone: America/St_Johns # 纽芬兰标准时间 (UTC-3:30)
timezone: America/Sao_Paulo # 巴西利亚时间 (UTC-3)
timezone: Atlantic/Azores # 亚速尔群岛时间 (UTC-1)
timezone: Europe/London # 格林威治标准时间 (UTC+0)
timezone: Europe/Berlin # 中欧标准时间 (UTC+1)
timezone: Europe/Helsinki # 东欧标准时间 (UTC+2)
timezone: Europe/Moscow # 莫斯科标准时间 (UTC+3)
timezone: Asia/Dubai # 海湾标准时间 (UTC+4)
timezone: Asia/Kolkata # 印度标准时间 (UTC+5:30)
timezone: Asia/Dhaka # 孟加拉国标准时间 (UTC+6)
timezone: Asia/Bangkok # 中南半岛时间 (UTC+7)
timezone: Asia/Shanghai # 中国标准时间 (UTC+8)
timezone: Asia/Taipei # 台灣标准时间 (UTC+8)
timezone: Asia/Tokyo # 日本标准时间 (UTC+9)
timezone: Australia/Sydney # 澳大利亚东部标准时间 (UTC+10)
timezone: Pacific/Auckland # 新西兰标准时间 (UTC+12)
- 自我介绍 Doublespending
- 你认为你会完成本次残酷学习吗?Yes
A: Damn Vulnerable DeFi(18)
- UnstoppableVault
UnstoppableMonitor.onFlashLoanrevert whenconvertToShares(totalSupply) != balanceBefore- We can get the share amount by
convertToShares(totalSupply) - In normal case, share amount equals to asset amount by
depositormintmethod. - However, if we transfer the token to the pool directly, the share amount does not changed but the asset amount increases.
- NaiveReceiver
- We can send all weth of
IERC3156FlashBorrower receivertofeeReceiverby calling flashLoan 10 times - Then, we use
Forwrarderto callMulticallwith awithdrawcall. - In this case,
msg.senderisForwarderand the pool will use the last 20 bytes as thesender - So, we can append
feeReceiverto thewithdrawcall to act asfeeReceiver.
- We can send all weth of
A: Damn Vulnerable DeFi(18)
- Truster
target.functionCall(data)can be atoken.approve(player, TOKENS_IN_POOL)call.- To by pass the balance check, we can set
amountofflashLoadto be 0 - Finally, palyer can use
token.transferFromto rescue all funds in the pool.
- Side Entrance
- To by pass the balance check, we can set
amountofflashLoadto be 0. - Receiver can call
pool.depositwhile calling back to receiver.- the ownership of the deposit can be assigned to receiver.
- the balance of pool have not changed.
- To by pass the balance check, we can set
A: Damn Vulnerable DeFi(18)
- The Rewarder
claimRewardsuses_setClaimedto prevent reclaiming based on <token, sender, batchNumber>.- However,
_setClaimedis only called after token switch ofinputClaimsarray. - So, before switching token, we can reclaim with same <token, sender, batchNumber>.
A: Damn Vulnerable DeFi(18)
- Selfie
- If we want to take all the token inside
SelfiePool - We should call
SelfiePool.emergencyExit - To bypass the check of
SelfiePool.emergencyExit, we should useSimpleGovernance.executeAction. - To execute an action, we should calling
SimpleGovernance.queueActionfirst. - To call
SimpleGovernance.queueAction, we should bypass the check of_hasEnoughVoteswhich means we should get more than half of the voting token. - We can use
SelfiePool.flashLoanto get the require token voting token. - Then, we can delegate the token and
SimpleGovernance.queueAction. - Finally, we can return the token to SelfiePool.
- After
ACTION_DELAY_IN_SECONDS, we can callSimpleGovernance.executeAction.
- If we want to take all the token inside
A: Damn Vulnerable DeFi(18)
-
Compromised
- The basic attack vector is that we buy the nft with low price and sell it with high price.
- So, we should manipulate oracle. Specifically, we should control two of the three price sources to manipulate the median price.
- The strange response from the server includes the base64 encoded private keys of two price sources.
- With the private keys, we can manipulate the nft price oracle.
A: Damn Vulnerable DeFi(18)
- Puppet
- We can manipulate the oracle price of PuppetPool to enable almost free borrow.
- At first, We can sell the token for ETH to make
uniswapPair.balance * (10 ** 18) / token.balanceOf(uniswapPair)small - Then, we borrow all the token balance of PuppetPool with very few ETH.
- Finally, we can sell ETH for the token to get back tokens which are used to manipulate the oracle price of PuppetPool.
- Note: If we want to attack with only one transacation, we can
- Deploy an
Attackercontract - Attack inside the
constructor - Pay ETH to
constructor - Pay Token to
constructorbypermit2
- Deploy an
A: Damn Vulnerable DeFi(18)
- Puppet V2
- The attack vector is still exist like
Puppet - The differences are
- use WETH instead of ETH
- use
IUniswapV2Router02
- The attack vector is still exist like
A: Damn Vulnerable DeFi(18)
- Backdoor
- We find that
proxyCreatedwill ensure thesetupprocess is fine - So, we can check if there is something missed
- We can see that <
to,data> and <paymentToken,payment,paymentReceiver> are not checked. - However,
proxyCreatedis called aftersetup, so we do not have any token to transfer - Then, we can check the logic of
setupModules(to, data) - Finally, we find that
delegatecall is allowed here. - So, we can let the wallet approve the token to anyone by manipulate the <
to,data> input. - Finally, we can use
transferFromto rug the token of the wallet.
- We find that
A: Damn Vulnerable DeFi(18)
-
Free Rider
-
From here, we can buy nft for free
payable(_token.ownerOf(tokenId)).sendValue(priceToPay)actually pay to the new owner (i.e. msg.sender) instead of the previous owner
- However, we need to have enough ETH to bypass the check here when buying the first NFT.
- We find that we can use the flashswap of uniswap v2
- Put the above logic inside
uniswapV2Call
- Put the above logic inside
-
A: Damn Vulnerable DeFi(18)
- Climber
- If we want to transfer all token of
ClimberVault, we have three potential choices:withdraw:onlyOwnersweepFunds:onlySweeperupgradeToAndCall:onlyOwner
sweepercan only be set while initialize. It is unlikely compromised.owneris theClimberTimelock. It is more likely compromised. Then,upgradeToAndCallis more dangerous thanwithdraw- So, we need
ClimberTimelockto callupgradeToAndCall. We must callexecutein this case.
- If we want to transfer all token of
- It seem that we actor as
ClimberTimelockitself to do arbitrary call includingupgradeToAndCalluntil the check here - To bypass the check
- We should make scheduled operation can be executed immediatedly. So, we can update dely to zero here.
- We should call
schedule.- For
address(this)is the role admin ofPROPOSER_ROLE. We can updatePROPOSER_ROLEto malicious contract. - We can call the malicious contract and let it schedule the executions.
- For
A: Damn Vulnerable DeFi(18)
- Wallet Mining
- At first we should find the nonce that match the
USER_DEPOSIT_ADDRESS. After brute-force method, we find thatnonceequals to 13 - Then, we should bypass
can(msg.sender, aim) - It's weired that we actually can reinit the
AuthorizedUpgradeablefor the misuse of the slots underTransparentProxy- When the proxy calls the init method of
AuthorizedUpgradeable,needsInitis actually the first slot ofTransparentProxy(e.g.upgrader) instead of the first slot ofAuthorizedUpgradeable(e.g.needsInit). - At first,
upgraderismsg.sender. So, we can bypass the check here. - Then,
AuthorizerFactorywill setupgraderto zero address - Finally,
upgraderis set to non-zero address again. So, we can reinit.
- When the proxy calls the init method of
- At first we should find the nonce that match the
A: Damn Vulnerable DeFi(18)
-
ABI Smuggling
-
The key is to bypass the selector check when
exeuctebytes4 selector; uint256 calldataOffset = 4 + 32 * 3; // calldata position where `actionData` begins assembly { selector := calldataload(calldataOffset) } if (!permissions[getActionId(selector, msg.sender, target)]) { revert NotAllowed(); } -
The above implemtation to fetch
selectoris not correct. For, the data ofactionDatais not required to follow theoffsetofactionData. The right approach to fetchselectoris according to theoffset. -
So, we can add malicious data following the
offsetand let the above code fetch wrong selector which is approved to player. -
Then, we set
offsetto skip the malicious data and point to the real selector which will be executed here.
-
A: Damn Vulnerable DeFi(18)
- Puppet V3
- The attack vector is the same as
PuppetandPuppet V2 - The differences are
- Uniswap v3 oracle will prevent price manipulation in the same block. So, we should call
lendingPool.borrowin the future block. - use
ISwapRouterof Uniswap V3
- Uniswap v3 oracle will prevent price manipulation in the same block. So, we should call
- The attack vector is the same as
A: Damn Vulnerable DeFi(18)
-
Shards
- First, we can buy shards for free becase the required token can be zero in case
want * _toDVT(offer.price, _currentRate) < offer.totalShards. We find the max number of free shards is 133. - Then, we can cancel immediately because of the incorrect timestamp check
- Again, there is also incorrect calculation of refund token here.
- We can keep calling
fillandcancelto fetch all the token ofShardsNFTMarketplace.
- First, we can buy shards for free becase the required token can be zero in case
A: Damn Vulnerable DeFi(18)
- Withdrawal
- As a operator, we can invoke arbitrary withdrawal to secure all the token inside l1 bridge.
- Then, we should leave enough token to l1 bride for 3 noraml withdrawals and not enough token for 1 suspicious withdrawal with pretty large amount.
- Then, after the
DELAY, we can finalize the 4 withdrawals.- The 3 normal withdrawals can receive the token
- The 1 suspicous withdrawals cannot recevie the token for the l1 bridge does not have enough token.
- No matter if the reciver can receive the token, the withdrawals will finalized.
- Finally, we should return the token back to the l1 bridge.
A: Damn Vulnerable DeFi(18) DONE
- CurvyPuppet
- Curve LP Oracle Manipulation: Post Mortem
- We can manipulate the curve pool using fund from aave flashloan
skip
B: EthTaipei CTF 2023(5)
- Arcade
- According execution order of the code,
_redeemacutally is executed after_setNewPlayerinsidechangePlayer - So,
getCurrentPlayerPoints()will get the point of new player instead of the old palyer. - Finally, old player can mint the token from the points of new player.
- According execution order of the code,
B: EthTaipei CTF 2023(5)
- Casino
- The
_betinsideplayis used to charge token from user_betwill first consider the input token is a CToken and try to callcToken.bet(msg.sender, amount). If faild,_betwill consider the input token as an token.- However, if the token has a fallback function, the
cToken.bet(msg.sender, amount)can be executed and charge nothing.
- The
CToken.getinsideplayis sendamount * slot()ctoken to user- So, we should find a slot where more than
amountctoken is sent.
- So, we should find a slot where more than
- Finally, we can call
withdrawto convert cToken to token.
- The
skip
B: EthTaipei CTF 2023(5)
- CasinoAdvanced
- Almost the same as
Casino - Besides, we should swap the free
WETHtoUSDCto play the game which will have 2x returns in some slots. Then, we can withdraw all theUSDCinside CasinoAdvanced. - Next, we should swap the
USDCtoWBTCto play the game which will have 2x returns in some slots. Then, we can withdraw all theWBTCinside CasinoAdvanced.
- Almost the same as
B: EthTaipei CTF 2023(5)
- ETHTaipeiWarRoomNFT
- At first, we can see that balance is changed after NFT transfer. Reentrancy attack is obvious.
- Then, solidity (version < 0.8.0) has not applied underflow check as default.
- Finally, we can withdraw twice using
onERC721Receivedcallback and make the balance underflow.
B: EthTaipei CTF 2023(5)
- WBC
- We should create contract
AnsimplmentingIGameto satisfy the requirements ofWBC - To pass
bodyCheck - To pass
readyjudge()ofAnsshould returnblock.conbase.
- To pass
_swing- To pass
_secondBase steal()ofAnsshould return theinputvalue by just copy- To pass
_thirdBase- We should find xxx thatthis.decode(xxx) = "HitAndRun".- xxx = 0*(32-10)||9||HitAndRun
function decode(bytes32 data) external pure returns (string memory) { assembly { mstore(0x20, 0x20) mstore(0x49, data) // [0x20(offset), 0*9 || data[0:23](length), data[23:32] || 0*23(raw)] // [0x20(offset), 0*9 || xxx.length(length), xxx.data || 0*23(raw)] return(0x20, 0x60) } }
- xxx = 0*(32-10)||9||HitAndRun
- To pass
_homeBase- We should find a way to distinguish the two sequential static call. - We can use
gasleft() - We can find a value
i-gasleft() % i == 0in the first call -gasleft() % i != 0in the second call
- We should find a way to distinguish the two sequential static call. - We can use
- To pass
- We should create contract
B: Grey Cat the Flag 2024 Milotruck challs (6)
- GreyHats Dollar
- The share finally updates at this line
transferFromhas not consider the case thatfromequals toto.- At this case, we get
shares[to=from] = origin + _shares - However, the share is expected unchanged.
- At this case, we get
B: Grey Cat the Flag 2024 Milotruck challs (6)
-
Escrow
-
Ownership of escrowId has been renounced at the end.
-
So, we should find a way to get back the ownership through
deployEscrow -
Approach 1
- To by pass the check here, we can construct different
argsthat can generate the sameescrowId argscontributes totokenXandtokenY. They are fetched here.tokenYis expected to beaddress(0).- However,
_getArgAddressjust reads 20 bytes fromargOffset. - However,
tokenYcan bebytes19(0). If the following byte is bytes1(0), we can always gettokenYasaddress(0)by_getArgAddress. - For length of data smaller than 256, so the following byte is bytes1(0).
- Why not just append 0 bytes to
args?- Becasue there is a length check here
- To by pass the check here, we can construct different
-
Approach 2
- To by pass the check here, we can construct different
implIdthat can generate the sameescrowId - We can add the same implementation here with the same
implparameter. - Then, we get different
implId(=1) with the sameimplandimplIddoes not contribute toescrowId - However, only factory owner can call
addImplementation.
- To by pass the check here, we can construct different
-