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!
totalSupply is modified by a call to burn() but there is an external call prior to updating the reserves. 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.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. strlen()
vpxor xmm0,xmm0,xmm0 ... vpcmpeqb ymm1,ymm0,YMMWORD PTR [rdi] vpmovmskb eax,ymm1 ... tzcnt eax,eax vzeroupper ret
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!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.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.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:
cvtsi2sd instruction. vmovdqa instruction.vzeroupper branch prediction. This is a standard thing to force conditional branches to mispredict for speculative execution bugs.sfence in order to ensure they had full control of what was being executed._airdrop(). The code takes in three parameters:
from, to and amount. balance call with the amount provided by amount in the function.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.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. 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.dlopen/dlclose primitive. Is this even exploitable? To determine this, they reviewed various default libraries that could be loaded.SIGSEVG signal handler and do not deregister that handler if closed. This turns into a use-after-free-like situation. 9 were found. 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.... 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.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.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.block.difficulty with block.prevrandao. Although, the opcode is still the same, the values have different meanings now.prevrandao is meant to be a source of randomness on chain that is created using decentralized information. This is generated with the following steps for each validator:
prevrandao from the future. According to the EIP-4399 specification, this should be 4 epochs into the future. The reason for this is that we can limit the influence of an attacker by forcing them to guess earlier on. prevrandao opcode returns a favorable value. To fix this problem, enforce that the transaction happens on a particular block in the future. buy(uint256 _amount) amount variable is for the AMOUNT of NFTs you want to buy. If a user buys 0 NFTs, then the currentId is not set properly. The check from before about the auction being over doesn't work since the currentId is not set from the iteration within a loop. It's obviously lower than the final ID, since it's set to 0.feeReceiver and saleReceiver can be hit over and over again because of the if statement failing.validateWddxFilter() had been added. This did verification on the type attribute of the object to ensure it starts with coldfusion.getClassbySignature() that gets an instance of an arbitrary class. Then, it calls a function that must start with set. Being able to call arbitrary calls with a semi-restricted function is a good primitive to start from! java.util.Date.setDate(). After verifying that this worked in a debugger, they were set to look for more primitives. With the class com.sun.rowset.JdbcRowSetImpl, setDataSourceName() sets a JNDI lookup name. Then, by calling setAutoCommit(), we can create a JNDI injection vulnerability, like with log4shell.commons-beanutils to get code execution. Pretty neat bug and unique primitive.ETH-unshETH pool - over the 3 updates. By sandwiching the update process, the attacker was able to get all of their money back but was still able to manipulate the price.