Presentation Material
Abstract
The presentation will cover some very cool attack vectors, which often go unnoticed by smart contract auditors while auditing projects and are not that popular to be taken care of at the time of writing code, and may lead to some serious exploits and loss of millions of dollars of funds. It will also expand upon some bad-solidity coding patterns and how to write safe code. Also, we’ll be expanding upon exploits over some patterns that are actually introduced to protect against some attacks. In the end, we will go into the defensive mode and see, what are the possible strategies and coding patterns to protect against these attacks.
AI Generated Summary
This talk addresses several under-discussed attack vectors in Ethereum smart contract security, focusing on low-level EVM behaviors and compiler nuances.
The first vector involves “dirty higher-order bits” in calldata. For parameters smaller than 32 bytes, older Solidity compiler versions (pre-0.8) extracted the value without validating that higher-order bits were zero. An attacker could supply arbitrary non-zero bits in these positions. If a contract used inline assembly to store this raw calldata directly into storage, the extra bits would be preserved, allowing manipulation of values like prices beyond their intended range (e.g., exceeding a u8’s 0-255 limit).
“Gas grieving” attacks exploit the gas stipend mechanism for sub-calls (EIP-150). An attacker can craft a transaction where a critical sub-call receives just enough gas to execute partially but fail silently, while the parent transaction succeeds. This can bypass essential logic, such as token transfers in a trade, giving the attacker assets for free. A variant uses a malicious sub-call to exhaust all available gas via operations like RETURNDATACOPY, causing a denial-of-service that prevents the main transaction from completing, such as permanently blocking a “king of the hill” contract from being dethroned.
The “uncast values” vector abuses view functions called multiple times within a single transaction. Since GASLEFT() decreases with each operation, a view function can return different values on successive calls without state changes. An attacker can bypass conditional checks (e.g., an oracle price > minimum) by ensuring the first call returns a valid value and the second, made after gas has decreased, returns an invalid one like zero.
“Metamorphic contracts” use SELFDESTRUCT to destroy and redeploy new logic on the exact same address. By pre-calculating the deployment address via CREATE2 and storing the new implementation’s bytecode, an attacker can upgrade contract functionality without changing the address, evading detection by systems monitoring for proxy implementation changes.
Finally, a bad pattern involves static calls that fail but where the contract proceeds using stale data from memory. If a low-level call reverts, any intended write to memory (e.g., from return data) does not occur, leaving previous values intact. If subsequent logic incorrectly uses this old data, it can lead to incorrect state interpretations, such as using a stale price.
Mitigations include avoiding raw assembly for calldata processing, fully validating calldata lengths and values, caching results from view functions, always checking the success flag of low-level calls and reverting on failure, and understanding that contract addresses can change via self-destruct even without proxies.