Skip to content

Commit ddf9db4

Browse files
committed
Added writeup for Transient Heist Revenge by Vrishab
1 parent fd21bba commit ddf9db4

File tree

1 file changed

+53
-0
lines changed

1 file changed

+53
-0
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
+++
2+
title = "Transient Heist Revenge"
3+
date = 2025-07-15
4+
authors = ["Vrishab"]
5+
+++
6+
7+
## Overview
8+
9+
The main goal of the challenge is to trick the function `isSolved` from Setup.sol into completing. There are 2 main entities here:
10+
11+
- **Bank Vault** (USDSEngine): This is a very secure vault that stores collateral and allows minting of USDS stablecoin. It uses transient storage (tstore/tload) to verify only the correct Bi0sSwapPair can call certain functions.
12+
- **Currency Exchange** (Bi0sSwapPair): This booth swaps one type of token for another.
13+
14+
We need to trick the Vault into thinking we’ve deposited a huge amount as collateral - it has to be more than a very large hash value.
15+
16+
## How Collateral Deposits Work
17+
18+
Collateral Deposits normally work like this:
19+
20+
1. A user calls `depositCollateralThroughSwap` to deposit collateral via an automated token swap.
21+
2. The vault transfers tokens to the Bi0sSwapPair, writes the swap pair’s address into transient storage (`tstore(1, bi0sSwapPair)`).
22+
3. The swap is performed, and upon completion, the Bi0sSwapPair calls back into `bi0sSwapv1Call` to deposit the collateral.
23+
4. In `bi0sSwapv1Call`, the vault checks that `msg.sender` matches the stored address from transient storage, ensures the requested collateral deposit is not greater than the amount of tokens received (`collateralDepositAmount <= amountOut`), and updates internal collateral balances.
24+
25+
## The Vulnerability
26+
27+
Now, the only check on who can call `bi0sSwapv1Call` is the transient storage slot. This means that if an attacker can use the value written into transient storage, they can then call `bi0sSwapv1Call` from their own contract address. The vault only checks `msg.sender == tload(1)`, but `tload(1)` gets overwritten during the callback with `tokensSentToUserVault`, allowing us to control this value.
28+
29+
## Exploit Steps
30+
31+
**1. Deploying a malicious contract at a controlled address** - we use CREATE2 to deploy an attacker contract at an address that can be precomputed. This contract must implement the `bi0sSwapv1Call` function. A contract is used instead of a normal wallet since only contracts can call `bi0sSwapv1Call` and pass the transient storage check while being deployed at a known address. We should ensure we get a contract address with sufficient leading zeros for the arithmetic manipulation.
32+
33+
**2. Initiating a Legitimate Swap** - we now call `depositCollateralThroughSwap` with 80,000 WETH which we want to swap for SafeMoon. This triggers `tstore(1, bi0sSwapPair)` - usage of transient storage which survives for the entire transaction, not just the swap call.
34+
35+
**3. Overwrite Transient Storage During First Callback**: During the legitimate `bi0sSwapv1Call` callback, we set `collateralDepositAmount = amountOut - vanity_contract_address`, making `tokensSentToUserVault = vanity_contract_address`. The line `tstore(1, tokensSentToUserVault)` then overwrites the original swap pair address with our contract address.
36+
37+
**4. Second Call to bi0sSwapv1Call**: Within the same transaction, we call `bi0sSwapv1Call` directly from our vanity contract. The check `msg.sender == tload(1)` now passes because `tload(1)` contains our contract address from step 3.
38+
39+
**5. Set Arbitrary Collateral Amounts**: In the second call, we can supply ANY `amountOut` and `collateralDepositAmount > FLAG_HASH` (ensuring `collateralDepositAmount <= amountOut`). These don't need to be realistic token amounts - just arbitrary large numbers.
40+
41+
**6. Repeat for Second Token**: The `isSolved()` function requires BOTH `collateralTokens[0]` (WETH) AND `collateralTokens[1]` (SafeMoon) to exceed `FLAG_HASH`. Since transient storage persists for the entire transaction, you can make a second direct call to `bi0sSwapv1Call` with the other token type using the same poisoned transient storage.
42+
43+
## Arithmetic Manipulation Used
44+
45+
The exploit requires careful calculation: `tokensSentToUserVault = amountOut - collateralDepositAmount` must equal the vanity contract address. We need a vanity address with 7+ leading zeros to make this arithmetic feasible compared to `FLAG_HASH`.
46+
47+
## Why the Vanity Address Matters
48+
49+
- We need a contract deployed at a **small numeric address** (7 leading zeros) to make the arithmetic work.
50+
- The calculation `tokensSentToUserVault = amountOut - collateralDepositAmount` must equal our contract address.
51+
- We need an address comparatively smaller than `FLAG_HASH` so that in the first callback, `amountOut = vanityAddress + smallCollateralAmount` is achievable through legitimate token swaps, while in the second call you can use `collateralDepositAmount > FLAG_HASH`.
52+
- This address then gets written to transient storage, allowing our contract to **pass the `msg.sender` check** on the second call.
53+

0 commit comments

Comments
 (0)