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!

KaoyaSwap Path Double Counting Error- 1105

BlockSec    Reference →Posted 3 Years Ago
  • KaoyaSwap is a BSC Chain that is an AMM via exchange pools.
  • A Fee-On-Transfer token is slightly different than the standard ERC20. Typically, they implement the ERC20 interface with the main difference being the receiver gets a smaller amount to pay for the costs of the transfer. Normally, the fees are on the cost of the sender.
  • The function swapExactTokensForETHSupportingFeeOnTransferTokens takes in a path. This path is used to determine the swaps that are taking place by the protocol. For instance, A->B->C would take in token A, swap this to B then swap that to C for the user. Finally, the receiver would pay the fees for the transfer.
  • When determining how much to send to a user, it does this by calculating the difference in the balance before the function call _swapSupportingFeeOnTransferTokens() and after the function call. Although this is benign most of the time, there is a subtle bug here.
  • The path variable will perform as many swaps as we want. Additionally, there is no validation that there are duplicate tokens in here. This means that the difference calculation from above may include a transfer used earlier in the path! For instance, Token A->wBNB->B->wBNB would double count the difference n wBNB because the transferring was performed twice.
  • By performing all of these operations, the attacker was able to profit 271wBNB and 37K BUSD for a total of $118K advantage. To make this attack worse, they used a flash loan for the larger numbers. This twitter thread has a root cause as well.

Binance Smart Chain Token Bridge Hack- 1104

Andrey Bachurin - PT Swarm Security    Reference →Posted 3 Years Ago
  • The Binance has a token hub bridge that allows interoperability between two chains. These two chains are the EVM compatible Binance Smart Chain (BSC) and the Binance Beacon Chain used for management purposes. The currency of these chains is BNB.
  • Bridges do not work as you'd expect; it's more of a lockbox than anything else. Chain A has ownership of some tokens on Chain B. So, we look the Chain A tokens then unlock them for the specific user on Chain B. In the case of Binance, the logic for going to Chain B was busted.
  • When Chain B does the verification from the Chain A blockchain in Solidity, it uses a type of binary tree that can be used for verification called a Merkle Tree. This tree allows for the verification of the existence of a transaction on the other chain. Any discrepancy will indicate that the data in the node has been tampered with.
  • The BSC bridge uses a balanced binary tree called an AVL tree. The function handlePackage is made in order to add this information to chain B. It should be noted that this function is only callable by a relayer.
  • In the land of Cosmos, the relayer is how IBC communicates that a transaction on Chain A has occurred that Chain B cares about. A relayer doesn't need to be 100% trusted. Because, at the end of the day, the merkle proof speaks for itself. The relayer for BNB will parse the events at the very end of a block and send this to chain B.
  • To send it to chain B, there are 4 parameters: a source chain ID, destination chain ID, a channel ID and a sequence number. After this initial handshake, the relayer calls Chain B with the transaction data, the proof and the block height. This is how the data goes from a Chain A request to a Chain B request.
  • The Merkle proof library in Solidity was a precompiled IAVL library written in Go that directly interacts with Cosmos via a special EVM hook specific to Binance. Within Go, the function computes the root hash and verifies that it matches the hash against the new data being provided. If this is true, then the transaction must be legitimate.
  • The tree verification algorithm assumes that only one leaf node will exist. If there's a left node (if statement), then it allows verifies that. If there's a right (else statement) node, then it only verifies that. This becomes a problem when there are two leafs, which is 100% possible. With the logic of the program, we can provide a right node that will never be verified!
  • To exploit this, the attacker took a legitimate transaction and modified the payload to add the right node. The node said that a transaction of 2 million BNB with each BNB worth $293 at the time, was sent to them. Once they had done this, they took the money out of the bridge and laundered it to some other places. This resulted in a $586 million dollar hack.
  • In the aftermath of this, Binance forked the BSC blockchain to remove this. Additionally, USDT blocked the attackers address, preventing them from accessing a percentage of their stolen funds. To fix this, the proof errors out if there is more than one child. Overall, an interesting dive into the Cosmos and Binance eco-system.

BCrypt hashes erroneously validate if the salt is cut short by `$`- 1103

