0

SharkTeam:攻击者选择高度为110217401的区块到底有什么讲究?

 1 year ago
source link: https://www.tuoniaox.com/news/p-548163.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

摘要:

高保真还原BNBChain跨链攻击实现原理

BNBChain跨链桥攻击事件已过去10天了,网上的分析和讨论也非常多,大家对漏洞的产生和修复非常关心。

但是,我们发现99%的开发者只理解漏洞产生是由于Cosmos SDK中IAVL+树叶子节点的Hash算法存在漏洞,但并不知道攻击者到底是如何构造payload(inputdata)的,实现这样的攻击的难度到底有多大,选择很久之前的这个区块高度(110217401)到底是什么原因?

带着这些问题,SharkTeam高保真还原了黑客的漏洞挖掘过程和payload构造逻辑,让我们来一探究竟!

一、交易分析

txHash:0xebf83628ba893d35b496121fb8201666b8e09f3cbadf0e269162baa72efe3b8b

image.png

访问的合约是BNBChain中的CrossChain合约,地址是0x2000,调用的函数以及参数如下:

image.png

参数解析:

1. payload:有效负载,是跨链交易的包信息,其中包含了调用BNB转账的函数及其参数,长度为147bytes。

2. proof:跨链交易的包的Merkle证明,包含了IVAL树的范围证明以及MultiStore存储的简单Merkle树证明,长度为999bytes。

3. height:区块高度,读取跨链交易的来源链高度为height的区块中的appHash,实际是前一个区块的appHash,用于验证MultiStore存储的简单Merkle树证明。

4. packageSequence:跨链交易的包序列号,其特性类似于区块的Nonce。

5. channelId:跨链通道ID,指明跨链交易的来源链与目标链。这里,channelId=2表示从BC链到BSC链的跨链转账交易。

二、合约分析

CrossChain合约如下:

image.png

我们通过整个执行过程,将handlePackage函数分为6个关键函数,下面依次分析这6个关键函数的逻辑、参数以及返回值。

2.1 validateMerkleProof函数

该函数的功能就是验证Merkle证明,函数名称和参数定义如下:

image.png

传递的参数如下:

  "appHash": "0x72cda827a83531ca0fd7ac917a6b65649719aab0836722caafe0603147a52318",

  "storeName": "ibc",

  "key": "0x00000100380200000000010dd85c",

  "value": "0x000000000000000000000000000000000000000000000000000000000000000000f870a0424e4200000000000000000000000000000000000000000000000000000000009400000000000000000000000000000000000000008ad3c21bcecceda100000094489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec94489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec846553f100",

  "proof": "0x0a8d020a066961766c3a76120e00000100380200000000010dd85c1af201f0010aed010a2b0802100318b091c73422200c10f902d266c238a4ca9e26fa9bc36483cd3ebee4e263012f5e7f40c22ee4d20a4d0801100218b091c7342220e4fd47bffd1c06e67edad92b2bf9ca63631978676288a2aa99f95c459436ef632a20da657c1ffb86c684eb3e265361ef0fa4f9dfa670b45f9f91c5eb6ad84b21a4d112001a370a0e0000010038020000000000000002122011056c6919f02d966991c10721684a8d1542e44003f9ffb47032c18995d4ac7f18b091c7341a340a0e00000100380200000000010dd85c12202c3a561458f8527b002b5ec3cab2d308662798d6245d4588a4e6a80ebdfe30ac18010ad4050a0a6d756c746973746f726512036962631ac005be050abb050a110a066f7261636c6512070a0508b891c7340a0f0a046d61696e12070a0508b891c7340a350a08736c617368696e6712290a2708b891c7341220c8ccf341e6e695e7e1cb0ce4bf347eea0cc16947d8b4e934ec400b57c59d6f860a380a0b61746f6d69635f7377617012290a2708b891c734122042d4ecc9468f71a70288a95d46564bfcaf2c9f811051dcc5593dbef152976b010a110a0662726964676512070a0508b891c7340a300a0364657812290a2708b891c73412201773be443c27f61075cecdc050ce22eb4990c54679089e90afdc4e0e88182a230a2f0a02736312290a2708b891c7341220df7a0484b7244f76861b1642cfb7a61d923794bd2e076c8dbd05fc4ee29f3a670a330a06746f6b656e7312290a2708b891c734122064958c2f76fec1fa5d1828296e51264c259fa264f499724795a740f48fc4731b0a320a057374616b6512290a2708b891c734122015d2c302143bdf029d58fe381cc3b54cedf77ecb8834dfc5dc3e1555d68f19ab0a330a06706172616d7312290a2708b891c734122050abddcb7c115123a5a4247613ab39e6ba935a3d4f4b9123c4fedfa0895c040a0a300a0361636312290a2708b891c734122079fb5aecc4a9b87e56231103affa5e515a1bdf3d0366490a73e087980b7f1f260a0e0a0376616c12070a0508b891c7340a300a0369626312290a2708b891c7341220e09159530585455058cf1785f411ea44230f39334e6e0f6a3c54dbf069df2b620a300a03676f7612290a2708b891c7341220db85ddd37470983b14186e975a175dfb0bf301b43de685ced0aef18d28b4e0420a320a05706169727312290a2708b891c7341220a78b556bc9e73d86b4c63ceaf146db71b12ac80e4c10dd0ce6eb09c99b0c7cfe0a360a0974696d655f6c6f636b12290a2708b891c73412204775dbe01d41cab018c21ba5c2af94720e4d7119baf693670e70a40ba2a52143"

