Resources

People often ask me "How did you learn how to hack?" The answer: by reading. This page is a collection of the blog posts and other articles that I have accumulated over the years of my journey. Enjoy!

Nomad Bridge Hack- 911

samczsun    Reference →Posted 3 Years Ago
  • Nomad bridge is a cross-chain protocol between Cosmos compatible blockchains, such as Moonbeam, and Ethereum. Being able to transfer ERC-20 coins between chains is amazing for scalability but is really hard to do correctly. 4 out of the 5 biggest hacks on rekt.news have been on bridges.
  • When moving stuff across chain, there appears to be two functions for this: prove() and process(). The process() function is used for uses a transaction from one chain to another after proving it.
  • To validate if the message hash is valid, the following line of code is ran:
    require(acceptableRoot(messages[_messageHash]); 
    
  • The following code can be broken into a few steps:
    1. The variable _messageHash is the keccak hash of the data being added to the blockchain.
    2. The map (dictionary in Python) is used to find the matching hash for this message. If it's valid, it will the expected root hash. Otherwise, it will return 0x0, since that's the default for a bad map find in Solidity.
    3. acceptableRoot() is called to ensure that the root hash of this message is valid. If it is, the transaction continues. Otherwise, the contract reverts.
  • So, this looks like good code, what's the problem? During an update of the smart contract, the hash 0x0 was made into a valid root! Since a Solidity map defaults to 0x0, any fake transaction will pass. Wait, how is this repeatable?
  • It turns out that the function acceptableRoot() had been updated to handle an ENUM in legacy code. Instead of having PROVEN and PROCESSED (which return true and false respectively) the legacy code checks to see if the time for the data passed in is 0. Since the structure will return a hash time that is not 0, this ALWAYS passes. Thanks to Zellic_io for tracing this down.
  • For the repeatable effect, I'm still not 100% sure about it. According to source code the 0x0 hash would be set to a value of 2 (PROCESSED) then would never be usable again. The state to update does messages[_messageHash], making it so the specific call cannot be used again. However, we can remake this call with slightly different values over and over again!
  • What is super crazy about this, is that the QuantStamp audit called this out as a potential issue. I never would have called this out, since it seemed like it was part of the design.
  • The attack lost 190 million but it appears that several of these people are whitehats who will give the money back. Regardless, I'm sure that 75% is gone forever. All of the other platforms that rely on this bridge, such as Moonbeam and EVMO, drastically sank in value from this attack.

Pods Finance Bugfix Review- 910

Immunefi    Reference →Posted 3 Years Ago
  • Pods Finance has a rewards system built into the protocol. If you decide to put issue options, then the contract will mint rewards for you.
  • The vulnerability, which was present in both rewards system, allowed a malicious attack to claim rewards for other users. If a user had minted at least one option in the pat, they could repeatedly call unmintWithRewards() in order to claim the same share over and over again. By doing this, all of the funds from the contract could be stolen.
  • The fix for this was calculating the reward based upon the options being unminted and NOT the shares that the user possesses. Even though this could steal all of the funds from the contract, it was rated as "high" instead of "critical" since just the theft of rewards was possible. Regardless, being able to drain the contract of all the money seems like a critical finding.

RocketPool and Lido Frontrunning Bugfix Review- 909

Immunefi    Reference →Posted 3 Years Ago
  • RocketPool and Lido are both third party staking pools for Ethereum 2.0. Proof of stake, vs. proof of work, is the future, as it should make the Ethereum much more performant. Instead of random people computing a hash, a staker will propose and validate blocks from other validators. A person is only a staker if they have deposited funds into the staker contract themselves.
  • Currently, to be a staker in Ethereum 2.0, it is 32ETH or around 112K. Because this is so expensive, staking pools were created. The purpose of this is to pool together user funds to become a staker as a group. Then, from staking, you get rewards for doing so.
  • When an Ethereum transaction is made, it isn't made instantaneously. Instead, it is stuck into a queue of our transactions. The validators will choose which transactions to put into the next block, depending on the amount paid to perform the transaction. So, what would happen if somebody abused their knowledge of the queue to do something malicious? This is called frontrunning.
  • The deposit contract is used for sending the money to be a validator. This takes in three main parameters: BLS public key for validator, withdrawal credentials and signature of the data above.
  • This is how a user signs up for the service. When value is added to deposit for a NEW user, the user is created within an IF statement. However, if the user, which is defined by the public key is already in the system, the funds are simply added to that validator.
  • An attacker can SEE the presence of a deposit using a specific public key. Once they see this, they can create their own transaction linking their account to this public key PRIOR to the original user. Once the original users transaction goes through, the funds will be added to the attacker account, because of the else clause, instead of being in the new users account. Damn, that's crazy!
  • Frontrunning appears to be more of a problem within the cryptocurrency bot community. However, it does have serious consequences in the security world as well. In this case, the identification of a user was NOT their address; instead, a value that could be spoofed. Interesting bug that the developers explain well here as well.
  • It should be noted that this required a node operator to go rogue to perform this attack; this is considered a privileged user nonetheless but still got a 100K bounty for each project this effected. The authors include a few possible mitigations as well.

PancakeSwap Logic Error Bugfix Review- 908

Immunefi    Reference →Posted 3 Years Ago
  • PancakeSwap is a platform for swapping tokens and many other functionality. In this blog post, the author goes into the lottery functionality. The vulnerable code persisted in several other projects, since they had forked PancakeSwap.
  • When claiming a ticket, an array of tickets can be used. The flow of code verifies that the ticket has not been claimed yet and adds the reward for each ticket in a variable. Once the verification has been done, the reward is sent to the user.
  • This becomes a problem when the tickets are NOT all unique. In particular, the verification step does NOT mark the ticket as used. So, the same ticket can be provided multiple times. None of the checks validated that this happened.
  • To pull this off, you would need to win a lottery ticket, which isn't very hard. Once you win the lottery, call multiClaim() with the same winning ticket up to 255 times. I think there is a hard limit on the amount of elements for dynamic arrays in Solidity.
  • Overall, a really simple bug that could have lost all user funds. The fix was to set the lottery ticket specified to have claimed the reward during the verification step.

Two Rights Might Make A Wrong- 907

samczsun    Reference →Posted 3 Years Ago
  • The author beings with a quick statement: "A common misconception in building software is that if every component in a system is individually verified to be safe, the system itself is also safe". This article is really into the mind of the blockchain hacker.
  • SushiSwap has a MISO platform (sidenote: if you want to be taken seriously, please don't use ridiculous names like this). This platform has two types of auctions: Dutch and Batch Auctions. The author quickly skimmed through all of the contracts to see nothing wrong obviously wrong with it.
  • While scrolling through all of the files, they found two libraries: SafeTransfer and BoringBatchable. The BoringBatchable library is added in order to easily introduce batch calls to any contract that imports it. This is done with a simple delegateCall flow.
  • In a previous time, the author of the post was on a call with the Opyn team about trying to protect user funds after a horrible attack. The contract allowed for the batching of multiple calls and would REUSE the msg.value in the loop. This was the same bug in a different form.
  • In the context of an auction... you could send 1ETH to the batch functionality. When calling this functionality, you could call commitEth for an auction once. Then again. And over and over for all auctions, while only ever spending the 1ETH.
  • Is there anything else we can do besides win auctions for free? Yes! We can call refund on repeat. By sending ETH that went over the auctions hard cap, the contract would simply refund the ETH. By doing this over and over again via the batch call, all of the funds from the contract could be drained.
  • The timeline of this is absolutely wild. From discovery to being on a call with the Sushi team, it was only 2 hours. No funds ended up being stolen.
  • Overall, the post is awesome and really puts you into the mind of a hacker. Sometimes, taking a few extra minutes to look at something can change the world. Welcome to the world of hackers!

MCDEX Insufficient Validation Bugfix Review- 906

Immunefi    Reference →Posted 3 Years Ago
  • MCDEX is a decentralized exchange and layer 2 platform that allows users to trade perpetual contracts.
  • When performing batch trades, a user can provide the liquidity pool contract. The liquidity pool is expected to do several validations on the data. However, since this contract is controlled by the attacker, this is a major problem.
  • Later, the Broker contracts reimburses gas expenses by transferring funds from a user's balance to a destination address. Since both of these can be chosen by an attacker, we can steal funds from any user! Simply put: we can set the pool contract to say this is a legit transaction from the pool. When, in reality, it is not.
  • All funds in the Broker contract can be stolen by specifying that the user with funds that should pay the gas fee and putting a large sun for this. Neat bug, where calling an outside contract ended up being a catastrophic failure.

Tidal Finance Logic Error Bugfix Review- 905

Immunefi    Reference →Posted 3 Years Ago
  • Tidal Finance is a discretionary mutual cover protocol that offers the DeFi community the ability to hedge against the failure of any DeFi protocol or asset. In normal person terms, this is insurance.
  • A user is able to stake or add assets to the pool. By providing assets to the pool, they are entitled to rewards from fees for using the service. The protocol needs to distribute these rewards evenly, depending on the amount of value contributed.
  • The payout for rewards has four steps: startPayout, setPayout, doPayout, finishPayout. The administrator of the contract can initiate a payout for Asset at some index. Then, the four payout functions are called in order. The payout is marked as finished.
  • A user has two main fields: rewardAmount and rewardDebt. The rewardAmount indicates the amount of money a user should be paid out. rewardDebt shows the amount taken out already. Since the pool had already finished, the rewardDebt for the asset is 0 (default value of Solidity).
  • When doing the payout, the math user.rewardAmount - user.rewardDebt is performed. But, user.rewardDebt starts with 0, giving the user more money than they are really entitled to. In particular, the unset variable of the user for a specific field is what is causing the problem here.
  • Many of the bugs in smart contracts are logic issues with the handling of money. It is really easy to have small/subtle mistakes with multi-step processes.

Belt Finance Logic Error Bugfix Review- 904

Immunefi    Reference →Posted 3 Years Ago
  • Belt Finance has a strategy token. This represents shares within the pool of assets. Each token is given out proportionally (pro rata) for assets put into the strategy contract. The strategy token is an interest baring asset as well.
  • To understand the bug, we need to understand the withdrawal flow of the contract. There are two main variables for keeping track of funds: balanceSnapshot and wantLockedInHere. wantLockedInHere is the balance of the contract not being put to work to generate yield on the assets. balanceSnapshot holds the balance of the contract.
  • When calling withdrawal, two paths can be hit. First, if the contract has enough funds in wantLockedInHere it will send this. Otherwise, it will liquidate the yield-generating asset and decrease the value of balanceSnapshot.
  • When making a withdrawal from the contract directly, instead of through a different contract, there is a double counting bug that occurs. In particular, both balanceSnapshot and wantLockedInHere will be subtracted from. Why does this happen when calling directly? There's an if statement that fails to actually NOT withdraw the money but update the state variables.
  • By making these variables very small, the contract has much MORE assets than it believes. Because the perceived value is so low, the attackers amount of shares appears to be much higher than it actually is. Now, when the attacks deposits money again, the contract will mint too many shares because of how low the balance appears to be. A attacker could call earn to get the real value of the contract. Practically, this means that this attack can be performed multiple times.
  • Finally, an attacker calls withdraw to claim all of the shares they have earned from the contract. They now have more money than what they started with.
  • Calling contracts in weird ways causes problems! A great bug find for a 1 million dollar payout.

The Alpha Homora DeFi Hack- 903

Rob Behnke    Reference →Posted 3 Years Ago
  • HomoraBankv2 allows for the usage of a custom smart contract for providing logic called a spell. The only check performed is that the loan is greater than the borrowed amount for custom contracts. When this was exploited, the bank was prepped for an upcoming release, with no UI and nothing publicly announced. This fact meant that an attacker can fully manipulate the market, since there is no liquidity inside the contract yet.
  • First, the attacker takes out a loan of 1,000e18 sUSD from the bank. When paying back the loan, the attacker should have to pay back 1000.000098548938710984 sUSD. However, a rounding error in the protocol only required them to pay back 1000.000098548938710983 sUSD. By doing this, the debt is now at 1 minisUSD and 1 debt share. Finally, by calling resolveReserve, the debt increases but the shares remains at 1. Because of the off by 1 error, the contract believes that NO money has been taken out!
  • By exploiting this bug over and over again, they are able to take out loans with only a single share. This bug only occurs when you are the only owner of shares in the contract. Neat! The pre-release aspect of this attack was quite important then.
  • After performing the attack above, they couple this with a flash loan attack to extract the sUSD from the contract to steal even more money. I don't understand this part of the attack though.
  • To me, there are few takeaways. First, a single off by 1 error resulted in a horrible security bug. Second, having this functionality public prior to launch seems strange as well. Finally, this contract was audited twice and still fell victim to attack. This is the second largest heist of an audited smart contract platform, only behind Wormhole.

The PancakeBunny Protocol Hack- 902

Rob Behnke    Reference →Posted 3 Years Ago
  • Pancake Bunny is a yield farming aggregator and optimizer for Binance Smart Chan (BSC) and Ethereum. The attacker took out a flash loan before doing this attack on BNB and Tether (USDT).
  • First, the author minted a large amount of Liquidity Provider (LP) tokens for the pool. The price of BUNNY (the LP token) is based upon the BNB compared to the amount of USDT in the pool. Another description of this attack with more detail is found here.
  • Then, they swapped a large number BNB into the pool for USDT. By swapping such a large number of tokens into the pool, the exchange rate was drastically modified. The pricing of tokens depends on the balance on the swapping. By taking a ton of them out, the pool becomes unbalanced. This makes the BNB token very expensive and USDT very cheap.
  • Here is the main issue: the BUNNY tokens that are minted is strictly based upon the amount of BNB compared to the amount of USDT in the pool. So, by exchanging their LP tokens, they claim more BUNNY tokens then they should be entitled to.
  • Finally, they repay all of the locations of the flash loan by swapping the BUNNY for something else on other exchanges. Wow, flash loans are crazy complicated... The simple remediation is to require multi-transaction operations. Additionally, using a pricing oracle, such as Chainlink, could have solve this problem as well.
  • The same company was hit by ANOTHER flash loan attack on their Polygon version of this. In this case, the flash loan allowed them to get a crazy performance fee to mint too much BUNNY (again).