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!

Uniswap's Financial Alchemy- 1193

Dave White    Reference →Posted 2 Years Ago
  • Uniswap is one of the best Automated Market Makers in the DeFi space. Because of their innovation, they had to create solid mathematical models in order to ensure everyone makes a profit. How did they do this?
  • In Uniswap, there is a concept of a pool; this is a collection of assets controlled by various individuals. The users provided value or tokens into this are liquidity provdiers (LP). When another user wants to perform a trade, they trade one asset for another asset within the pool. The LPs get rewards in the form of fees put on the users performing trades.
  • This comes with a problem. The price of the assets in the pool moves randomly. Additionally, it makes the assumption that all traders are informed; meaning that the price gets arbitraged to the proper price threshold. In other words, this means that ever liquidity provider would lose money from impermanent lose on their assets.
  • Market makers demand a lower price to buy than to sell, they directly profit when assets don't move in price with an even amount of buys in sells. The arbitrager comes in and fixes the cost of the market compared to the real world, stealing value along the way. It seems like the LPs would always lose money. So, where's the magic?
  • The concept of volatility harvesting comes into play here. It is possible to outperform any static portfolio of two assets by periodically rebalancing them. When the market gets arbitraged, the LPs are paying a fee to the market for the portfolio to be rebalanced. By redistributing the portfolio over time (instead of it being static), it is more accurate to reality.
  • The next concept is volatility drag. When using multiplication on betting, the results can be devastating. For instance, if we start with $100 and have an equal chance of the asset going up by 75% or dropping by 50%, this sounds like a wonderful deal. However, in reality, it is very hard to recover from a loss.
  • The expected value of the equation above is 50/2 + 175/2=$112.5 in isolation. But, if we consider the compounding asset to this, it's different. A 50% loss and a 75% gain gives us 87.5% of the value. This is the same in the other direction as well. The effects of compounding on gaining back the wealth are devastating. This is based upon the Kelly Criterion optimal betting strategy.
  • So, what's the lesson? Keep some of your money in reserve! Don't put all of the eggs in one basket, as they say. Instead of betting all of your money, only bet a portion of it. This way, your positive bet you placed will win in the long run. For instance, keeping $75 and betting $25 with those values will yield different results. A 75% gain then a %50 loss will end up with $131.25 instead of a loss. This is because we kept some of the value in the second step after making money.
  • In Uniswap, they learned that making the fee as cheap as possible to incentivize rebalancing is important. It pays to an LP vs. simply holding onto the asset in cash if the fee is not zero and the volatility is between (2 * sqrt(fee))/sqrt(3) and 2sqrt(fee). What's going on here? If the asset is too volatile or doesn't move at all, you're better off keeping the asset. Within that middle zone, we can stop volatility drag and make a profit from it though.
  • This is an interesting post on the finances of the market. I would love to learn more about market making and how the math works behind this in the future. Good read!

LPE and RCE in RenderDoc: CVE-2023-33865, CVE-2023-33864, CVE-2023-33863- 1192

