Deadly Remaining: A $300,000-on-chain robbery caused by transient storage

Reprinted from panewslab
04/01/2025·1MAuthor: Jiujiu & Lisa
Editor: Sherry
background
On March 30, 2025, according to the monitoring of the Slow MistEye Security Monitoring System, leveraged trading project SIR.trading (@leveragesir) on the Ethereum chain was attacked and assets worth more than $300,000 were lost. The Slow Fog Security Team analyzed the incident and shared the results as follows:
(https://x.com/SlowMist_Team/status/1906245980770746449)
Related information
Attacker 's address:
https://etherscan.io/address/0x27defcfa6498f957918f407ed8a58eba2884768c
The contract address with the vulnerability:
https://etherscan.io/address/0xb91ae2c8365fd45030aba84a4666c4db074e53e7#code
Attack transactions:
https://etherscan.io/tx/0xa05f047ddfdad9126624c4496b5d4a59f961ee7c091e7b4e38cee86f1335736f
Pre-knowledge
Solidity version 0.8.24 (released in January 2024) introduces transient storage features based on EIP-1153. This is a new data storage location designed to provide developers with a low-cost, effective temporary storage method during transactions.
Transient storage is a new data location that is parallel to storage, memory, and call data. Its core feature is that the data is only valid during the current transaction execution period and will be automatically cleared after the transaction is completed. Accessing and modifying transient storage is implemented through two new EVM instructions:
- TSTORE(key, value): Stores the 256-bit value in the memory corresponding to the specified key key in the transient storage.
- TLOAD(key): Reads a 256-bit value from the memory corresponding to the specified key stored in transiently.
This feature has the following main features:
- Low gas cost: The gas cost of TSTORE and TLOAD is fixed at 100, which is equivalent to warm storage access. By contrast, regular storage operations (SSTORE) can be as high as 20,000 gas for the first write (from 0 to non-0) and at least 5,000 gas for updates.
- In-transaction persistence: Transiently stored data remains valid throughout the transaction, including all function calls and sub-calls, suitable for scenarios where temporary states are required to be shared across calls.
- Automatic clearance: After the transaction is over, the transient storage is automatically reset to zero, without manual cleaning, reducing developer maintenance costs.
root cause
The fundamental reason for this hacked event is that the value of the transient storage called store in the function is not cleared after the function call ends, which leads to the attacker using this feature to construct a specific malicious address to bypass permission checks and transfers tokens.
Attack steps
1. The attacker first creates two malicious tokens A and B, and then creates a pool for these two tokens on UniswapV3 and injects liquidity, where the A token is an attack contract.
2. The attacker then calls the initialize function of the Vault contract, using token A as collateral token and token B as a leveraged trading market APE-21 for the debt token.
3. Following the attacker calls the mint function of the Vault contract and deposits the debt token B token minted leveraged token APE.
Following up to the mint function, we found that when we need to deposit debt tokens B to mint leveraged tokens, the value of the collateralToDepositMin parameter that needs to be passed cannot be equal to 0. Then, the B token will be exchanged into collateral token A through UniswapV3 and transferred to Vault, which will be stored in the first transient way of the address of the UniswapV3 pool created by the attacker.
When the UniswapV3 pool performs a redemption operation, the uniswapV3SwapCallback function of the Vault contract will be called back. It can be seen that this function will first use tload to retrieve the value from the memory corresponding to the specified key 1 stored in the previous transient to verify whether the caller is a UniswapV3 pool. Then, the debt token B is transferred from the minter address and minted the leverage token APE. Finally, the amount of the minted quantity is stored for the second transient time, and saved in the memory corresponding to the specified key 1, and used as the return value of the mint function. The number of castings required here is calculated and controlled by the attacker in advance, and its value is 95759995883742311247042417521410689.
4. The attacker then calls the safeCreate2 function of the Keyless CREATE2 Factory contract to create a malicious contract with the contract address 0x00000000001271551295307acc16ba1e7e0d4281, the same value as the second transient stored.
5. Then the attacker uses the malicious contract to directly call the uniswapV3SwapCallback function of the Vault contract to transfer the token.
Because the uniswapV3SwapCallback function uses tload(1) to verify whether the caller is a UniswapV3 pool, however, in the previous casting operation, the value in the corresponding memory corresponding to the specified key 1 is saved as the number of castings 95759995883742311247042417521410689, and the value in this memory is not cleared after the mint function call, so the address of the uniswapPool is retrieved at this moment as 0x0000000000001271551295307acc16ba1e7e0d4281, causing the identity check of the caller to be passed incorrectly.
And the attacker calculated the number of tokens that need to be transferred in advance, and constructed the final minted quantity amount into the specified value: 1337821702718000008706643092967756684847623606640. Similarly, at the end of this call to the uniswapV3SwapCallback function, a third transient storage will be performed, and the value will be saved to the corresponding memory of the specified key 1. This requires that the value be the same as the address of the attack contract (A token) value 0xea55fffae1937e47eba2d854ab7bd29a9cc29170 to allow the subsequent check of the caller to pass.
6. Finally, the attacker can directly call the uniswapV3SwapCallback function of the Vault contract through the attack contract (A token) to transfer other tokens (WBTC, WETH) in the Vault contract to make profits.
MistTrack Analysis
According to an analysis by MistTrack, an on-chain anti-money laundering and tracking tool, the attacker (0x27defcfa6498f957918f407ed8a58eba2884768c) stole approximately $300,000 in assets, including 17,814.8626 USDC, 1.4085 WBTC and 119.871 WETH.
Among them, WBTC is redeemed to 63.5596 WETH and USDC is redeemed to 9.7122 WETH:
Then, a total of 193.1428 WETH was transferred to Railgun:
In addition, the attacker's initial funding comes from 0.3 ETH transferred by Railgun:
Summarize
The core of this attack is that the attacker uses the transient storage in the project to not immediately clear the saved value after the function call, but will instead save the feature that will be saved throughout the transaction period, thereby bypassing the permission verification of the callback function to make profits. The Slow Fog Security Team recommends that the project party should use store(key, 0) to clear the value in the transient storage immediately after the function call is completed according to the corresponding business logic. In addition, audit and security testing of the project's contract code should be strengthened to avoid similar situations.