Alright. I figured out the Solana x Wormhole Bridge hack. ~300 million dollars worth of ETH drained out of the Wormhole Bridge on Ethereum. Here& #39;s how it happened.
Here& #39;s the transaction which finalized a majority of the exploit. 80k ETH pulled out of the Wormhole contract on Ethereum in a single transaction: https://etherscan.io/tx/0x24c7d855a0a931561e412d809e2596c3fd861cc7385566fd1cb528f9e93e5f14">https://etherscan.io/tx/0x24c7...
Although it& #39;s dramatic, this transaction is just the very end of an interesting series of events. I had to start working my way backwards to figure out how this was even possible.
Wormhole is a "bridge" -- basically a way to move crypto assets between different blockchains. Specifically, Wormhole has a set of "guardians" that sign off on transfers between chains. It& #39;s a little more complicated than that in practice, but that& #39;s the general idea.
The transaction that pulled out 80k ETH was actually the attacker transferring 80k ETH from Solana to Ethereum. I originally thought that the contract might& #39;ve incorrectly validated the signatures on the transfer, but the signatures completely checked out.
The Wormhole "guardians" had somehow signed off on this 80k ETH transfer as if it were 100% legit. How was that possible?
The first breakthrough came from this transaction on Solana which somehow minted 120k "Wormhole ETH" out of nowhere: https://solscan.io/tx/2zCz2GgSoSS68eNJENWrYB48dMM1zmH8SZkgYneVDv2G4gRsVfwu5rNXtK5BKFxn7fSqX9BvrBc1rdPAeBEcD6Es">https://solscan.io/tx/2zCz2G...
That explains part of it! The attacker was able to mint Wormhole ETH on Solana, so they were able to correctly withdraw it back to Ethereum. Now we just need to figure out how the attacker was able to mint this Wormhole ETH on Solana...
The next interesting piece of information is this Solana transaction that came right before the 120k ETH one, where 0.1 Wormhole ETH was minted on Solana: https://solscan.io/tx/3HRKFcGjVVtf1kTHWXqJmG6zimbNeQAgN5xbt3MjP3ewYB1ih1WuWjMU6EWQXAz8shD46A3uyYW9pahoARrBmmPu">https://solscan.io/tx/3HRKFc...
If we take a look at the attacker& #39;s transaction history on Ethereum, we end up seeing that the attacker *did* make a deposit of 0.1 ETH *into* Solana from Ethereum: https://etherscan.io/tx/0xf54d9d84e3c8a63cd007cd52d42eedbb7be34a64f7c088086071f1e2929e1521">https://etherscan.io/tx/0xf54d...
Of course, the attacker definitely did not make a 120k ETH deposit into Wormhole on Ethereum. But there& #39;s something interesting about this deposit. It definitely has something to do with the attack, but what?
Alright, here& #39;s where we start getting into the weeds of Solana. This is the first time I& #39;ve ever looked at Solana contracts so it took me a while to get my bearings, but I think I finally get what& #39;s going on.
The transactions that minted Wormhole ETH on Solana were triggering this Wormhole function "complete_wrapped": #L152">https://github.com/certusone/wormhole/blob/8d15138d5754b5e1202ff8581012debef25f7640/solana/modules/token_bridge/program/src/instructions.rs #L152">https://github.com/certusone...
One of the parameters that this function takes is a "transfer message", basically a message signed by the guardians that says which token to mint and how much: #L190">https://github.com/certusone/wormhole/blob/8d15138d5754b5e1202ff8581012debef25f7640/solana/modules/token_bridge/program/src/instructions.rs #L190">https://github.com/certusone...
Solana is kinda weird, so these parameters are actually smart contracts themselves. But the important thing is how these "transfer message" contracts get created. Here& #39;s the transaction that made the 0.1 ETH transfer message: https://solscan.io/tx/5fKWY7XyW6PTzjviTDvCTpsqgfoGAAqUs1mC6w4DZm25Ppw7fX7aWDmrnkknewyZ81qMSix3c18ZuvjoZUF34tpa">https://solscan.io/tx/5fKWY7...
This "transfer message" contract is created by triggering a function called "post_vaa": #L162">https://github.com/certusone/wormhole/blob/9a4af890e3e2d4729fe70e43aaced39ba8b33e35/solana/bridge/program/src/instructions.rs #L162">https://github.com/certusone...
It doesn& #39;t really matter if you understand the fancy code, the most important thing here is that post_vaa checks if the message is valid by checking the signatures from the guardians. That part seems reasonable enough. But it& #39;s this signature checking step that broke everything.
One sec loading up my next tweets... hold your horses
Alright let& #39;s keep going. "post_vaa" doesn& #39;t actually check the signatures. Instead, in typical Solana fashion, there& #39;s another smart contract which gets created by calling the "verify_signatures" function. #L132">https://github.com/certusone/wormhole/blob/91296e67722032debf04e95c71b3d701d4625c5b/solana/bridge/program/src/instructions.rs #L132">https://github.com/certusone...
One of the inputs to the "verify_signatures" function is a Solana built-in "system" program which contains various utilities the contract can use: #L155">https://github.com/certusone/wormhole/blob/91296e67722032debf04e95c71b3d701d4625c5b/solana/bridge/program/src/instructions.rs #L155">https://github.com/certusone...
Within "verify_signatures", the Wormhole program attempts to check that the thing that happened right before this function was triggered was that the Secp256k1 signature verification function was executed: #L108">https://github.com/certusone/wormhole/blob/ca509f2d73c0780e8516ffdfcaf90b38ab6db203/solana/bridge/program/src/api/verify_signature.rs #L108">https://github.com/certusone...
This verification function is a built-in tool that& #39;s supposed to verify that the given signatures are correct. So the signature verification has been outsourced to this program. But here& #39;s where the bug comes in.
The Wormhole contracts used the function load_instruction_at to check that the Secp256k1 function was called first: #L101">https://github.com/certusone/wormhole/blob/ca509f2d73c0780e8516ffdfcaf90b38ab6db203/solana/bridge/program/src/api/verify_signature.rs #L101">https://github.com/certusone...
The load_instruction_at function was deprecated relatively recently because it *does not check that it& #39;s executing against the actual system address*! #L180-L188">https://github.com/solana-labs/solana/blob/7ba57e7a7c87fca96917a773ed944270178368c9/sdk/program/src/sysvar/instructions.rs #L180-L188">https://github.com/solana-la...
You& #39;re supposed to provide the system address as the program you& #39;re executing here (it& #39;s the third-to-last program input): #L153">https://github.com/certusone/wormhole/blob/9a4af890e3e2d4729fe70e43aaced39ba8b33e35/solana/bridge/program/src/instructions.rs #L153">https://github.com/certusone...
Here& #39;s that system address being used as the input for the "verify_signatures" for the legit deposit of 0.1 ETH
But here& #39;s the "verify_signatures" transaction for the fake deposit of 120k ETH
That& #39;s not the system address!
Using this "fake" system program, the attacker could effectively lie about the fact that the signature check program was executed. The signatures weren& #39;t being checked at all!
After that point, it was game over. The attacker made it look like the guardians had signed off on a 120k deposit into Wormhole on Solana, even though they hadn& #39;t. All the attacker needed to do now was to make their "play" money real by withdrawing it back to Ethereum.
And one withdrawal of 80k ETH + 10k ETH later (everything in the bridge on Ethereum), everything was gone.
Wormhole reports that the exploit has been patched https://twitter.com/wormholecrypto/status/1489036273012682753?s=20&t=NLOh_IUNd6DlVWNAYUIIVA">https://twitter.com/wormholec...
A commit was made ~9 hours ago replacing usage of load_instruction_at with load_instruction_at_checked, which actually confirms that the program being executed is the system program: #diff-0d27d8889edd071b86d3f3299276882d97613ad6ab3b0b6412ae4ebf3ccd6370R92-R103">https://github.com/certusone/wormhole/commit/7edbbd3677ee6ca681be8722a607bc576a3912c8 #diff-0d27d8889edd071b86d3f3299276882d97613ad6ab3b0b6412ae4ebf3ccd6370R92-R103">https://github.com/certusone...
It& #39;s interesting that this commit was made ~9 hours ago and the exploit happened a few hours after that. Possible that an attacker was keeping an eye on the repository and looking out for suspicious commits.
Could be that the Wormhole team spotted the bug, patched it, but the attacker got to it before the patch could be rolled out. Super important to keep these sort of patches lowkey and to try to stuff them into larger commits.
It looks like maybe Wormhole tried to do this by including the change in a much larger and unassuming commit called "Update Solana to 1.9.4": https://github.com/certusone/wormhole/commit/7edbbd3677ee6ca681be8722a607bc576a3912c8">https://github.com/certusone...
Not sure exactly what happened here, but a clear lesson to try to deploy before making any patch details public, if you can afford to do that. Of course this ends up being at odds with Web3 ideals, so not always clear how to best handle these sort of things.
As another commenter has noted, it could also be that the attacker knew about the bug in advance and was forced into exploiting the bug because the patch was being rolled out. Seems hard to construct this attack within ~2 hours so could be a possibility here.