Qualys    Reference →Posted 2 Years Ago
  • RenderDoc is a graphics debugger that allows for quick and easy introspection of graphics applications. It supports many different types such as Vulkan, D3D11, OpenGL and more. This is a write up on 3 vulnerabilities and the full exploits on them within RenderDoc.
  • librenderdoc.so is LD_PRELOADed into the application using a library load function. This works by creating a directory with /tmp/Renderdoc/ and calling open on a log file in this location with append mode. However, there is no validation on who owns the file, in the case that a malicious user wins this race.
  • The data is partially controllable by writing data to the TCP debugger server. Sadly, it has a major string header at the top that makes traditional configuration files not viable for escalation. How did they get around this? There's a call to fgets() within the systemd processing that only get 512 bytes at a time. By sending a very long string, we can use only our data on a given line.
  • An additional way to escalate privileges is to write to , using the truncation mentioned above they write to .config/user-dirs.defaults with SYSTEMD=.config/systemd to create a systemd directory in a user controlled location. By writing to a configuration file in this directory, code execution is trivial to achieve. To bypass the head issue again, the authors abuse a different in deliminators (\r) to add their own lines.
  • The second vulnerability is a heap based buffer overflow that occurs from unexpected sizes in processing the client name. This overflow does not seem very useful to start with but these people are wizards! When a thread of renderdoc starts up, glibc malloc allocates a new heap for the thread within an mmaped section of memory. This is always 64MB, aligned in 64MB groups and is mprotected for more security. This section is close to the libraries but there is a gap in memory.
  • The authors decided to use a technique that I've documented here. In glibc malloc, the mmap chunks have a special bit. By calling free() on an mmap chunk, the chunk isn't put into a free list; it's literally unmapped with munmap(). Arbitrarily mapping and unmapping memory is an incredibly powerful primitive. The munmap and mmap calls are the attack method but there is very crazy strategy to it.
  • They have a text picture explaining the setup. By using the vulnerability from before in the 64KB buffer, they are able to corrupt another mmap chunk directly ahead of it. Once the chunk ahead of it is updated to be extremely large. This is because we want the munmap() call to succeed. The goal is to punch a hole of exactly 8MB+4KB, which is the size of a threat stack and its guard page.
  • With the gap ready to go, we can allocate our data into that location that we want. This is done by simply connecting to the server, which creates a new thread in this gap, then disconnecting for this to be reused. After this, a large allocation of the client name (without triggering the vulnerability) will overlap with this section. I don't fully understand why this is the case but I'm assuming it's weirdness with munmapping memory.
  • With a long lived connection with our client name data being readable and overlapping with the stale client stack, we create a great primitive for reading and writing data. First, they force a new stack into this area. By doing this, a bunch of libc, stack addresses and much else can be leaked. After this, they use it to write to the RIP on the thread stack to hijack control flow with a large amount of ROP gadgets. It's weird that this works because of stack canaries though.
  • The final vulnerability is a fairly straight forward integer overflow that leads to a large amount of data being written to a small buffer. Overall, an amazing and innovative post. The shout out to me was appreciated and made my day!

Striking Gold at 30,000 Feet: Uncovering a Critical Vulnerability in Q Blockchain for $50,000- 1191

Blockian    Reference →Posted 2 Years Ago
  • Q (lolz) is a proof of stake EVM compatible blockchain. It's native currency is Q tokens that are used for voting, staking and much more.
  • The voting mechanism has four components:
    1. A proposal is created.
    2. Voters lock their Q Tokens.
    3. Voters cast their votes on the proposal.
    4. The proposal is either accepted or rejected.
  • The _vote() function counts votes that are proportional to the amount of tokens they have. After enough time the proposal is either accepted or rejected.
  • To use a Q token for voting, the token is locked. Users can delegate their voting power to other users as well. The voting power of a user is calculated based upon the quantity of Q tokens that have been locked until the end of the proposal voting period.
  • The vulnerability is about the counting the votes. Using a particular flow, we can get tokens to be counted twice.
    1. User A delegates their Q tokens to User B.
    2. User B votes on a proposal, with the incorporating voting power from User A.
    3. User A announces the unlocking of their Q Tokens.
    4. User A votes on the same proposal with the same Q tokens being counted twice.
  • Why does announcing the unlock make this possible? It seems like such a weird flow! The flow of operations does not seem to consider the case where a delegated entity had already voted then decided to unlock their tokens. At this point, it is assumed that the person will take their tokens out; but, they are still able to vote.
  • Voting bugs are always fun! Double counting bugs on voting are terrible and compromise the eco-system. Write up could have been more clear on WHY this happens instead of the teaser they include.

Strategy v2 Burn Bug Post Mortem- 1190

