Follow me on Twitter ✌🏻
Web3 industry evolving rapidly, we see that many interesting projects are coming into the space. The price of the Bitcoin hit 50k$, it is a huge sign that the bull-run is coming. Unfortunately, the hacks are still the most problematic question in the DeFi space. How to protect the protocols? The bast way is to look at the past and based on the experience move forward.
Smart-contracts are the blood vessels through which the crypto blood flows
Today , we are going to explore the PolyNetwork exploit that resulted in 600m loss. The funds were returned, but we have to take the lesson from it. Even if it was in 2021 such a sophisticated attack could happen again today.
When the smart contact wants to call another smart contract, it uses the special command call
. The concern is, if the contract that is called isn’t trusted, it could lead to a serious risks.
It is the prerequisite for the attack explanation below. This contract stores very important function called putCurEpochConPubKeyBytes
. Basically it manages the public keys of the owners. It is so important function so it is protected by the OnlyOwner
modifier (only owner can call this function).
verifyHeaderAndExecuteTx
- it is the function that is responsible for verifying the correctness of a cross-chain transaction from the Polychain to Ethereum. It could be called by anyone, and it is important!
When this function is called, it starts to do the job of verifying and checking necessary information. And, almost at the end of the execution this function calls another function in the same contract. The name of the called function is _executeCrossChainTx
. This function is responsible for invoking a targeted contract on the Ethereum side and triggering the execution of a cross-chain transaction.
As you remember, the smart contract shouldn’t call other ‘untrusted’ contracts, cause they could be malicious(controlled by hackers). But in this scenario it is more interesting. The _executeCrossChainTx
function doesn’t call any ‘untrusted’ contract. BUT, it calls the EthCrossChainData contract(see above), so it is a friendly-well known contract, what could go wrong? As you remember, this contract has a very important function that manages the public keys of the owners!
The interesting part, that _executeCrossChainTx
can’t just call
any function from the target contract, but only the one, which fits for certain criteria.
Eventually, we have the opportunity to call very important contract, and potentially bypassing the onlyOwner
modifier. But, we still need to figure out how to call valuable function(which manages public keys)? Because the function that we want to trigger take one input parameter, but the _executeCrossChainTx
force us to call only the function with 3 parameters.
How to solve this quiz? See second chapter.
Okay! What we have? We need to call the function that manages the public keys, however if we take a look on it we see that it accepts only one input parameter, meanwhile using the _executeCrossChainTx
we can call only functions that accept 3 parameters. How to figure it out?
// Store Consensus book Keepers Public Key Bytes
function putCurEpochConPubKeyBytes(bytes memory curEpochPkBytes) public whenNotPaused onlyOwner returns (bool) {
ConKeepersPkBytes = curEpochPkBytes;
return true;
}
In Solidity there is a term called ‘function selector’, imagine it like a cryptographic number(in bytes) that differentiate the function from all others. To compute function selector we have to take the name of the function and parameters “transfer(uint256)” and hashed it → 0x12514bba
🔑 Function selector is the first 4 bytes of the hash. For example if we hash “transfer(uint256)” it will output a very long line of characters, but we only need the first 8 characters.
So, take a look one more time. abi.encodePacked
will hash
The _method (it will be the name of the function)
The inputs that this function will receive (there have to be 3 inputs) → and it will produce the function selector.
The valuable function that manages the pub.keys also have the function selector that is visible to everyone. It is - 0x41973cd9 → putCurEpochConPubKeyBytes(bytes)
. What attacker did, he has took this function selector and based on it , he has managed to precompute the same selector but for the function with 3 inputs using the ‘brute-forcing method’.
🔑 He has simply went through all the options where the first 8 characters are the same. Remember! It is almost impossible to precompute equal hashes with all characters! But if we care about only first 4 characters - it is simpler)
Eventually, attacker managed to call very important function that basically granted him the owner rights. With it he could do whatever he want, for example withdraw almost one billion dollars. Anyway, it teaches us that security matter and owners have to be aware of the block-holes in the code, and hire the good smart-contract developers)
Resources used