PHP    Reference →Posted 3 Years Ago
  • BCrypt is a popular hashing algorithm for passwords. In PHP, this is one of the standard ways to verify passwords.
  • PHP assumes that the hash will be in a proper format when using password_verify. This takes in the plaintext version of a password, alongside the salted and hashed password.
  • Within the modification to cryp_blowfish.c, there is a line of code that will cut a salt short if it contains a $ inside of it. However, since this was a modification of the code and not an original implementation, the developer didn't consider the ramifications this would have down the road. This change is literally labeled "PHP Hack", which is awesome.
  • Different places in the code assume that the salt and hash are a specific size. Since this isn't the case, this creates creates an out of bounds read in C.
  • More impactful though, is that the string can become truncated from this action as well. This occurs because of a strlen being used on a string that was assumed to have a specific length, which isn't the case. In some cases, this could even verify a password when it's incorrect. Pretty neat!
  • This attack is only possible via craftable hashes. Since this is a weird use case that isn't super common, it is doubtful this affects very many people but is interesting none-the-less.

The code that wasn’t there: Reading memory on an Android device by accident- 1102

Man Yue Mo - Github    Reference →Posted 3 Years Ago
  • Qualcomm chips are common in many phones, such as Samsung's and Google Pixels. Many of these devices have a Graphic Processing Unit (GPU) for performing various actions as well for things like shaders. GPUs tend to have proprietary instruction sets, making them hard to attack.
  • The kernel drivers have some GPU instructions inside of it though, as demonstrated by a Project Zero post. Using this, it's possible to somewhat understand the instructions of a GPU, but not totally. While playing around with the IOCTL interface for the GPU, the author of the post ran into some bugs. Errors were occurring constantly, unless they put a delay in. Even the opcode looked wrong when viewing the logs. But why is this? Down the rabbit hole!
  • The write opcode is structured as follows: opcode|gpu address destination|value to write. Since we were getting an error when executing this instruction with the delay, the author didn't know how to debug this. The workaround was to use two buffers instead of one. By doing this, they could see where the inconsistency was occurring at. Since we can read both buffers, this is easy to do.
  • On the first test of this, it appears that the values were userland addresses with a prefixed 0x7e. The hunch was that the GPU was reading data from the buffer from the previous use. The author attempted to replicate this by filling the buffers with a magic value. To their shock, their magic value appeared in their output! This means there is a major information disclosure big that can be accessed from Android applications.
  • When writing to memory, the access is done via the CPU and the content is first written to the CPU cache, then propagated to the physical memory later. In modern CPUs, the cache is the same across all of the different cores. However, the GPU access of the physical memory is slightly different, as it doesn't use the cache. This means it could access physical memory that is stale, since the real version is stored in the cache. The author thought this was a cache coherency problem.
  • The cache should be flushed prior returning this page for use. Viewing the code, the functions appeared to do so. Except, on ARM, some of these functions were not implemented, which resulted in NO-OPs instead of flushing the cache. Even though a page is marked as dirty, the cache is never flushed. Since the call to return the memory on the write never updates the physical memory, this is the vulnerability! Wow, so crazy.
  • This bug has no limitations - it can leak all memory from the GPU buffers. The caveat is that we are limited by what was written before (luck) and how fast the flush occurs. To me, a cross in the user boundaries between apps on Android is the worst thing possible.
  • Leaking kernel memory was trickier. This is because the kernel page allocator allocates pages according to their zones and migration types. For exploitation, this means that a user space call to MIGRATE_UNMOVABLE must overlap with a kernel page. After some poking around, they found something that met their requirements. Some other shenanigans and Android kernel specific knowledge later, and they had a complete KASLR bypass by reading specific objects.
  • Overall, a super crazy and impactful vulnerability that really wasn't that complicated. In fact, it was seen by other people in the past who simply rode it off as a bug. If something seems off, go down the rabbit hole; you may find a crazy vulnerability in plain sight.

Tecra Unlimited Burn Vulnerability- 1101

