Recently, there’s been a surge in sophisticated phishing attacks targeting Solana users, bypassing even the most secure wallet protections. While users authorize transactions with a simple click, they may unknowingly approve malicious activities.
These attacks exploit the hidden complexities of wallet interactions, putting funds at risk. We’ll reveal how these attacks work, share real-life cases, and provide tips to help you stay safe.
The Hidden Dangers of Wallet Interactions
Imagine a scenario where your wallet shows you’re about to make a swap on Jupiter, but in reality, you’re unknowingly authorizing a malicious transaction that could drain your entire account. This isn’t just a hypothetical situation – it’s a growing threat in the Solana ecosystem.
Every day, countless users interact with their Solana wallets, authorizing transactions with a simple click. But what really happens behind the scenes once you hit that “Sign” button? The truth is, there’s much more going on than meets the eye.
In recent weeks, the community has witnessed a disturbing trend: sophisticated phishing attacks targeting Solana users that bypass even the most robust wallet security measures. As we delve deeper, we'll uncover the “black magic” behind these attacks, explore real-world cases, and arm you with the knowledge to protect yourself.
Drainer Alert: A Series of Sophisticated Attacks
The alarm bells started ringing a few weeks ago when the Jupiter Team forwarded reports of suspicious hacking incidents from multiple victims. With no clear indication of the root cause and new victims coming every few hours, we found ourselves in a race against time to uncover the grifter's hiding place before more users got drained.
A pattern quickly emerged among the victims: they were Solana users using the Phantom wallet via a Chrome extension on Windows. These attacks weren’t limited to a specific DeFi protocol but were identified on various platforms, including Jupiter products and Raydium.
From these incidents, we could draw a few important conclusions:
The attack method was not widespread, as the number of victims was not explosively increasing.
These attacks were not targeting specific users, because the victims appeared to be random and not exclusively “whales”.
The vulnerability didn’t seem to be tied to a specific DeFi product but appeared to be related to the Phantom browser extension wallet.
The attacker had no direct control of the victim’s wallet or private keys because the attacks were only triggered by user interactions.
As we delve deeper into this mystery, we invite you, our fellow sleuths, to join us in tracing the root causes. What theories or ideas come to your mind, Sheriff Cat?
Solana Transactions Basics
Unpacking a Solana Transaction
To comprehend the sophistication of these attacks, we first need to understand the structure and workflow of a typical Solana transaction. A Solana transaction is a compact but powerful data structure that encapsulates all the information needed to execute a set of actions on the blockchain. Let’s break down its components:
Signatures: A list of signatures, one for each signer required by the transaction. These signatures cryptographically prove that the signers authorize the transaction.
Message: The core content of the transaction, containing:
Header: Includes metadata such as the number of required signatures and the number of read-only account references.
Account Addresses: A list of all accounts that will be read from or written to during transaction execution.
Recent Blockhash: A recent blockhash to prevent transaction replay and ensure freshness.
Instructions: The actual commands to be executed. Each instruction within the transaction message contains the following:
Program ID: The address of the program that will process this instruction.
Accounts: A list of accounts involved in this specific instruction.
Instruction Data: Arbitrary byte array interpreted by the program.
The Lifecycle of a Token Swap
Let’s take a common scenario like token swapping on Jupiter as an example. When you initiate a swap on Jupiter, several steps occur behind the scenes. First, Jupiter fetches the current token price for your reference. It then queries its backend to determine the most efficient routing for your swap. With this information, the front end assembles the routing details into a series of instructions that will form the transaction.
Once the instructions are assembled, an unsigned transaction is created and submitted to your connected wallet. At this point, your wallet plays a crucial role in security. It typically queries a backend service for simulation results (more details covered in the next section), allowing it to predict the outcome of the transaction. Based on these results, the wallet prompts you, the user, for confirmation. If you approve, the wallet signs the transaction and submits it to an RPC client, which then broadcasts it to the Solana network for confirmation.
“Unlimited Approval” as a Signer
On Solana, signing a transaction grants full authority over your account for that transaction’s scope. This is similar to “unlimited approval” in Ethereum, but with a key difference: it happens automatically with every signature.
When you sign, you authorize the transaction to act on your behalf for all included accounts. This means a single signature can permit multiple actions, including unexpected token transfers, without additional prompts. In the context of a malicious attack, this could allow a bad actor to include harmful instructions that appear legitimate to the user!
Case Study – Anatomy of a Malicious Transaction
To illustrate this, let's examine a malicious transaction we encountered during our investigation. Transaction 5krgaq2FTZAp9CT1X1ue4dY7JV2rSVYeNLQFgHjWKFcyTE88wU54KWKhvuzcG4Fm6bfdByk1ngiyewcjLydWc93d appeared to be a standard token swap on the surface.
However, upon closer inspection, we discovered additional instructions embedded within the transaction.
These malicious instructions were made to steal funds and take control of the victim’s token account while pretending to be part of the normal swap process.
Default Safeguard: Transaction Simulation
Modern wallets like Phantom usually utilize embedded analyzers to provide users with comprehensive information before signing. Transaction simulation is a critical feature that allows the wallet to predict transaction outcomes before execution.
Phantom Wallet employs this measure in partnership with Blowfish, a specialized analysis provider, as stated in their blog:
Secure transaction scanning
We scan transactions and messages with Blowfish. If we find anything fishy, we warn you via a pop-up alert so you can take a closer look, and if necessary, avoid the transaction altogether. If we can’t simulate it, we’ll return you to the normal confirmation flow. If something is wrong, we break out of Auto-Confirm and alert you.
When a user initiates a transaction in Phantom, the unsigned transaction is sent to Blowfish’s servers for simulation in a controlled environment. This process can identify various threats, including unexpected token transfers, interactions with suspicious smart contracts, potential rug pulls, and abnormal slippage in DEX transactions.
However, simulation has limitations. As we’ll see in our case study, sophisticated attacks can still bypass these checks through clever programming and exploitation of simulation constraints.
Case Study – Examining a Simulation Log
We were quite confused as to why the built-in transaction simulation did not protect the victims. Luckily, a sample of a victim’s simulation provided by Blowfish confirmed our guess: the simulation failed to reveal the malicious behaviors.
In the log, the malicious program did not execute long or consume many compute units:
“Program 5UMucMksJweA1AtgyxrK8DJeBXr3DQGEGRs5Kkq2pZjr invoke [1]”,
“Program 5UMucMksJweA1AtgyxrK8DJeBXr3DQGEGRs5Kkq2pZjr consumed 1106 of 102657 compute units”,
“Program 5UMucMksJweA1AtgyxrK8DJeBXr3DQGEGRs5Kkq2pZjr success”
However, the on-chain log shows different traces:
Program 5UMucMksJweA1AtgyxrK8DJeBXr3DQGEGRs5Kkq2pZjr invoke [1]
Program 11111111111111111111111111111111 invoke [2]
Program 11111111111111111111111111111111 success
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]
Program log: Instruction: SetAuthority
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 2875 of 97266 compute units
Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success
Program 5UMucMksJweA1AtgyxrK8DJeBXr3DQGEGRs5Kkq2pZjr consumed 8480 of 102668 compute units
Program 5UMucMksJweA1AtgyxrK8DJeBXr3DQGEGRs5Kkq2pZjr success
So, what happened in this program 5UMucMksJweA1AtgyxrK8DJeBXr3DQGEGRs5Kkq2pZjr?
Unmasking the Malware: Reverse Engineering
Executable Program Accounts on Solana
On Solana, smart contracts are deployed as executable program accounts. These accounts store the compiled bytecode of the program, which can be executed by the Solana runtime. Unlike Ethereum’s EVM bytecode, Solana programs are compiled to BPF bytecode, which is a nightmare for reverse engineering.
Our target program is 5UMucMksJweA1AtgyxrK8DJeBXr3DQGEGRs5Kkq2pZjr. The program content could be dumped via Solana’s built-in command:
$ solana program dump 5UMucMksJweA1AtgyxrK8DJeBXr3DQGEGRs5Kkq2pZjr a.elf -u https://api.mainnet-beta.solana.com
Wrote program to a.elf
$ file a.elf
a.elf: ELF 64-bit LSB shared object, eBPF, version 1 (SYSV), dynamically linked, stripped
Now we have an ELF file containing BPF bytecode.
Reverse Engineering like an OtterSec Pro
Reverse engineering a program in a nontraditional instruction set is never a trivial task. Luckily, we could stand on the shoulders of pioneers: OtterSec has open-sourced their amazing eBPF decompiler plugin for reverse engineering!
The decompiled pseudo c code is like this:
As we mentioned earlier, the program in simulation consumed much fewer computing units. We suspected an early exit was triggered somewhere and we found these suspicious constant comparisons and “goto label_100000718” jumps, as shown above.
A deeper analysis reveals that variable r7
here points to the array of input accounts, with each size being 0x30 bytes. Thus r7 + 0x30
is account #2, r7 + 0x60
is account #3. Dereferencing the first pointer in an account is reading its key
.
The four magic numbers 0x4d702b828d0f0a6e
, 0x2a8e185ab7b764da
, 0x7752c6d53f4d424b
, and 0xef7a7aa1ace0d9
, make up a 256-bit big number 0x6e0a0f8d822b704dda64b7b75a188e2a4b424d3fd5c65277d9e0aca17a7aef00
, which is exactly the raw representation of account 8QYkBcer7kzCtXJGNazCR6jrRJS829aBow12jUob3jhR
!
The program rejects any invocation with mismatching account #2. But what makes its on-chain behavior deviate from the simulation? Let’s take a deeper look at the last “goto” jump.
Function sub_100003120(account #3)
just dereferences and loads the lamports field of account #3. If it’s zero, the program silently exits. (Check the definition of AccountInfo
struct here)
Thus the balance (lamports) of the third input account, is the key to the backdoor!
Checking the corresponding account BKW62NtBeQJkjbBdh61WNfpfuT6ai34pU1eX4QPjxQyW in our case, only three consecutive transactions are recorded.
The first transaction (last in the picture) deposited SOL into account BKW6. The final transaction zeroed account BKW6 and tipped Jito. The malicious transaction was “sandwiched” by the others, just like quickly turning the switch on and off. In this way, the malicious behavior can only be triggered in a very narrow time window, never in the simulation, nor even after the execution.
Crime Scene Investigation
The Suspected Extension: Bull Checker
Upon further investigation of several affected users who have been drained by the same program, we have identified an extension called “Bull Checker”, which has the permission to read and change all the data on the website, as a potential cause.
Bull Checker is supposed to be a read-only extension that allows you to view the holders of memecoins. There should be no need for an extension like this to read or write data on all websites.
This should have been a major red flag for users, but apparently, several users continued to install and use the extension.
Targeting Memecoin Traders
In addition to the above information, while researching “Bull Checker” we discovered that it was publicized by an anonymous Reddit account, “Solana_OG” (now deleted). This person appeared to target users looking to trade memecoins and lured them to download the extension.
Spoofing Workflow Revealed
The extension has now been removed from the Chrome Web Store. We can find the cached stats here. The bundled files can be extracted by unzipping the extension as a zip file. These files are compressed but not heavily obfuscated and can be recovered using JavaScript beautifier tools.
The prettified `pha.292c2974.js` contains suspicious code related to the Phantom wallet. Since it can read and change data on all websites, it actively monitors apps containing the wallet adapter.
It replaces the wallet adapter’s signTransaction
method with its own implementation. If it detects a “simple” transaction with only one signer, it forwards this unsigned message to a remote server through an encrypted channel. On the server side, it determines the token accounts owned by the victim to take over, generates a fresh “switch” account for the victim, and then appends the drainer instruction to the original unsigned transaction.
The modified transaction is then submitted to Phantom for user confirmation. Once approved, the signed transaction is immediately sent to the server again. The server broadcasts the “sandwiched" transaction in a Jito bundle to ensure the drainer in the backdoor is exactly triggered on-chain.
Stop Spoofing and Stay Safe
Through collaborative efforts with Jupiter, Blowfish, Raydium, Phantom, and Offside Labs, we have uncovered the root cause of this series of crypto thefts. The Bull Checker extension has been removed, and the associated program account has been labeled as malicious.
While we have identified one malicious extension, there might still be other malicious extensions out there. There have been reports of other drains that we have not been able to track down. It’s crucial to maintain a healthy skepticism, even towards seemingly popular recommendations on social media platforms. Do not trust something just because someone mentioned it on Reddit or other media and it has many upvotes. Stay away from those “Solana OG”. Astroturfing and social engineering are very real.
Extensions that request extensive permissions are highly suspicious. An extension like Bull Checker should not need to read and modify all your website data. You should have an extremely high degree of confidence in an extension before you start using it.
Fortunately, there are ways to mitigate similar spoofing issues in the future. Unlike Ethereum, Solana transactions predeclare all accessed accounts in the message header before signing. This feature allows wallets and simulation-based analyzers to detect unusual patterns or unknown program invocations, potentially raising alarms regardless of whether the execution triggers malicious behavior.
In addition, Blowfish has released a new guard instruction feature called SafeGuard that prevents all simulation spoofing attacks. It’s currently being adopted by multiple Solana wallets and will prevent future such attacks.
Conclusion
This investigation has been an insightful journey, made possible through close collaboration with the Jupiter team. We extend our sincere gratitude to the teams at Blowfish, Raydium, and Phantom for their invaluable assistance throughout this process.
It's been a privilege to work alongside these dedicated teams, all of whom share a common mission: prioritizing the security of every user in the web3 ecosystem. This commitment aligns perfectly with our core values at Offside Labs.
As we conclude this investigation, we urge all users to remain vigilant in the face of evolving threats. The crypto landscape is dynamic, and new challenges will undoubtedly emerge. Remember to always verify, question, and stay informed. Keep watching for updates from Offside Labs as we continue our mission to enhance blockchain security.
Stay safe, stay curious, and keep an eye out for the Offside!