Author: Jiu Jiu & Lisa
Editor: Sherry
Background
On March 30, 2025, according to the SlowMist MistEye security monitoring system, the leveraged trading project SIR.trading (@leveragesir) on the Ethereum chain was attacked, resulting in a loss of assets valued at over $300,000. The SlowMist security team conducted an analysis of the incident and shares the results as follows:
(https://x.com/SlowMist_Team/status/1906245980770746449)
Related Information
Attacker Address:
https://etherscan.io/address/0x27defcfa6498f957918f407ed8a58eba2884768c
Vulnerable Contract Address:
https://etherscan.io/address/0xb91ae2c8365fd45030aba84a4666c4db074e53e7#code
Attack Transaction:
https://etherscan.io/tx/0xa05f047ddfdad9126624c4496b5d4a59f961ee7c091e7b4e38cee86f1335736f
Prerequisite Knowledge
The Solidity version 0.8.24 (released in January 2024) introduced the transient storage feature based on EIP-1153. This is a new data storage location designed to provide developers with a low-cost, temporary storage method that is valid during the transaction.
Transient storage is a new data location that exists alongside storage, memory, and calldata. Its core feature is that data is only valid during the execution of the current transaction and is automatically cleared after the transaction ends. Accessing and modifying transient storage is implemented through two new EVM instructions:
- TSTORE(key, value): Stores a 256-bit value
value
in the memory corresponding to the specified keykey
in transient storage. - TLOAD(key): Reads a 256-bit value from the memory corresponding to the specified key
key
in transient storage.
The main features of this feature are as follows:
- Low gas cost: The gas cost for TSTORE and TLOAD is fixed at 100, equivalent to warm storage access. In contrast, regular storage operations (SSTORE) can cost up to 20,000 gas for the first write (from 0 to non-0) and at least 5,000 gas for updates.
- Transaction-level persistence: Data in transient storage remains valid throughout the transaction, including all function calls and sub-calls, making it suitable for scenarios that require sharing temporary state across calls.
- Automatic clearing: After the transaction ends, transient storage is automatically reset to zero, eliminating the need for manual cleanup and reducing maintenance costs for developers.
Root Cause
The root cause of this hacking incident is that the value stored in transient storage using TSTORE in a function was not cleared after the function call ended, allowing the attacker to exploit this feature to construct specific malicious addresses to bypass permission checks and withdraw tokens.
Attack Steps
- The attacker first created two malicious tokens A and B, then created pools for these two tokens on UniswapV3 and injected liquidity, with token A being the attack contract.
- Next, the attacker called the initialize function of the Vault contract, using token A as the collateral token and token B as the debt token to create a leveraged trading market APE-21.
- The attacker then called the mint function of the Vault contract, depositing the debt token B to mint leveraged token APE.
In the mint function, we find that when depositing the debt token B to mint the leveraged token, the value of the collateralToDepositMin parameter cannot be equal to 0. It will then exchange token B for the collateral token A through UniswapV3 and transfer it to the Vault, during which the address of the UniswapV3 pool created by the attacker is stored in transient storage for the first time.
When the UniswapV3 pool performs the exchange operation, it will call back the uniswapV3SwapCallback function of the Vault contract. It can be seen that this function first uses TLOAD to retrieve the value from the memory corresponding to the previously stored key 1 to verify whether the caller is the UniswapV3 pool. It then transfers the debt token B from the minter's address and mints the leveraged token APE, finally storing the minted amount in transient storage for the second time in the memory corresponding to key 1, to be used as the return value of the mint function. The amount to be minted is calculated and controlled by the attacker in advance, with a value of 95759995883742311247042417521410689.
- The attacker then calls the safeCreate2 function of the Keyless CREATE2 Factory contract to create a malicious contract, with the contract address 0x00000000001271551295307acc16ba1e7e0d4281, which is the same as the value stored in transient storage for the second time.
- The attacker then uses this malicious contract to directly call the uniswapV3SwapCallback function of the Vault contract to withdraw tokens.
Since the uniswapV3SwapCallback function uses TLOAD(1) to verify whether the caller is the UniswapV3 pool, and in the previous mint operation, the value in memory corresponding to key 1 was saved as the minted amount 95759995883742311247042417521410689, and this value in memory was not cleared after the mint function call, the address of the uniswapPool is retrieved as 0x00000000001271551295307acc16ba1e7e0d4281, causing the identity check of the caller to be incorrectly passed.
Moreover, the attacker has calculated the amount of tokens to be withdrawn in advance, constructing the final minted amount to a specified value: 1337821702718000008706643092967756684847623606640. Similarly, at the end of this call to the uniswapV3SwapCallback function, a third transient storage operation will occur, saving this value in the memory corresponding to key 1. This value needs to match the address of the attack contract (token A) with the value 0xea55fffae1937e47eba2d854ab7bd29a9cc29170 to allow the subsequent caller check to pass.
- Finally, the attacker can directly use the attack contract (token A) to call the uniswapV3SwapCallback function of the Vault contract, withdrawing other tokens (WBTC, WETH) from the Vault contract for profit.
MistTrack Analysis
According to the analysis by the on-chain anti-money laundering and tracking tool MistTrack, the attacker (0x27defcfa6498f957918f407ed8a58eba2884768c) stole approximately $300,000 in assets, including 17,814.8626 USDC, 1.4085 WBTC, and 119.871 WETH.
Among them, WBTC was exchanged for 63.5596 WETH, and USDC was exchanged for 9.7122 WETH:
Subsequently, a total of 193.1428 WETH was transferred to Railgun:
In addition, the attacker's initial funding came from 0.3 ETH transferred into Railgun:
Summary
The core of this attack lies in the fact that the attacker exploited the characteristic of transient storage in the project, which does not immediately clear the saved values after a function call but retains them throughout the entire transaction. This allowed the attacker to bypass the permission verification of the callback function to profit. The SlowMist security team recommends that project teams should immediately use TSTORE(key, 0) to clear the values in transient storage after the function call ends, based on the corresponding business logic. Additionally, the contract code of the project should undergo enhanced auditing and security testing to prevent similar incidents from occurring.
免责声明:本文章仅代表作者个人观点,不代表本平台的立场和观点。本文章仅供信息分享,不构成对任何人的任何投资建议。用户与作者之间的任何争议,与本平台无关。如网页中刊载的文章或图片涉及侵权,请提供相关的权利证明和身份证明发送邮件到support@aicoin.com,本平台相关工作人员将会进行核查。