Mauricio Perdomo Cortes    Reference →Posted 3 Years Ago
  • Tecra (TCR) is some ERC20 token.
  • A burn in ERC20 is the destruction of a token; literally removing it from the total supply. Although this doesn't seem useful at first glance, pools rely on the amount of tokens in a pool to determine the price. The price isn't USD to token. The price is proportional to the pool ratio itself; trade 5 of token A for 2 of Token B.
  • In ERC20, there is a concept called allowances. This allows other addresses to spend money on your behalf. The map for this is allowances[OWNER][SPENDER]. The check for burning as another user was written in reverse though: allowances[SPENDER][OWNER]. So, an attacker could burn tokens in an arbitrary account!
  • The unlimited burn was exploited in a Uniswap pool. By removing the TCR tokens owned by the pool with the unlimited burn vulnerability, the ratio for the AMM got messed up. This resulted in extremely expensive TCR tokens to buy and really cheap of the other token in the pool.
  • Step by step, this was exploited as follows:
    1. Approve a big number of tokens to the uniswap pool. This is crucial, since the burnFrom messed up the ordering. So, to satisfy the require, we must allow the pool to access our tokens.
    2. Buy 101 TCR tokens from the pool.
    3. Use the unlimited burn vulnerability remove the TCR tokens from the pool. This will increase the price of TCR drastically.
    4. Sell back the TCR take make a large profit from the other token in the pool
  • Overall, a really bad vulnerability that is not uncommon to make. It's interesting this even got through QA testing.

Double Counting Free Collateral Bugfix Review- 1100

Immunefi    Reference →Posted 3 Years Ago
  • Notional is a lending and borrowing platform on Ethereum. Most operations for their platform are performed using their fCash token. These tokens are redeemable for positive or negative cash flow at a later date, acting as a receipt/IOU. The fCash token is also denoted in its underlying token, such as DAI or USDC. Once settled, it is given out in cToken (collateral tokens, such as cUSDC), which can be redeemed for the original token.
  • All users have a portfolio. This is an array of assets, which is governance limited. There is also an additional asset bitmap portfolio that is very handy for market makers. The name is bitmap, so I assume that the data is a literal bitmap. A user can only hold data in only of these data structures at a time though.
  • While switching from one bitmap to another, there is validation in place.
    • Bitmap Enabled:
      • Validate no assets are currently set in the bitmap.
    • Bitmap Disabled:
      • Require there are no assets in the array from the first mode of operation.
      • Call the function setActiveCurrency. This ensuring that there is no double counting of the current collateral.
  • The bitmap currency is used to keep track of the current currencies being used by the account. This appears to be important for bookkeeping on how much a user has. These currencies can only be changed if there are no debts or credits in the bitmap asset.
  • The call to setActiveCurrency() in the else clause even has a comment about "... so there is no double counting during FreeCollateral.". However, this function is ONLY called during the else clause and NOT the IF statement. As a result, the activeCurrencies field is NOT cleared.
  • By calling external code that sets another currency, such as depositUnderlyingToken(), setActiveCurrency will be called for the account. To get two currencies to be active, we can call enableBitmapForAccoutn with a currency we do not care about then make a second call to enableBitmapForAccoutn after the deposit mentioned. Due to the logic error, the free collateral calcuations will run twice on the asset deposited: once for the bitmap and once for the activeCurrencies.
  • What does free collateral mean in this context though? To evaluate the position of the user, the debts are significantly over collateralized. To denote this, Notional uses free collateral to denominate the ETH that an account holds beyond what it needs to meet the minimum collateral requirements. If positive, the position is proper. If negative, it's eligible for liquidation.
  • If the amount of free collateral is doubled (with the bug above), an attacker could borrow more without actually holding enough of the currency. This means they can trade a small amount of one token and take a loan out for more with the protocol, making it possible to just never pay back the loan to make a profit!
  • To exploit the bug, the steps needs to be taken:
    1. Enable the bitmap portfolio for any currency.
    2. Deposit funds using depositUnderlyingToken() on the currency you want to double.
    3. Enable the bitmap currency again. This performs the double count operation, making it believe we put in more collateral than we actually did.
    4. Transfer out the fCash. This will be worth more than our original collateral.
    5. Convert fCash into another token and make the money!

How did a hacker steal OP?- 1099

