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!

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!

Postmortem: EP9 deployment - 1095

nick.eth    Reference →Posted 3 Years Ago
  • ENS is a naming service for wallets, websites and more. In March of 2022, there was a vote to replace the existing price oracle. The original interface returned 1 value but the new interface returned 2. These were the registration fee and premium instead of just the total.
  • The price oracle was updated to return two. However, the calling code was only expected to return 1. So, the registration fee was the only value being used, making the domains cheaper than they should have been.
  • The issue was discovered shortly after the proposal was made. While testing slightly different code, the issue was discovered and the voters were made aware of the issue. Overall, it's weird this wasn't caught during initial testing. I'm surprised we don't see more migration issues like this.

ENS Domain Name Character Parsing Bug- 1094

lcfr.eth    Reference →Posted 3 Years Ago
  • ENS stores domains. Once it's been registered, there is a metadata service that is offchain and written in JS/TS.
  • This is done with the following steps:
    1. Register the name
    2. Emit an event of NameRegistered
    3. The ENS metadata service
    4. The metadata service passes the event info to Subgraph.
  • When the JS/TS is parsing the string, it sees 0x00 has a nullbyte and terminates the string. By registering a name with a nullbyte at the end, it won't be a duplicate but it can be an arbitrary string in subgraph!
  • This is a pretty neat vulnerability! The combining of systems creating terrible and unexpected vulnerabilities.