以上参数解读如下:

(1)appHash通过调用TendermintLightClient合约中的getAppHash函数来读取的高度为height的AppHash,getAppHash函数如下:

image.png

状态变量lightClientConsensusStates是由跨链中继器Relayer调用syncTendermintHeader函数同步每一个区块头部信息的时候进行更新的,其中保存了每一个BC链中区块的appHash。

image.png

AppHash是区块头部的一个字段,代表前一个区块执行完成后的应用状态散列值。

image.png
image.png
image.png

摘自:《区块链架构与实现:Cosmos详解》

由此可见,参数appHash是BC链高度为110217400的区块的AppHash(参数height为110217401),其值为0x72cda827a83531ca0fd7ac917a6b65649719aab0836722caafe0603147a52318

(2)参数storeName=”ibc”,是合约定义的常量,转换成十六进制为0x696263

(3)参数key是调用generateKey函数生成的,代码如下:

image.png

参数key的值长度为14bytes,由两部分组成:

前6 bytes由通道ID,即channelId生成,其值为0x000001003802,按顺序包含:

长度(/字节)

来源链ID

0x0001

目标链ID

0x0038

后8 bytes是包序列号packageSequence,即17684572=0x00000000010dd85c

所以,这里key=0x00000100380200000000010dd85c,length=14=0x0e

(4)参数payloadLocal就是交易InputData中的payload,长度为147bytes

(5)参数proofLocal就是交易InputData中的proof,长度为999bytes

validateMerkleProof函数的实现代码如下:

image.png
image.png

该函数调用了0x65地址的合约对proof进行验证,参数如下:

(1)input=0x00000000000000000000000000000000000000000000000000000000000005086962630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000100380200000000010dd85c0000000000000000000000000000000000000000000000000000000000000093000000000000000000000000000000000000000000000000000000000000000000f870a0424e4200000000000000000000000000000000000000000000000000000000009400000000000000000000000000000000000000008ad3c21bcecceda100000094489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec94489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec846553f10072cda827a83531ca0fd7ac917a6b65649719aab0836722caafe0603147a523180a8d020a066961766c3a76120e00000100380200000000010dd85c1af201f0010aed010a2b0802100318b091c73422200c10f902d266c238a4ca9e26fa9bc36483cd3ebee4e263012f5e7f40c22ee4d20a4d0801100218b091c7342220e4fd47bffd1c06e67edad92b2bf9ca63631978676288a2aa99f95c459436ef632a20da657c1ffb86c684eb3e265361ef0fa4f9dfa670b45f9f91c5eb6ad84b21a4d112001a370a0e0000010038020000000000000002122011056c6919f02d966991c10721684a8d1542e44003f9ffb47032c18995d4ac7f18b091c7341a340a0e00000100380200000000010dd85c12202c3a561458f8527b002b5ec3cab2d308662798d6245d4588a4e6a80ebdfe30ac18010ad4050a0a6d756c746973746f726512036962631ac005be050abb050a110a066f7261636c6512070a0508b891c7340a0f0a046d61696e12070a0508b891c7340a350a08736c617368696e6712290a2708b891c7341220c8ccf341e6e695e7e1cb0ce4bf347eea0cc16947d8b4e934ec400b57c59d6f860a380a0b61746f6d69635f7377617012290a2708b891c734122042d4ecc9468f71a70288a95d46564bfcaf2c9f811051dcc5593dbef152976b010a110a0662726964676512070a0508b891c7340a300a0364657812290a2708b891c73412201773be443c27f61075cecdc050ce22eb4990c54679089e90afdc4e0e88182a230a2f0a02736312290a2708b891c7341220df7a0484b7244f76861b1642cfb7a61d923794bd2e076c8dbd05fc4ee29f3a670a330a06746f6b656e7312290a2708b891c734122064958c2f76fec1fa5d1828296e51264c259fa264f499724795a740f48fc4731b0a320a057374616b6512290a2708b891c734122015d2c302143bdf029d58fe381cc3b54cedf77ecb8834dfc5dc3e1555d68f19ab0a330a06706172616d7312290a2708b891c734122050abddcb7c115123a5a4247613ab39e6ba935a3d4f4b9123c4fedfa0895c040a0a300a0361636312290a2708b891c734122079fb5aecc4a9b87e56231103affa5e515a1bdf3d0366490a73e087980b7f1f260a0e0a0376616c12070a0508b891c7340a300a0369626312290a2708b891c7341220e09159530585455058cf1785f411ea44230f39334e6e0f6a3c54dbf069df2b620a300a03676f7612290a2708b891c7341220db85ddd37470983b14186e975a175dfb0bf301b43de685ced0aef18d28b4e0420a320a05706169727312290a2708b891c7341220a78b556bc9e73d86b4c63ceaf146db71b12ac80e4c10dd0ce6eb09c99b0c7cfe0a360a0974696d655f6c6f636b12290a2708b891c73412204775dbe01d41cab018c21ba5c2af94720e4d7119baf693670e70a40ba2a52143