Banteg    Reference →Posted 3 Years Ago
  • Optimism is a L2 blockchain and Wintermute is a liquidity provider.
  • Optimism sent funds to Wintermute on the L2 chain but it should have been on mainnet ETH. So, nothing should happen, right?
  • Wintermute's mainnet safe from deployed using an older version of the Proxy Factory; earlier than the first Optimism deployment. The Safe that was deployed uses a non-EIP-155 (replay attack prevention) compliant deployment!
  • An attacker can replay the deployment with the Proxy Factory used by Wintermute on Optimism. The safe was created using the CREATE opcode, which uses the contracts nonce to determine the address. By creating enough contracts (8884), one will have the proper nonce and deploy to the proper address.
  • They had now grabbed the address that Optimism sent the funds to. The Gnosis Safe deployed there could now retrieve the lost funds for themselves.
  • Interesting vulnerability that required a lot of things to go wrong. Pretty neat!

Bunni Finance MEV Exploit- 1098

Riley Holterhus    Reference →Posted 3 Years Ago
  • Timeless Finance released a product called Bunni. The purpose of this is to make Uniswap v3 liquidity pools composable. This allows loans to be taken out via Uniswap liquidity and aims to have lower gas fees.
  • When a user passes in funds and it mints tokens, the function _mintShares() is called. If the ERC20 token has zero supply, then the minted shares are simply the amount of liquidity added. Otherwise, it mints the amount proportional to the percentage of the total liquidity.
  • In the second case, this is done with mulDiv(totalSupply,liquidityAdded,existingLiquidity). It should be noted that the division will round down. If totalSupply * addedLiquidity is less than existingLiquidity then this value could be 0.
  • An attacker can take advantage of this scenario with the following steps:
    1. A user deposits funds into the new contract. For example, let's use 10K in liquidity.
    2. A MEV bot sees this and frontruns the transaction with two steps:
      1. Provide 1 wei of liquidity to the contract to make the supply 1.
      2. Provide 10,001 of liquidity to the Uniswap pool on behalf of Bunni. This is possible with the standard Uniswap functions.
    3. The users transaction executes. The math turns out as follows: 1 * $10K / 10,001. This will round down to 0.
    4. Withdraw all of the liquidity, including the profiting 10K.
  • The attacker owns all of the original shares from the 1 wei deposit. Since the attackers owns all of the shares and zero shares were minted for the user depositing 10K, withdrawing the funds from the contract will get all of the funds!
  • To fix this problem, there are two main ways. First, requiring a minimum deposit to start with could make this infeasible. The other solution would be having a check to ensure that it's not possible to create zero shares.
  • The twitter thread links to code4rena and by Yannis Smaragdakis, which have similar findings. Overall, interesting find that exploits weird math.

BabySwap User Supplied Addresses- 1097

Block Sec    Reference →Posted 3 Years Ago
  • BabySwap is a trading platform on BNB chain.
  • When performing a swap call, the address of the factory is used controlled. With proper input validation, this would be okay. However, this factory could return a fake token pair for BabySwap.
  • The fake pair, with the proper interfaces implemented, performs fake swaps. Although this doesn't seem like a big deal, it's pretty terrible. Since the contract thinks that it was a real swap, it records rewards. By inflating the cost of the rewards with the fake trades, the attacker can take a lot of money from th contract. Real BABY tokens from the fake swap.

FEG Token Flashloan Exploit Analysis- 1096

Certik    Reference →Posted 3 Years Ago
  • FEG (Feed Every Gorilla) is a peer-to-peer trading protocol with its own governance token FEGToken on the Binance Smart Chain. It also supports NFT trading.
  • The project allowed for user supplied addresses to approve the spending of their deposited funds. However, this approval was kept separately and didn't do bookkeeping on the amount of funds the user actually had at the time of spending. It manually increases the balance of the other user without actually subtracting from the user until the money was spent.
  • An attacker could (and did) exploit this issue. This was done by approving multiple addresses to use the same funds. Then, double/triple/12uple spending the money. The allowance to themselves without checking the users underlying balance created a money duplication bug. This was done multiple times to drain the contract.
  • To make matters worse, an attacker used a flashloan to get a ton of funds this performed this attack. Bad bookkeeping leads to a loss of funds. Very sad!