image source head

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

trendx logo

Reprinted from panewslab

04/01/2025·1M

Author: 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:

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

 (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.

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

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.

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

3. Following the attacker calls the mint function of the Vault contract and deposits the debt token B token minted leveraged token APE.

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

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.

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

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.

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

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

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.

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

5. Then the attacker uses the malicious contract to directly call the uniswapV3SwapCallback function of the Vault contract to transfer the token.

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

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.

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

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.

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

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.

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

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.

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

Among them, WBTC is redeemed to 63.5596 WETH and USDC is redeemed to 9.7122 WETH:

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

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

Then, a total of 193.1428 WETH was transferred to Railgun:

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

In addition, the attacker's initial funding comes from 0.3 ETH transferred by Railgun:

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

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.

more