
Backstory
On October 6th 2022, the BSC Token Hub bridge (hereinafter BSC), belonging to the largest cryptocurrency exchange, Binance, was hacked. This was one of the largest cryptocurrency hacks ever. BSC ensures the interaction between the Binance Beacon Chain blockchain used by Binance for decentralized management (stacking, voting) and Binance Smart Chain, an EVM-compatible blockchain used to create various decentralized applications. Hackers withdrew 2 million BNB (Binance’s cryptocurrency) from the bridge protocol, with 1 BNB worth $293 at the time. A total of $586 million was stolen.
The technical aspects
Blockchain bridges are used to transfer data and assets between heterogeneous blockchains. They act as intermediaries to send transactions, so whether you trust a transaction sent from blockchain A to blockchain B depends on the bridge between A and B. To trust a transaction provided by blockchain A, the bridge needs to validate it. Depending on the bridge logic, there are several ways to verify transactions, but they all depend on how data is recorded and stored in the blockchain, that is, the tree-like structure of data representation.
Each node of the binary tree is a concatenation of hashes from its two child nodes. The end nodes of the tree corresponding to the transactions added to the blockchain are called leaves, and the top root nodes are called roots. This tree-like structure of data representation is called a binary search tree and allows you to easily check the legitimacy (authorship) and integrity of data recorded in any of the tree nodes. Knowing the hash of the data being checked and the values of intermediate nodes used when calculating the root hash, you can perform the Merkle proof: starting at the bottom of the node, check that each successive hash is correct, up to the root. Any discrepancy will indicate that the data in the node has been tampered with.

How BSC transaction validation works
The BSC bridge uses a balanced AVL tree, a kind of a binary search tree, to validate transactions. For each node of this tree, the height of its two branches differs by no more than 1. The verification algorithm is called in the handlePackage function of the main CrossChain smart contract, which processes token transfers between blockchains.

handlePackage
function declaration in BSC main smart contractThis function also contains the onlyRelayer
modifier, which means that only a relayer can call this function.
Structure of the transaction validation request
Relayers in bridges process specially formatted data packets coming from blockchain A, extract the necessary parameters from them, and translate them to the network for transmission to blockchain B. To register as a relayer, it is necessary to deposit 100 BNB tokens and configure the device connected to the blockchain in accordance with the configuration file. After registration, the relayer starts parsing the data in the endBlock event table of each network block and selecting from it all the IBCPackage events.

The value parameter has four attributes separated by “::”:
- The first attribute is the destination chain name; in this example it is “bsc”.
- The second attribute is the CrossChainID of destination chain; in this example it is “2”.
- The third attribute is the channel id; in this example it is “8”.
- The fourth attribute is the sequence; in this example it is “19”.
After processing this event, the relayer processes the data packet with the transaction sended from blockchain A to blockchain B. The relayer extracts the following parameters from the packet:
Parameter name | Size | Value |
---|---|---|
prefix | 1 byte | 0x00 |
source chain CrossChainID | 2 bytes | Transaction source blockchain ID |
destination chain CrossChainID | 2 bytes | Transaction destination blockchain ID |
channelID | 1 byte | IBCPackage event channel ID |
sequence | 8 bytes | IBCPackage event sequence number |
Next, the relayer sends these parameters, transaction data, the transaction validation sign (prove), and block height to a special RPC request that calls the handlePackage
function of the CrossChain contract:

handlePackage
functionMerkle proof
In the handlePackage
function, the validateMerkleProof
function of the MerkleProof library is called; using the staticcall
method at 0x65, the precompiled iavlMerkleProofValidate
contract is called. This contract is a library written in Go that has a number of dependencies (methods) of the Cosmos cross-chain framework that implement the Merkle proof functionality.

These dependencies are called in the Run function of the iavlMerkleProofValidate
contract in strings 8, 9, and 16:

iavlMerkleProofValidate
precompiled contractThe called op.Proof.ComputeRootHash()
method calculates the AVL tree root hash for the tree leaf that contains the transaction being checked. Next, the op.Proof.Verify(root)
method compares the hash of the AVL tree root with the one calculated at the previous step. If the compared hashes differ, the op.Proof.Verify(root)
method will return an error, and the transaction transfer from blockchain A to blockchain B will be canceled. If the hashes are the same, the op.Proof.VerifyItem(op.key, value)
method is called, in which the presence of the transaction data hash in the AVL tree is checked. If the hash is found, the transaction is considered valid and is executed.
Transaction verification vulnerability
The vulnerability of the transaction verification process is related to the way the tree root hash for the transaction is calculated and checked. The ComputeRootHash
function calls the pwl.Leaf.Hash()
method:

Hash
function call in computeRootHash
functionThe Hash
function correctly calculates the root hash in case when the left leaf in the Merkle proof chain for the transaction being checked equals zero, that is, not defined. But if the left leaf is defined, the tree root hash is calculated without taking into account the right leaf. In other words, if the left leaf in the Merkle proof chain for the checked transaction is defined, the value of the tree root hash will not depend on the presence of the right leaf in the proof chain.

This flaw allows attackers to write a payload to the right leaf and successfully pass the tree root hash value check, provided that the payload hash is calculated correctly.
Attack scenario
Before starting the attack, criminals deposited 100 BNB to the Relayer Hub contract in order to register as a BSC bridge relayer.
They used a legitimate transaction that was used to transfer 0.05 BNB from the BSC bridge two years ago. They changed the payload by specifying the attacker address as the recipient and changed the amount to 1 mln BNB:
Original transaction | Attacker transaction | |
---|---|---|
payload | 0x00000000000000000000000000000000000 00000000000000000038d7ea4c68000f86da0 424e420000000000000000000000000000000 0000000000000000000000000009400000000 0000000000000000000000000000000087b1a 2bc2ec50000944e656459ed25bf986eea1196 bc1b00665401645d94a10123c15a63135fe94 5a54232bae7fac8177056845fd12999 | 0x00000000000000000000000000000000000 0000000000000000000000000000000f870a0 424e420000000000000000000000000000000 0000000000000000000000000009400000000 000000000000000000000000000000008ad3c 21bcecceda100000094489a8756c18c0b8b24 ec2a2b9ff3d4d447f79bec94489a8756c18c0 b8b24ec2a2b9ff3d4d447f79bec846553f100 |
The payload content is a structure encoded in RLP format:

Next, attackers modified the proof variable content by adding the right leaf with the payload hash to the AVL tree, and added an empty internal host to balance the AVL tree:
Original transaction proof structure | Attacker transaction proof structure |
---|---|
RangeProof{ LeftPath: PathToLeaf{ 0:proofInnerNode{ Height: 1 Size: 3 Version: 110217392 Left: 0C10F902D266C238A4CA9E26FA9BC36483CD3 EBEE4E263012F5E7F40C22EE4D2 Right: } 1:proofInnerNode{ Height: -1 Size: 2 Version: 110217392 Left: E4FD47BFFD1C06E67EDAD92B2BF9CA63631978676288A2AA99F95C459436EF63 Right: } } InnerNodes: Leaves: proofLeafNode{ Key: 0000010038020000000000000002 ValueHash: 11056C6919F02D966991C10721684A8D1542E44003F9FFB47032C18995D4AC7F Version: 110217392 } (rootVerified): true (rootHash): E09159530585455058CF1785F411EA44230F39334E6E0F6A3C54DBF069DF2B62 (treeEnd): true } | RangeProof{ LeftPath: PathToLeaf{ 0:proofInnerNode{ Height: 1 Size: 3 Version: 110217392 Left: 0C10F902D266C238A4CA9E26FA9BC36483CD3 EBEE4E263012F5E7F40C22EE4D2 Right: } 1:proofInnerNode{ Height: -1 Size: 2 Version: 110217392 Left: E4FD47BFFD1C06E67EDAD92B2BF9CA63631978676288A2AA99F95C459436EF63 Right: DA657C1FFB86C684EB3E265361EF0FA4F9DFA670B45F9F91C5EB6AD84B21A4D1 } } InnerNodes: empty-PathToLeaf Leaves: proofLeafNode{ Key: 0000010038020000000000000002 ValueHash: 11056C6919F02D966991C10721684A8D1542E44003F9FFB47032C18995D4AC7F Version: 110217392 } proofLeafNode{ Key: 00000100380200000000010DD85C ValueHash: 2C3A561458F8527B002B5EC3CAB2D308662798D6245D4588A4E6A80EBDFE30AC Version: 1 } (rootVerified): true (rootHash): E09159530585455058CF1785F411EA44230F39334E6E0F6A3C54DBF069DF2B62 (treeEnd): true } |
Due to the vulnerability, the content of the added right leaf did not affect the root hash, so the fraudulent transaction was successfully verified. After checking the transaction, the CrossChain contract called a function to transfer 1 mln BNB from BSC to the attacker address. Next, the attackers tried to repeat the transaction, but the next 15 attempts were unsuccessful because of the incorrect packageSequence
value. On the 16th attempt, however, they managed to find the correct packageSequence
value and obtain another 1 mln BNB at their address.
Next, for fear of freezing and blocking of assets in BSC, the attackers began to withdraw money from the bridge. For laundering, they used the Venus Finance DeFi: they issued wrapped vBNB tokens in exchange for BNB, and used vBNB as a collateral to loan BUSD, Binance’s stablecoin. Next, using the Stargate and Anyswap bridges, the attackers converted BUSD to the USDT and USDC stablecoins in several blockchains: Ethereum, Avalanche, Fantom, Polygon, Arbitrum, and Optimism.

Reaction of Binance and the crypto community
After identifying the attack, Binance suspended and forked the BSC blockchain, preventing the attackers from withdrawing more than $400 million. Following that, Tether, the owner of the USDT stablecoin, blocked the attacker’s USDT address, preventing them from laundering part of the stolen funds. The attacker addresses were blacklisted:

Vulnerability elimination
Initially, the AVL tree verification method assumed that only the right or only the left leaf of the tree could be defined for a transaction to be verified. However, the check for the simultaneous presence of the right and left leaves in the AVL tree check algorithm was initially missing, and the attacker took advantage of it.
Shortly after the hack, a fix was introduced to the AVL proof method of the Cosmos cross-chain framework:

In case of simultaneous presence of the left and right leaf of the AVL tree, the transaction verification will be rejected with a corresponding error.
Conclusion
The Binance Smart Chain Token Hub bridge hack is an example of exploiting a vulnerability in a third-party component that the bridge uses to determine whether a transaction can be trusted and executed or rejected. Although the vulnerability was in a third-party component, it caused great financial damage to the BSC bridge. Apparently, before it was fixed, this vulnerability was also present in other DeFi protocols using the AVL proof method of the Cosmos cross-chain framework. However, based on the amount of funds stolen from BSC, we can assume that the attackers were after the jackpot from the very beginning, knowing that after the vulnerability is fixed, they would not get a second chance.