How to Steal $100M from Flawless Smart Contracts
💡 Read the original post here: https://pwning.mirror.xyz/okyEG4lahAuR81IMabYL5aUdvAsZ8cRCbYBXh8RHFuE
My blockchains adventure continues! This time I protected Moonbeam network by disclosing a critical design flaw, safeguarding more than $100M assets at risk in various DeFi projects. I was awarded the maximum reward amount of their bug bounty program on Immunefi, $1M, and $50k bonus from Moonwell (I guess that’s also one of the top 10 highest bug bounties?)
After reporting the bug in Aurora engine, I started to think about the other potential misuses of delegate calls of native contracts. The original purpose of the delegatecall
was to provide a mechanism through which one smart contract could share and reuse the code of another one’s to avoid the overhead of duplicated code storage. Native contracts are usually prebuilt contracts that implement special functions as extensions of the original EVM.
Now, the tricky part: if you delegatecall
to a native contract, you may be able to execute some unexpected functions, even privileged ones! However, the developers of those native contracts might not realize that the actual user of the functions could be someone else. In the Aurora bug's case, the native contract just assumes the caller will always be a magical address, so the hardcoded log emitter leads to the malicious withdrawal vulnerability.
What other assumptions could be wrong? When you do a delegatecall
, the calling context is inherited from the previous one, including the msg.sender
and msg.value
. From the view of a native contract, the contract that invokes delegatecall
is transparent: its caller will be considered the real user instead. So if a malicious contract is invoked, it can impersonate its caller to operate on the native contracts!
Shadow in the Moonbeam
Moonbeam and Moonriver are both EVM-compatible platforms. There are some precompiled contracts in moonbeam runtime which are shared between Moonbeam and Moonriver.
The Balance ERC-20 precompile provides an ERC-20 interface for handling the native tokens (MOVR
& GLMR
) of balance. The implementation Erc20BalancesPrecompile
is in moonbeam/precompiles/balances-erc20/src/lib.rs
.
The designer did not take the usage of delegatecall
in EVM into consideration. A malicious contract can pass its msg.sender
to the precompile contract to impersonate its caller. In this scenario, there is no way for the precompile contract to figure out the actual caller. The attacker can either increase the allowance from the victim or transfer the available balance immediately.
There are similar issues in the Asset ERC-20 precompile, which provides an ERC-20 native implementation of interoperable tokens (xcKSM
, xcDOT
, ...). The implementation Erc20AssetsPrecompileSet
is in moonbeam/precompiles/assets-erc20/src/lib.rs
.
However, the designer did consider the usage of delegatecall
in EVM for this case. The ERC20Instance
contract in the tests has implemented approve_delegate()
, transfer_delegate()
and transferFrom_delegate()
, with test cases ensuring their validity. Unfortunately (or, for us, fortunately!), it turns out that the design logic can not work properly, so they have removed the relevant code in their patch.
Here is a simple exploit that can be reused in both cases: the `asset` address of native token is 0x0000000000000000000000000000000000000802
and the asset
address of a local token could be 0xFfFFfFff1FcaCBd218EDc0EbA20Fc2308C778080
.
Perfect Contracts, or Perfect Victims?
What can we do with the design flaw? The basic idea is to ask someone to trigger your malicious contract, e.g., calling the `trap()` in the POC contract.
How can we persuade users to invoke some mysterious contracts? By Airdrop! The hackers behind the UNIH
token have demonstrated the crypto-native solution of phishing! Any user who wants to sell an airdropped token in uniswap has to call the malicious token contract to approve the DEX to spend their balance. Due to the vulnerable design of RUNE tokens, where only the tx.origin
instead of msg.sender
is checked for approval, all the RUNE tokens could be stolen by a trojan contract.
The phishing idea is creative, but it strongly limits the potential damage of the vulnerability. We want to find a better victim that is
willing to call your contract
not as smart as users
rich!
Who are the best friends of poor hackers? The flash loan providers! They hold a lot of money and call your contract callbacks as you wish! In the flash loan callback, they are forced to approve your future transfer of their asset tokens, even if they are flawless contracts!
Some DEX pairs support callbacks to arbitrary contracts, such as SolarBeam on MoonRiver and StellaSwap on Moonbeam. The stable swap pair between xcKSM
and stKSM
is also vulnerable due to the support of flash loans. If one token can be drained from the trading pair, then the other token can be moved out in one single swap, all the liquidity will be cleared.
Now the perfect written contracts become perfect victims. By the time I reported, the richest vulnerable contracts were:
0xea3d1e9e69addfa1ee5bbb89778decd862f1f7c5 on Moonriver, SolarBeam LP Token, $7.5M
0xa927e1e1e044ca1d9fe1854585003477331fe2af on Moonbeam, Stella LP Token, $2.7M
0x77d4b212770a7ca26ee70b1e0f27fc36da191c53 on Moonriver, xcKSM & stKSM pair, $2.4M
These tokens are worth about $12.6M, even a 10% potential loss is already more than $1M, the maximal bounty prize that Moonbeam offers. But real hackers won’t be stopped by trivial achievements, will there be any crazier victims?
the Glimmer
The native tokens MOVR
(on Moonriver) and GLMR
(on Moonbeam) are equivalent to ETH
on Ethereum. To be used in DeFi protocols, they have to be wrapped in ERC-20 compatible token contracts, just like WETH
.
The Balance ERC-20 precompile provides the native wrapper of the native tokens. None of any deployed protocols ever use it because the official wrapped tokens WMOVR
and WGLMR
are more widely adopted. By leveraging the vulnerability of the Balance ERC-20 precompile, we are able to steal the native token balance from any userif it invokes the malicious contract. Unlike the Asset ERC-20 precompile tokens, e.g., xcKSM
and xcDOT
, which are directly used by DEXes, MOVR
and GLMR
balances are rarely used by smart contracts.
Most contracts with non-zero balances are multisig wallets, which are not so different from the externally owned accounts. The only exception, our glimmer of hope, is the MGlimmer
contract of the Moonwell project.
The Moonwell project is the dominant DeFi protocol on Moonriver. It has nearly $200M in supply and $100M available for borrowing (at the time I reported). MGlimmer
, or Moonwell: mMOVR Token
is the specific contract that handles the lending and borrowing against MOVR
, and it has the native MOVR
in its balance.
The cool thing is that when it transfers the balance to the user, it will call the destination as a contract! Here is a simple exploit that gains the approval of spending all the balance of MGlimmer
.
The balance stored in the vulnerable contract is not too much (a few thousand of MOVR
now). Still, any amount you deposit into that contract will be considered your collateral in this lending protocol. The exploit can be further weaponized by repeating the deposit → borrow → transfer all back → leave bad debt procedure. All borrowable assets can be drained!
It could absolutely be in the top 10 heists of DeFi history if any hacker took all the $100M borrowable assets plus $12M vulnerable tokens in DEX pairs! Another amazing fact about these vulnerabilities is that actual attacks could be super stealthy by only keeping the unauthorized privilege instead of stealing immediately. Since the contracts themselves are flawless, it’s almost impossible for the developers of those DeFi projects to figure out what is going wrong!
Responsible Disclosure
Everybody is struggling in the bear market. I don’t want to hurt anyone, I want to help, especially when they are such hard working teams. So I tried my best to help the project teams understand the root causes and speed up the patching process.
The situation here is extremely complex at first. The flaw is in the deep core of the blockchain system, but the victims are the DeFi protocols built on top of it. Some protocols may have emergency exits to pause and upgrade their contracts, while others are just immutable.
The ultimate patch has to be made by the Moonbeam team, but the biggest victim, Moonwell, could have mitigations for this vulnerability before the actions of the Moonbeam. I noticed that both Moonbeam and Moonwell had launched their bounty program on Immunefi, so I discussed with the Immunefi team the potential special case (without disclosing the specific name of the projects). After realizing that other projects, like the uniswap style DEXes, can not really do anything useful, I decided not to report to them to minimize the risk of leaking the critical information. Since only the Asset ERC-20 issue might be a problem for the Moonwell team, I have to split the report for Moonbeam into two parts, only the necessary information and the exclusive GlimmerExploit POC are submitted to them.
I took special care of the Moonbeam team, even if they might not be aware of that. I spent extra hours learning their code base and writing two complete test cases (600+LOC) just in case they needed more explanation of the root cause. I asked the common timezone of the PureStake team in their discord to make sure I submit the report once the first engineer comes to work. Luckily, it worked out: they started handling this issue on Friday and finished the patch before the weekend without any inquiries!
Is this the last unexpected flaw hiding near the core design of blockchain-based smart contracts? Certainly not! For every delegatecall
, there's another, even wilder issue hiding in the darkness. There are always other, more exotic issues waiting for me!. Stay with me on my journey as I find these bugs, push them into the light, and make the blockchain safer for everyone!