(2)length=1260=0x508=32+1228

input的实际长度为1260bytes,但实际的参数长度是1228,之所以length=1260,是因为input的第一个字节中保存了实际参数的长度1228=0x508.

image.png

结果输出到result中,其值为0x01。最终,proof验证通过,返回true。

image.png

2.2 getSubmitter函数

image.png

状态变量submitters由跨链中继器Relayer调用syncTendermintHeader函数同步每一个区块头部信息的时候进行更新。其值为msg.sender,即中继器Relayer的地址:

0xb005741528b86f5952469d80a8614591e3c5b632。

image.png

2.3 decodePayloadHeader函数

image.png

payload=0x000000000000000000000000000000000000000000000000000000000000000000f870a0424e4200000000000000000000000000000000000000000000000000000000009400000000000000000000000000000000000000008ad3c21bcecceda100000094489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec94489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec846553f100

其中,包括:

l packageType=0x0,长度为1byte

l relayFee=0x0,长度为32bytes

l package=0xf870a0424e4200000000000000000000000000000000000000000000000000000000009400000000000000000000000000000000000000008ad3c21bcecceda100000094489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec94489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec846553f100,长度为114bytes

2.4 handleSynPackage函数

image.png

这里的参数如下:

l channelId=0x02

l msgBytes=0xf870a0424e4200000000000000000000000000000000000000000000000000000000009400000000000000000000000000000000000000008ad3c21bcecceda100000094489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec94489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec846553f100

其中,handleTransferInSynPackage函数如下:

image.png
image.png
image.png

2.5 addReward函数

该函数的功能是在跨链业务完成后为中继器Relayer分发奖励金。

3公链分析

在合约中验证Merkle证明的时候调用了0x65地址处的预编译合约。

image.png

我们查看了VM中0x65处的预编译合约,如下:

image.png

预编译合约执行入口如下:

image.png

参数解析:

l p:地址0x65处的iavlMerkleProofValidate

l input:由validateMerkleProof函数传递的input,长度是32+1228=1260bytes,前32bytes是input实际参数的长度,即1228=0x508

