Skip to content

Instantly share code, notes, and snippets.

@HildisviniOttar
Last active June 12, 2022 23:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save HildisviniOttar/5b86859eab0705646c23820dc072e69e to your computer and use it in GitHub Desktop.
Save HildisviniOttar/5b86859eab0705646c23820dc072e69e to your computer and use it in GitHub Desktop.
THORChain Vulnerability - Mint 90 billion RUNE for 10 BNB

Description

THORChain started with a BEP2 token (RUNE-B1A) and ERC20 RUNE. To "upgrade" RUNE to native, a user sends BEP2 RUNE-B1A to the BNB vault with memo switch:<rune address>.

There is a bug where 90 billion fake BEP2 RUNE can be sent and redeemed for real RUNE, then swapped for 100% of assets.

Attack:

Firstly an attacker needs to create a BEP2 token, exactly RUNE-67C on mainnet. This is available because it is only registered on Binance testnet (as of writing!). The token suffix -XXX is selected by Binance chain as the first 3 digits of the transaction hash.

https://github.com/binance-chain/BEPs/blob/master/BEP2.md

Normally this is essentially "random" and would require average 23,000 "guesses" to get, worst case 46,000, costing 10 BNB each. However a script can be written fairly easily to ensure the transaction hash begins with 67C in order to get the desirable BEP2 token suffix. One way would be to randomly generate private keys until the transaction hash is correct, or slightly modifying the amount of BNB, for example 10.00000001. Several tokens exist with the popular -000 and -888 suffix which proves this is feasible. This disclosure does not go into detail here, suffice to say it's possible to generate the RUNE-67C token on mainnet with around $3500 to $20,000 funds and 1-2 days work.

The attacker then sends 90 billion fake RUNE into the BNB vault address with memo switch:<attackers thor.rune address>. THORChain mints 90 billion native RUNE which the attacker then does SWAP and/or ADD+WITHDRAW to steal 100% of pool assets.

Funds at risk / Severity:

Critical
Without network rate limiting: 100% of all pools.
With network rate limiting and fast validator "halt" response: around $1m.

Difficulty
Easy-ish: A script needs to be written to generate the RUNE-67C asset, which is achievable, and at least 10 BNB (Binance token issue fee).

Code walkthrough

In handler_switch.go the observed attackers BNB switch transaction comes in and passes ValidateBasic and validateCurrent.

The handleCurrent function then checks for THORChainHalted mimir and immediately checks the asset:

if !msg.Tx.Coins[0].IsNative() && msg.Tx.Coins[0].Asset.IsRune() {
	return h.toNativeV56(ctx, msg)
}

The IsRune() function is defined as follows:

// IsRune is a helper function ,return true only when the asset represent RUNE
func (a Asset) IsRune() bool {
	return a.Equals(Rune67CAsset) || a.Equals(RuneB1AAsset) || a.Equals(RuneNative) || a.Equals(RuneERC20Asset) || a.Equals(RuneERC20TestnetAsset)
}

The Asset .Equals is defined as:

a.Chain.Equals(a2.Chain) && a.Symbol.Equals(a2.Symbol) && a.Ticker.Equals(a2.Ticker) && a.Synth == a2.Synth

And the symbol constants are:

Rune67CAsset = Asset{Chain: BNBChain, Symbol: "RUNE-67C", Ticker: "RUNE", Synth: false} // testnet asset on binance ganges
// RuneB1AAsset RUNE on Binance main net
RuneB1AAsset = Asset{Chain: BNBChain, Symbol: "RUNE-B1A", Ticker: "RUNE", Synth: false} // mainnet
// RuneNative RUNE on thorchain

The fake mainnet shitcoin RUNE-67C passes the test and leads to the holy grail: h.mgr.Keeper().MintAndSendToAccount.

I did not investigate the feasibility of generating a fake ETH contract address for the testnet version as it is not required given that it is much easier to create the desired BNB token.

How was it found?

This was found stepping through all of the handlers. For each handler asking how would I abuse the priveleges in this handler, then looking for ways to exploit. The switch function goal is clearly to pass an attacker controlled coin amount and address into the mint function. The main stumbling block is to pass the IsRune() test, which upon further research on BNB token creation, revealed that RUNE-67C does not exist on Binance mainnet and it is possible to use a script to mint exactly RUNE-67C fairly cheaply by generating transaction hashes. Otherwise a "random" generations at 10 BNB each would cost $50-120m before finding the right address, which would be unrealistic.

Disclosure

This was reported to @leena on Discord at 2231h AEST, 28 Jul 2021. A video conference was organised with @leena and @heimdall, and also reported on Discord to @Son of Odin who confirmed the vulnerability and immediately began a PR.

This vulnerability will not be publicly disclosed until 90 days or until fixed in THORChain with all ACTIVE validators running the patched code.

How to fix

Remove the testnet tokens from the IsRune() check for THORChain mainnet build.

The network is safe whilst halted, and can be brought up safely with HaltTHORChain prior to 100% of nodes running the new code.

Bounty

More beer coolers ?!

@HildisviniOttar
Copy link
Author

Update: actually I think I could extract more by not being greedy and minting say 500M tokens, sending 5M to a wallet, perform the attack mint switch to native, then do swaps in various pools. This would appear like a whale dumping, which is a legitimate activity and could increase the block sign out rate even with a limiting system. There would probably be more confusion and discussion in #mccn than usual as to whether it’s malicious because it look like a switch (legit) and swaps (legit). I think this ruse could get away with $5-20m before people got spooked or dug deep enough to realise that it wasn’t RUNE-B1A

@HildisviniOttar
Copy link
Author

@HildisviniOttar
Copy link
Author

Update: This is now 100% patched in 0.63.2 live with 100% consensus, so will release to public.

@AminAzGol
Copy link

Very nice catch. I bet you jumped when you found it lol. This was a huge bug and could lead to an end for THORChain. I'm glad this was found first by a white-hat. Hope the bounty is worth your great work.

@HildisviniOttar
Copy link
Author

Somebody a few weeks later registered RUNE-67C and actually tried attacking Single Chain (which was still active at the time) - with a bit of patience they could have stolen assets. That was on its final days and is now fully shutdown (ragnarok'd). A few other people found this but thought they could horde their 0days - they should have taken the bounty.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment