Unveiling the Scroll Token Attack: A Case Study in Solidity Overflow
On May 29, 2024, the Scroll Token plunged in a dramatic price drop of 99.99% after a successful attack. The exploit involved the attacker transferring an enormous amount of tokens, seemingly originating from an address with zero balance. While Solidity versions at or above 0.8.0 typically include overflow/underflow checks during compilation, the Scroll Token contract, despite being compiled with version 0.8.19, appears to have contained a bug that bypassed this safeguard. This blog post will delve into this apparent vulnerability to understand how the attack unfolded.
Overview
Attacker:
https://etherscan.io/address/0x55f5aac4466eb9b7bbeee8c05b365e5b18b5afcc
Vulnerable Contract:
https://etherscan.io/address/0xbc452fdc8f851d7c5b72e1fe74dfb63bb793d511
Transaction attack:
https://etherscan.io/tx/0x661505c39efe1174da44e0548158db95e8e71ce867d5b7190b9eabc9f314fe91
Exploit Analysis
Following the attack transaction, the attack contract uses the execute
call to trigger an arbitrary call in the router. This call then triggers the Uniswap Universal Router
to transfer 1 wei
token to the admin address.
It's a strange action because the router doesn't typically hold tokens. In the normal flow of a decentralized exchange (DEX), the router needs approval to transferFrom
tokens from users. However, this call transfers 1 wei, similar to an old vulnerability—an underflow from a zero balance address.
A deep dive into the Scroll Token contract's logic reveals a few interesting points:
Unverified Contract: The contract is unverified, meaning its source code isn't publicly available. To analyze its logic, we need to use decompilation tools.
Solidity Version Inconsistency: The decompiled code suggests that the contract was compiled with Solidity version 0.8.19. However, this version typically includes automatic overflow/underflow checks during the compilation process to prevent arithmetic errors that could lead to vulnerabilities.
In a deeper analysis of the Scroll Token contract's transfer
function logic, a critical vulnerability has been identified. While the recipient's balance does undergo an overflow check, the caller's balance does not. This discrepancy creates a weak point that has been exploited in the attack.
The absence of an overflow check in the Scroll Token contract raises an intriguing question: How did this crucial safeguard get skipped? Was it a compiler hiccup or a developer's misstep?
Our investigation points towards a developer mistake. Scroll Token, being a custom token, likely inherited its source code from existing public code. It seems that this public code employed an unchecked
block during development to intentionally bypass the overflow check. This maneuver was likely a deliberate optimization choice, as demonstrated in similar code from OpenZeppelin's library (shown below).
The developers likely intended to optimize gas usage by moving the subtraction statement to an unchecked
block, as the balance of the "from" address had already been verified.
However, in doing so, they mistakenly removed the critical if-statement that checked for overflow, creating a vulnerability that was exploited in the attack.
Lesson learned
Inherit and Modify with Caution: While inheriting and modifying existing code can be an efficient way to develop smart contracts, it's crucial to exercise extreme caution. Thoroughly review and understand every line of code you're incorporating, as even seemingly minor changes can introduce unexpected vulnerabilities. Don't blindly trust external code; scrutinize it as if it were your own.
Unverified Code is Not Impenetrable: The belief that unverified source code offers robust protection is a misconception. Determined individuals can often decompile and analyze the underlying logic, potentially exposing vulnerabilities. Always assume that your contract's inner workings could be revealed, and take proactive measures to safeguard against potential exploits.