Alberto Cuesta Canada    Reference →Posted 2 Years Ago
  • The Yield Protocol is a fixed-rate borrowing and lending protocol on Ethereum. As demonstrated by the name "Yield", getting yield from the assets provided is an extremely important part of this protocol.
  • With ERC20 tokens liquidity provider tokens, the mint() and burn() functions are common for adding liquidity and removing it from the protocol. mint() will create LP tokens from the provided asset token provided. burn() will destroy the LP token and give back the original asset token. These tokens are used for portions of the pool rewards.
  • In the burn() function, all of the users tokens are burned. Then, based upon the amount of tokens burned and their share in the pool, it will give them the underlying token back. The code for this is below:
    uint256 burnt = _balanceOf[address(this)];
    _burn(address(this), burnt); 
    
    poolTokensObtained = 
      pool.balanceOf(address(this)) * 
      burnt / 
      totalSupply_;
    
  • The attacker can donate a large set of tokens to inflate the balance of the pool. This leads the pool to the pool sending more tokens to a user than they should. Crazily enough, these donated funds are not lost though! An attacker can call mint(), which will use the difference between the balance and the cached pool. So, the inflation of the amount of tokens being sent to the attacker doesn't cost them anything.
  • The article has some interesting insights into the development process. First, cache-based contracts are known to be vulnerable to donation attacks if the developer is not careful. The author mentions going through the YieldSpace-TV project and validating that every single use of balanceOf() was not vulnerable.
  • This new feature was audited and missed. However, they mention that the level of complexity of this bug warranted a code based audit. The time pressure led to an internal audit instead. In this case, the bug bounty program saved the day, which is amazing! Having multiple levels like this prevents major hacks from happening.
  • Once the vulnerability was discovered, the protocol decided to use the eject() function to take all of the funds. They learned a few lessons from this warroom. First, having a pause() would have allowed them to explore their options without an attack being viable. Second, the contract is not upgradable but uses the code>eject() functionality to recover funds. By having the ability to upgrade contracts, restoring the protocol would have been much easier.
  • Overall, an amazing post into the world of bug bounties, handling issues and protocol design. The fix is a single line change to use the cached version instead of the balance of the pool.

PoC for libssh Auth Bypass - CVE-2023-2283- 1189

Kevin Backhouse    Reference →Posted 2 Years Ago
  • SSH is used to log in to servers by everyone. Finding a vulnerability in the authentication process for this would be catastrophic. This is exactly what the author found here! In this case, it is libssh and not openssh, meaning that we cannot simply log into other people's servers.
  • The function pki_verify_data_signature is used during the public key authentication check. In particular, it's checking to see if we've provided the proper signature to authenticate. At the beginning of the function, the rc (return code) is set to SSH_ERROR in order to prevent accidentally returning the improper value in case of a jump to the end.
  • However, one of the later function calls sets the variable when doing a hash comparison check. The idea was to reuse the variable rc for various calls. But, this comes with a problem: if we can get rc returned with the code assuming that it's set to the original default value, we could spoof a success! In several places, there is a goto that assumes this. A good find for code snippets is here. But, in what cases?
  • There is one directly before the signature verification! How do we trigger this to error? The function is trying to get a directly object from malloc. If the code errors, it would only be under extreme memory pressure. So, we need a memory leak or something else in order to trigger this?
  • Kevin's POC generates a large amount of memory pressure by sending a large number of service requests that require zlib compression. By not reading the reply of the server, the data is kept in memory. Even though this isn't hard to do, it's complicated to have it run out of memory at the exact right time on this tiny 72 byte allocation.
  • Analysis of the memory at this point did not work very well. So, instead of doing further work, they simply embraced the chaos. They kept triggering out of memory errors in the wrong locations. So, they ran the same copy of the PoC multiple times and it works! By bombarding the service, an attacker will eventually get lucky.
  • This bug is only exploitable in very memory-constrained systems. The author uses a container where only 256MB are allowed to be used. Additionally, since this is a library, it depends on how the library is used. The author was using the demo ssh server from the examples directory to test this out.
  • Overall, a simple bug once you see it but a crazy hard thing to find and exploit. Good work!

Jimbo's Protocol Hacked- 1188