input=0x00000000000000000000000000000000000000000000000000000000000005086962630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000100380200000000010dd85c0000000000000000000000000000000000000000000000000000000000000093000000000000000000000000000000000000000000000000000000000000000000f870a0424e4200000000000000000000000000000000000000000000000000000000009400000000000000000000000000000000000000008ad3c21bcecceda100000094489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec94489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec846553f10072cda827a83531ca0fd7ac917a6b65649719aab0836722caafe0603147a523180a8d020a066961766c3a76120e00000100380200000000010dd85c1af201f0010aed010a2b0802100318b091c73422200c10f902d266c238a4ca9e26fa9bc36483cd3ebee4e263012f5e7f40c22ee4d20a4d0801100218b091c7342220e4fd47bffd1c06e67edad92b2bf9ca63631978676288a2aa99f95c459436ef632a20da657c1ffb86c684eb3e265361ef0fa4f9dfa670b45f9f91c5eb6ad84b21a4d112001a370a0e0000010038020000000000000002122011056c6919f02d966991c10721684a8d1542e44003f9ffb47032c18995d4ac7f18b091c7341a340a0e00000100380200000000010dd85c12202c3a561458f8527b002b5ec3cab2d308662798d6245d4588a4e6a80ebdfe30ac18010ad4050a0a6d756c746973746f726512036962631ac005be050abb050a110a066f7261636c6512070a0508b891c7340a0f0a046d61696e12070a0508b891c7340a350a08736c617368696e6712290a2708b891c7341220c8ccf341e6e695e7e1cb0ce4bf347eea0cc16947d8b4e934ec400b57c59d6f860a380a0b61746f6d69635f7377617012290a2708b891c734122042d4ecc9468f71a70288a95d46564bfcaf2c9f811051dcc5593dbef152976b010a110a0662726964676512070a0508b891c7340a300a0364657812290a2708b891c73412201773be443c27f61075cecdc050ce22eb4990c54679089e90afdc4e0e88182a230a2f0a02736312290a2708b891c7341220df7a0484b7244f76861b1642cfb7a61d923794bd2e076c8dbd05fc4ee29f3a670a330a06746f6b656e7312290a2708b891c734122064958c2f76fec1fa5d1828296e51264c259fa264f499724795a740f48fc4731b0a320a057374616b6512290a2708b891c734122015d2c302143bdf029d58fe381cc3b54cedf77ecb8834dfc5dc3e1555d68f19ab0a330a06706172616d7312290a2708b891c734122050abddcb7c115123a5a4247613ab39e6ba935a3d4f4b9123c4fedfa0895c040a0a300a0361636312290a2708b891c734122079fb5aecc4a9b87e56231103affa5e515a1bdf3d0366490a73e087980b7f1f260a0e0a0376616c12070a0508b891c7340a300a0369626312290a2708b891c7341220e09159530585455058cf1785f411ea44230f39334e6e0f6a3c54dbf069df2b620a300a03676f7612290a2708b891c7341220db85ddd37470983b14186e975a175dfb0bf301b43de685ced0aef18d28b4e0420a320a05706169727312290a2708b891c7341220a78b556bc9e73d86b4c63ceaf146db71b12ac80e4c10dd0ce6eb09c99b0c7cfe0a360a0974696d655f6c6f636b12290a2708b891c73412204775dbe01d41cab018c21ba5c2af94720e4d7119baf693670e70a40ba2a52143

iavlMerkleProofValidate的Run方法如下:

image.png

解析出的kvmp结构如下:

image.png

然后,调用kvmp的validate方法:

image.png

其中prt和kp的结构如下:

image.png

调用prt的VerifyValue方法:

image.png

其中poz结构如下:

image.png

poz包含了2个Op,即IAVLValueOP和MultiStoreOp

调用poz的Verify方法,如下:

image.png

调用op中的Run方法。poz中有2个op,即IVALValueOp和MultiStoreOp。因此,这里先后调用了IVALValueOp和MultiStoreOp的Run方法,即对Merkle证明进行验证。

3.1 IVALValueOp验证

首先调用的是IVALValueOp的Run方法,如下:

image.png

这里的参数args只有一个元素args[0],是参数payload,长度为147字节。返回的是Merkle证明中的rootHash。

这里的op如下:

image.png

其中的Proof类型是RangeProof,结构定义如下:

image.png

RangeProof结构字段说明:

image.png
image.png
image.png

因此,proofInnerNode结构中的Left和Right必须有一个是nil,而另外一个不是nil。

以下面的IAVL+树为例:

image.png

说明:字母代表节点,数字代表key,公有8个叶子节点。

根据IAVL+树的特性,对于其中的任意中间节点node,满足条件:

node.key

比如,若要构造节点J的RangeProof,证明路径如下:

image.png

Key=2(节点J)时,PathToLeaf为{A, B, E}。其中,每一个节点的Left和Right如下:

l Left=nil,A.Right=C.key

l B.Left=D.key, B.Right=nil

l E.Left=nil, E,Right=K.key(E.Left实际上是节点J)

这里Proof的值如下:

image.png

Proof中,LeftToPath有2个LeafToPath,0个InnerNodes,2个Leaves。同时还发现,LeftToPath[1]的Left和Right都不是nil。另外,经计算,发现LeftToPath[1].Right实际就是leaves[1],即参数传入的key,整个叶子的Hash值为:

