On August 10, 2021, according to the news from the SlowMist Zone, the cross-chain interoperability protocol Poly Network was attacked by hackers. The SlowMist security team immediately cut into the analysis and shared the analysis results as follows.
The Object of Attack
As shown in the figure below, we can clearly see the architecture design of Poly Network through the official introduction: users can initiate cross-chain transactions on the source chain. After the transaction is confirmed, the source chain Relayer synchronizes the block header information to the Poly Chain, and then the Poly Chain synchronizes the block header information to the target chain Relayer, and the target chain Relayer transfers the verification information to the target chain, then executes block header verification on the target chain and executes the user’s expected transaction.
The following are the specific addresses involved in this attack:
The Root Cause of the Attack
1. The source chain did not check the initiated cross-chain operation.
2. The target chain did not check the parsed target call contract and call parameters.
3. The owner of the `EthCrossChainData` contract is `EthCrossChainManager`.
4. `bytes4(keccak256(abi.encodePacked(_method,“(bytes,bytes,uint64)”)))` can be collided by hash.
The Details of the Attack
Poly Network deploys smart contracts on each chain for cross-chain interoperability (analysis takes the smart contract deployed on Ethereum as an example), among which the `EthCrossChainManager` contract is used to verify the block header synchronized by the Poly Chain to confirm the cross-chain The authenticity of the information. The `EthCrossChainData` contract is used to store cross-chain data, and the public key of the relay chain validator (ie Keeper) is also stored in this contract. `LockProxy` is used for asset management.
In this attack, the attacker takes two steps to complete the attack.
First, the attacker initiated a cross-chain transaction by calling the `crossChain` function on other chains to construct data.
EthCrossChainManager.crossChain
It is clear from the figure above that this function is only used to help users construct `makeTxParam` and store the constructed hash for subsequent verification. It does not impose any restrictions on the cross-chain operation parameters passed in by the user, so the attacker can completely synchronize the data to the Poly Chain by the Relayer by constructing any data it wants to construct, and synchronize it to the Ethereum Relayer through the Poly Chain.
Then the Relayer on Ethereum verifies the authenticity of the cross-chain information by calling the `verifyHeaderAndExecuteTx` function in the `EthCrossChainManager` contract to submit the block header information.
EthCrossChainManager.verifyHeaderAndExecuteTx
From the above code, we can see that it first deserializes the block header to resolve the specific information that needs to be verified. The `getCurEpochConPubKeyBytes` function is then called to get the Keeper public key from the `EthCrossChainData` contract, and the Keeper address is obtained through the `deserializeKeepers` function.
Next, the `ECCUtils.verifySig` will be used to verify whether the signature is Keeper or not. From the following code, we can find that the `verifySig` function will cut out the signer’s `v r s`. And get the signer address through the `ecrecover` interface, then call the `containMAddresses` function to compare whether the signer is a Keeper’s or not. The check can be passed as long as the number of Keeper signatures meets the requirement, which is `n-(n-1) / 3)` passed in by the ‘EthCrossChainManager’ contract.
The signature is then verified by the Eccutils. merkleProve, which can be passed by normal cross-chain operations. The transaction is then checked for duplication and the validated data is stored. Just make sure not to submit repeatedly.
Last but not least, it executes the constructed data through an internal call to the _executeCrossChainTx function.
We can see that the _executeCrossChainTx function does not check the _toContract, _method and other parameters, and directly executes the transaction in the way of _toContract.call.
From the data on the chain, the owner of the `EthCrossChainData` contract is the `EthCrossChainManager` contract, and previously we knew that the public key of the relay chain validator (i.e. Keeper) exists in the `EthCrossChainData` contract, and this contract exists in `putCurEpochConPubKeyBytes ` function which can directly modify the Keeper public key.
After the above analysis, the result is very clear. The attacker only needs to initiate a cross-chain operation transaction normally through `crossChain` on other chains. The purpose of this transaction is to call the `putCurEpochConPubKeyBytes` function of the `EthCrossChainData` contract to modify the Keeper role. Then, through the normal cross-chain process, Keeper parses the target contract and call parameters of the user request and constructs a new transaction to submit to Ethereum. This is essentially just a normal cross-chain operation, so it can directly pass the Keeper check and Merkel root check. Finally, the operation of modifying the Keeper was successfully executed.
But notice that the `putCurEpochConPubKeyBytes` function is defined as
function putCurEpochConPubKeyBytes(bytes calldata curEpochPkBytes) external returns (bool);
And the execution of the `_executeCrossChainTx` function is defined as
abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, “(bytes,bytes,uint64)”)))
Under normal circumstances, we can know that the `_method` passed in the function signature of these two functions is `putCurEpochConPubKeyBytes`, which must be completely different, so the `_toContract.call` cannot theoretically call the `putCurEpochConPubKeyBytes` function.
But `_method` is controllable by the attacker, it is completely possible to enumerate various character combinations to obtain the same function signature as calling the `putCurEpochConPubKeyBytes` function, which requires only the first 4 bytes to be enumerated. The SlowMist security team also tried hash collision, as shown below:
It can be seen that the first four bytes are consistent with the `putCurEpochConPubKeyBytes` function
So far we have recovered the attacker’s attack details. By analyzing the data on the chain, we can find that the attacker replaced Keeper with 0xA87fB85A93Ca072Cd4e5F0D4f178Bc831Df8a00B
Finally, the attacker only needs to sign with the replaced Keeper address to transfer the assets under management by calling the `LockProxy` contract through all checks and executions.
The Process of the Attack
1. The attacker carefully constructs an operation on the source chain to modify the Keeper of the target chain.
2. Submit data in the target chain normally using the official Relayer and replace Keeper.
3. The attacker uses the replaced Keeper address to sign the operation and submits it to ‘EthCrossChainManager’ for verification.
4. Verify that the Keeper is the address that has been replaced by the attacker. If yes, transfer the asset to the address specified by the attacker.
5. Profit and leave.
The Analysis Process of MistTrack
According to the analysis and statistics of the SlowMist AML team, the total loss of this attack exceeded 610 million U.S. dollars! The details are as follows:
The Analysis of Capital Flow:
Analysis of the MistTrack anti-money laundering tracking system of SlowMist AML found that the attacker’s initial source of funds was Monero (XMR). Then it changed to BNB/ETH/MATIC and other currencies on the exchange and withdrew the coins to 3 addresses. Soon after, they launched attacks on 3 chains.
Event sorting: (As of 05:00 UTC August 11)
On BSC: Hacker address 1, the hacker added nearly 120 million U.S. dollars (including about 32.1 million BUSD and about 87.6 million USDC) of liquidity to the Curve fork project Ellipsis Finance. Currently, the market is still making no more transactions.
On Polygon: Hacker address 2, there is no more transactions in funds.
On Ethereum:
1) Hacker address 3, there was only one transaction that transferred out 13.37 ETH to address 0xf8b5c45c6388c9ee12546061786026aaeaa4b682
2) After the hacker added more than 97.06 million U.S. dollars (including 670,000 DAI and 96.38 million USDC) liquidity to Curve , the liquidity was canceled and 96.38 million USDC and 670,000 DAI were exchanged for 96.94 million DAI. The funds remained at address 3. Currently, 33.43 million USDT has been frozen by Tether.
Q&A:
Q: Why can the keeper be replaced successfully, and the contract code is not authenticated?
A: The `eccd` contract is authenticated, and only the owner is allowed to call `putCurEpochConPubKeyBytes` to change the keeper. Because the owner of the `eccd` contract is `eccm`, the value of the keeper can be changed through `eccm`.
Q: Why is it possible to replace a Keeper’s transaction with a signature transaction?
A: Verified based on toContract, the original keeper may have signed it as a normal cross-chain transaction, but it is a transaction to replace the keeper.
Q: Why can the attacker bypass the limitation of the code bytes4(keccak256(abi.encodePacked(_method, “(bytes,bytes,uint64)”))) and then execute the putCurEpochConPubKeyBytes(bytes) function?
A: The function signature uses keccak-256 for hash, and then takes the previous 4bytes. In this case, it is easier to be collided by hash.
Q: How is the hacker’s keeper replacement transaction signed by the old keepers?
A: Keepers is a chain Replayer that will sign all cross-chain requests of normal users. When a user initiates a cross-chain transaction on BSC, keepers will parse the target contract and call parameters requested by the user, construct a new transaction and submit it to Ethereum, and use the eccm contract to call the target contained in the user transaction on Ethereum contract. The hacker’s replacement of the keeper’s transaction is essentially a normal cross-chain transaction, except that the target contract called is the eccd contract, and the calling parameter is to replace the keeper, so it can be signed normally.
Summary
This attack is mainly because the keeper of the EthCrossChainData contract can be modified by the EthCrossChainManager contract, and the verifyHeaderAndExecuteTx function of the EthCrossChainManager contract can execute the data passed in by the user through the _executeCrossChainTx function. Therefore, the attacker uses this function to pass in carefully constructed data to modify the address specified by the attacker by the keeper of the EthCrossChainData contract. It is not the case that this event occurred due to the leakage of the keeper’s private key. At present, with the efforts of many parties, hackers have begun to return funds one after another.
SlowMist AML’s MistTrack anti-money laundering tracking system will continue to monitor the transfer of stolen funds, block all wallet addresses controlled by attackers, and remind exchanges and wallets to strengthen address monitoring to prevent related malicious funds from flowing into the platform. In addition, special thanks to the teams such as Hoo, Poly Network, Huobi ZLabs, ChainNews, WePiggy, TokenPocket, Bibox, OkLink and many individual partners for synchronizing relevant attacker information with the SlowMist security team on time under the premise of compliance, and buying valuable time for tracking attacker.
At present, with the efforts of many parties, hackers have begun to return funds one after another.