Rekt    Reference →Posted 2 Years Ago
  • Jimbo creates a semi-stablecoin via rebalancing. This is version 2 of the protocol, which was an attempt to fix the first version with too many bugs in it.
  • The whole point of this protocol is being able to rebalance (buying or selling accordingly) itself based upon the current state of the market. By doing this, the pool would keep a specific percentage of resources through out. By having a pool of resources, this would hopefully make the coin stable.
  • This rebalancing for stability sounds like a blessing. However, this makes the assumption that the pricing is done fairly and equally. In the case of Jimbo, the rebalancing with bad prices was possible. With a bad price on the rebalance, the protocol lost an insane amount of money ($7 million). With an inflated price of Jimbo, the JimboController would transfer the contracts ETH back into the pool. By selling the Jimbo back to the pool, the attacker could make off with some extra profit.
  • To hit this vulnerability, the attacker took out a large flash loan then performed the following actions:
    1. Swap a large amount of ETH to get JIMBO from the Uniswap and Trader Joe pools. NOTE: This causes a major surge in the price of Jimbo compared to ETH.
    2. Call shift() to rebalance the contracts assets for the Jimbo Controller.
    3. Use the now extremely valuable Jimbo tokens to get back the ETH.
    4. Leave the protocol in complete shambles. Do steps 1-4 over and over again.
    5. Do steps 1-4 over and over again.
    6. Repay the flash loan and keep everything else as profit.
  • According to Peckshield the issue was a lack of slippage control on the protocol-owned liquidity being invested. In particular, a time waited average or price change check should have been added to account for these large attacker controlled changes.

DFX Finance Rounding Error Bugfix Review- 1187

Immunefi    Reference →Posted 2 Years Ago
  • DFX Finance is a decentralized foreign exchange protocol that allows users to swap many stablecoins. DFX is an AMM that exchanges tokens according to a bonding curve, which is dynamically calculated from Chainlink prices. Each one of the currencies is paired with USDC in a pool.
  • There are two main parts to the DFX protocol: Assimilators and Curve. Assimilators allow the AMM to handle pairs of different values for fixed point arithmetic, since Solidity doesn't support floating point numbers. In particular, it's responsible for converting all amounts to numeraire, a base value for all computations. DFX Finance maintains the assimilators which integrate with Curve to provide proportional liquidity to pools.
  • When users would provide liquidity to a pool to receive yield on their stablecoins, they call the deposit() for a Curve pool and receive LP tokens. During this process, a check occurs to see if the deposit amount is greater than zero.
  • The EURS token is a token with only two decimals. When creating these sorts of protocols, the developers may not have considered these small decimal tokens. The amount to transfer from the user is calculated based upon the following values:
    amount_ = (_amount.mulu(10**tokenDecimals) * 1e6) / _rate;
    token.safeTransferFrom(msg.sender, address(this), amount_);
    
  • This code leads to zero tokens being transferred from the user. Despite this transferring zero tokens, the attacker gets some value being put into the contract. However, it is an extremely small amount of LP tokens that are sent back to the user.
  • Normally, tokens have at least six decimals. So, the amount of tokens gained from this would be tiny with these larger tokens. Because of how small the values normally are, the amount spent on gas would dwarf this. However, since the EURS token only has two decimals, this attack becomes viable! By depositing a tiny amount 10K times, an attacker can get $190 per attack by withdrawing the CURVE LP tokens.
  • To fix this vulnerability, there is a simple check to make sure the amount of funds transferred is not zero. The hacker got a 100K bounty for a 230K pool; this is a good look for the protocol. Overall, this is a fascinating vulnerability that was only possible because of a single token. Precision issues are super interesting!

Hacking My "Smart" ToothBrush- 1186

Cyrill Kunzi    Reference →Posted 2 Years Ago
  • The author of this post had recently bought a Phillips Sonicare toothbrush. When reviewing the documentation, it says that the product operates at 13.56MHz, which indicates this uses NFC. The communicate happens from the toothbrush handle to the toothbrush head to tell you that a new one is needed.
  • Using the NFC tools app, there is a lot we can learn about the tag. Some interesting notes from this:
    • The tag is NTAG213 and uses NfcA.
    • The device is password protected.
    • Address 0x24 contains the total brush time.
    • Various other fields such as checksums, IDs, links and more.
  • The goal of the attack is to overwrite the brushed time of the device. Reversing engineering the format is easy: observe the value, brush your teeth and observe again. However, it's password protected. So, what do we do?
  • Software Defined Radio (SDR) time! The author pulled out a HackRF (which goes down to 1MHz for listening). Then, they used Gnu Radio to take the raw IQ signals and convert the data into a WAV file. The WAV file is composed of a seris of complex numbers, which is why the author pulled out the real and imaginary portions to convert it to a WAV.
  • The tool NFC-laboratory takes in a WAV file and decoding the bytes manually for us. By cross-referencing the data being sent and the stored data, we can learn what's being sent from the WAV file.
    • Lines 0-6: Establish communication between the systems.
    • Lines 7: Send password - 0x1B command.
    • Lines 9: The counter is updated to a new value - 0xA2 command.
  • Reading line 7 shows that the password is 67:B3:8B:98! Woah, that's awesome. It's super crazy to me that the data is sent in plaintext over NFC; I figured some type of encryption would be done by default.
  • Using NFC tools, we can set the password of the field then attempt to edit the field like we tried before. Since the password is set, it works! We've got a NEW toothbrush as far as the device thinks.
  • Through the post, the password changes. What gives? Attempting the password incorrectly 3 times will permanently disable write access. Additionally, the passwords are unique per toothbrush. NXP recommends that the password should be generated from the UID but the author couldn't find the transformation function.
  • Overall, an amazing post! I learned all about WAV files and NFC hacking.

CSP Bypass Unveiled: The Hidden Threat of Bookmarklets- 1185

socradar    Reference →Posted 2 Years Ago
  • A bookmarklet is a primitive version of a browser extension. It's a simple version of a browser extension that can contain JavaScript code. Users can add bookmarklets by creating a bookmark, pasting the bookmarklets code as a URL or dragging/dropping the link onto the toolbar.
  • When the CSP came out in 2014, the idea was that the bookmarklets were going to die. However, this never happened, with references in the RFC for CSPs ignoring addons. Regardless of implementing a CSP or not, the bookmarklet can bypass this protections to interact with websites.
  • These bookmarklets were used for phishing attacks. In particular, Discords in the cryptocurrency space were being hit by these attacks. These attacks worked by asking users to drag a button to their bookmarks bar. Once this happened, the JavaScript within the bookmarklet code can be accepted.
  • With the admins knowledge within the account, the bookmarklet discreetly retrieved their Discord token when it was dragged, subsequently transmitting it to the attacker’s website. This led to posts on Discord channels with more malicious links and bookmarks.
  • Overall, an interesting attack vector to consider; I had no idea that bookmarklets could execute code like this. I'm sure we'll see some response from the browsers soon.

CosmWasm allocate stack overflow- 1184

claudijd    Reference →Posted 2 Years Ago
  • CosmWasm is a smart contract platform that can be used on Cosmos. This allows for a similar interaction of Solidity based smart contracts on the EVM.
  • Being able to find a denial of service (DoS) within a smart contract platform would be catastrophic. It could be used to stop the chain altogether for each node that was running. To me, it's weird that the virtual machine running the code wouldn't handle the error, toss out the error and move onto the next transaction though.
  • CosmWasm has a several runtime imports. This functions exist to offload expensive operations (like cryptography), perform validations and write state changes. All of these functions use a helper method called write_to_contract() to write error messages to the WASM address space.
  • To do this, write_to_contract() calls allocate. This function allocates a large block of memory in the address space. Normally, this is a standard library from CosmWasm but can be overwritten by a developer.
  • A classic problem that developers run into is recursively calling functions; this creates a stack to deep, otherwise known as a stack overflow. By adding a call to addr_validate() within our custom allocate() function, an infinite recursion call can be created.
  • This is a really simple bug that has horrible consequences. I bet there are many other issues in the layer 1 eco-system on newer blockchains. Just got to go look!