0xda657c1ffb86c684eb3e265361ef0fa4f9dfa670b45f9f91c5eb6ad84b21a4d1

Run方法中调用了3个重要方法:

(1)RangeProof的ComputeRootHash方法,即根据proof计算Merkle证明的rootHash

image.png
image.png

其中pathWithLeaf的computeRootHash方法如下:

执行到这一步时,pin的值如下:

接下来调用proofInnerNode的Hash方法,如下:

这里的Hash方法是根据pin的Left和Right计算中间节点的Hash。根据上面介绍的,这里Left和Right必须一个是nil,另外一个不是nil。但实际代码中,在Left不是nil的时候,并没有要求Right是nil,即这种情况下的Right的值不参与Hash值的计算,也就对Hash没有影响。

这里的Hash实际是有pin.Left和leaves[0]计算出来的,pin的Right应该是nil的,但实际不是。但也因此,计算的Hash是正确的。

从整个proof,我们可以构造出相应的IAVL+树,如下:

LeftPath[1].Right应该是nil,实际是leaves[0],攻击者将其改为了leaves[1],由于Hash函数计算漏洞,这并不影响Hash的计算。

将计算的rootHash添加到proof的rootHash字段,并返回rootHash。

(2)RangeProof的Verify方法

该方法的功能是验证上一步中是否计算并设置了proof的rootHash字段,验证通过后,将proof的rootVerified设置为true。

(3)RangeProof的VerifyItem方法

该方法用于验证参数中提供的key和payload在proof的leaves中,即参数中的跨链包是MerkleProof的叶子节点,并且其ValueHash与MerkleProof中相应叶子节点的ValueHash相同。

IAVLValueOp验证了模块的MerkleProof,即验证了从特定功能模块(“ibc”)的IAVL+树根到目标叶子节点的Merkle证明。

3.2 MultiStoreOp验证

根据poz中op的顺序,在完成IAVLValueOp的验证后,就会进行MultiStoreOp的验证。

在执行完IAVLValueOp的Run方法后,其返回值为iavl proof中的rootHash,因此,此时args有原来的payload变为iavlRootHash。然后执行MultiStore的Run方法:

这里,op的如下:

这里执行的是MultiStoreOp的Proof的ComputeRootHash方法:

计算结构以及返回值是appHash。

关于代码中的si.Core.CommitID.Hash,其中si指Proof.StoreInfos中的“ibc”模块。

那么si.Core.CommitID.Hash就是“ibc”模块的IAVL+树根值,即IVALValueOp验证中返回的iavlRootHash。

这一步验证appHash实现了从AppHash到特定功能模块(“ibc”)的IAVL+树根的Merkle证明的验证。

3.3 小结

以上两部分,分别验证了从特定功能模块的IAVL+树根到目标叶子节点的Merkle证明(IAVLValueOp验证)以及从AppHash到特定功能模块的IAVL+树根的Merkle证明(MultiStoreOp验证)。最终,参数提供的证明验证通过,返回0x01,代表true。

4总结

此次安全事件发生的根本原因是BNBChain底层使用的Cosmos SDK中IAVL+树叶子节点的Hash算法存在漏洞。攻击者完全可以从区块链中查询到完整的Merkle树,然后利用漏洞进行篡改,然后生成可以通过验证的Merkle RangeProof,从而实现伪造证明的目的。

叶子节点Hash漏洞并不是在指定的条件下才存在,理论上,任意的IAVL+树都可以伪造出Merkle RangeProof。在伪造证明时,明显越简单的IAVL+树,越容易伪造证明。IAVL+树叶子越多,结构越复杂,伪造证明也就越困难。

此次事件,攻击者选择高度为110217401的区块,原因是这个区块对应的IAVL+树构造更简单,更容易实现伪造证明。这也是攻击者在成功交易之前,有多笔失败交易的原因。

关于我们:SharkTeam的愿景是全面保护Web3世界的安全。团队成员分布在北京、南京、苏州、硅谷,由来自世界各地的经验丰富的安全专业人士和高级研究人员组成,精通区块链和智能合约的底层理论,提供包括智能合约审计、链上分析、应急响应等服务。已与区块链生态系统各个领域的关键参与者,如Polkadot、Moonbeam、polygon、OKC、Huobi Global、imToken、ChainIDE等建立长期合作关系。

欢迎加入鸵鸟区块链Telegram社群

中文社区 https://t.me/tuoniaox

英文社区 https://t.me/tuoniaoGroup


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK