Another on-chain random exploit, a case study with MechMaster project
Credit: lifebow, Giap Nguyen
Verichains had contacted the MechMaster team before the blog was published. The MechMaster team had planned to solve the issues.
Summary
On-chain randomness is not easy to implement because naturally blockchain is deterministic. In this case study, we analyze a typical mistake on-chain randomness implementation in a real NFT game. We also demonstrate the severe impact by arbitrarily minting high value NFT items. Finally, we give our opinions about on-chain randomness.
Why randomness?
Randomness is critical for any NFT game. It provides a general fairness and truthless for the community. If the random logic is abused, attackers are able to manipulate the game or generate unlimited high value NFTs which drain the project's pool and the community's investment.
Why is on-chain randomness not easy?
Naturally blockchain logic is deterministic. The process should be repeatable and predictable which allows every node in the network to re-run the same logic to generate the same result. So it is hard for a node to perform a random minting NFT item and the rest of the network to reproduce or verify the result.
An outdated recommendation for on-chain randomness takes some random source from the PoW block to generate pseudo random numbers.
function _getRandomNumber(uint _upper) private returns (uint) {
_seed = uint(keccak256(_seed,
block.blockhash(block.number — 1),
block.coinbase,
block.difficulty));
return _seed % _upper;
}
Notice it is not a safe implementation and the behavior of those supposed random variables is different between blockchains.
Scope of the case study
Website market of Mech master:
https://market.mechmaster.io/
Verified source code on bscscan of the proxy contract published on the website. https://bscscan.com/address/0xe35f67aec4f633c01130fdc9f18286a4215c3e5f#code
The main contract which the proxy contract points to.
https://bscscan.com/address/0x37281cf9d0eda5059f41a62e969757f55c62bc1f
Explain the game on-chain logic
The game’s ERC1155 contract requires players to stake a certain $MECH token to minting random puzzles in the shape of the game's ERC1155 token.
The rarity of puzzles is distributed as below.
The analysis
Step 1: dig into the website’s dApp source code.
Notice the highlighted smart contract’s draw function called with two parameters: now and count which are totally controlled by the end-user.
Step 2: dig into the on-chain smart contract
Following the dApp calls, we identify the logic smart contract for the draw function at https://bscscan.com/address/0x37281cf9d0eda5059f41a62e969757f55c62bc1f
The smart contract has no source code but with a little effort, we can decompile into a clean version of the draw version. And focus on the random logic below.
The sha3 function is used to derive random results for the minting process. Parameters used in the calculation included:
block.difficulty: on Binance Smart Chain it is default = 2.
_blockNumber: it is actually the now value submitted by the end-user. By mistake, the decompiler assigned an incorrect name.
caller: address of the caller.
stor256: the value of contract’s storage which is extractable from blockchain data.
Now, we guess that by controlling the now value from dApp, we are able to manipulate the smart contract to mint a rare legendary anytime.
Step 3: Exploitation
Don't waste time to understand the minting logic, we fork the bsc mainnet and write a simple code to brute force the now value until the contract vomits legendary tokens (in the local environment).
Applying the results into bsc mainnet, we successfully mint 3 legendary tokens.
https://bscscan.com/tx/0x7dc44ff24ac46250547be458261699a8bb2854e2e189c557985fef4394ecf7f2
https://bscscan.com/tx/0xac972b895e72331a4ba79ba63140f666ea18338383ae15d2d3c3832d1f4019a6
https://bscscan.com/tx/0xf26bc9f07e9e2052da502fda20b55ce1019bb069a9695da3272bbc8cf042535f
What is the developer's mistake?
Looks like the developer made a common mistake when copying random code from Ethereum for BSC. The block.difficulty in Ethereum might contribute some randomness source but in BSC it is a fixed value. But even updating the source code, it is still vulnerable for advanced attacks which are out of the scope of the study-case.
To summarize our recommendations:
Notice 1: Never assume every blockchain has the same behavior even if it is emv-compatible.
Notice 2: On-chain randomness is very difficult, don't do it by yourself unless you are very good in cryptography and blockchain security.