28

BCH 硬分叉背后:一场预谋已久的真实攻击

 4 years ago
source link: https://www.tuicool.com/articles/immm2qe
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.

5 月 15 日 BCH 升级遭到攻击,慢雾安全团队及时跟进,并在社区里注意到相关分析工作,通过交流将此分析文完整转载于此。这是一场真实攻击,从行为上分析来看确实预谋已久,但 BCH 响应很及时,成功化解了一场安全危机。

YFr2uiU.jpg!web

BCH 的 5 月 15日 升级遭到攻击,导致节点报出 too many sigops 错误。经分析,攻击载荷为一个精确构造的 P2SH Transaction,利用了 BCH 去年 11 月升级引入的 OP_CHECKDATASIG 操作码。

攻击导致了矿工节点无法打包,BCH 方面通过类似于空块攻击的方式,紧急挖出十个空块以触发滚动检查点保证升级。攻击发生约 1 小时后,BCH 矿池上线紧急修复后的代码成功继续出块。

不过同时也有人观察到,在 582698 区块高度,有矿工挖出了哈希结尾为 6bf418af 的区块,大小 139369 字节。但随后该区块被 10 分钟后 BTC.top 挖出的哈希结尾为 449e2bb4 区块所重组。或许是一次误伤,不过可见 BCH 对于升级防守之严密。

攻击原理分析

攻击载荷

捕捉到的攻击载荷 TXID 为

4c83ab55623633c86ec711b3d68ccdea506b228178ff1533f287ab744b006c44

其内容点击阅读原文查看。

该攻击载荷由 1334 个 Input 构成,每一个 Input 均是 P2SH 格式。

其内容为

OP_FALSE OP_IF OP_CHECKDATASIG OP_CHECKDATASIG OP_CHECKDATASIG OP_CHECKDATASIG OP_CHECKDATASIG OP_CHECKDATASIG OP_CHECKDATASIG OP_CHECKDATASIG OP_CHECKDATASIG OP_CHECKDATASIG OP_CHECKDATASIG OP_CHECKDATASIG OP_CHECKDATASIG OP_CHECKDATASIG OP_14 OP_CHECKDATASIG OP_ENDIF OP_TRUE

可见其中包含 15 个 OP_CHECKDATASIG

该攻击载荷利用了一个 CORE 曾经帮 ABC 修复,但未完全修复的漏洞,制造了组块和验证之间的差异,从而导致矿工组出的区块不被节点接受。

漏洞背景

OP_CHECKDATASIG 是一种椭圆曲线签名校验指令(SigOP),这类指令由于需要进行椭圆曲线运算,执行开销远高于其他指令。因此在节点代码中,对于这类指令的数量做出了限制,以避免拒绝服务攻击。

相关代码位置:

https://github.com/Bitcoin-ABC/bitcoin-abc/blob/f27da0752c0a3b7382df54a65ca3cf1c3629aad4/src/consensus/consensus.h#L27

static const uint64_t MAX_TX_SIGOPS_COUNT = 20000;

即,单个 Transaction 中 SigOP 的数量不能超过 20000。

漏洞原理

细心的话,你已经发现了,攻击载荷的 SigOP 数量为 1334 * 15 = 20010,这个攻击载荷 TX 会被节点拒绝,报错即是 too many sigops,这是导致节点拒绝包含该 TX 的区块的原因。

相关代码位置:

https://github.com/Bitcoin-ABC/bitcoin-abc/blob/f27da0752c0a3b7382df54a65ca3cf1c3629aad4/src/validation.cpp#L1936

if (nSigOpsCount > nMaxSigOpsCount) { 
  return state.DoS(100, error("ConnectBlock(): too many sigops"),
                       REJECT_INVALID, "bad-blk-sigops");
}

然而在组块时为什么没有拒绝这个 TX 呢?我们可以在 Bitcoin-ABC 的补丁中发现端倪。

补丁位置: https://reviews.bitcoinabc.org/D3053

相关代码位置:

https://github.com/Bitcoin-ABC/bitcoin-abc/blob/f27da0752c0a3b7382df54a65ca3cf1c3629aad4/src/validation.cpp#L592

// 原代码

int64_t nSigOpsCount = GetTransactionSigOpCount(tx, view, STANDARD_SCRIPT_VERIFY_FLAGS);



// 补丁代码

int64_t nSigOpsCount = GetTransactionSigOpCount(tx, view, STANDARD_CHECKDATASIG_VERIFY_FLAGS);

所在的函数为:AcceptToMemoryPoolWorker

可见原代码组块过程中在计算 Transaction 中的 SigOP 数量时,错误地使用了 STANDARD_SCRIPT_VERIFY_FLAGS,而非 STANDARD_CHECKDATASIG_VERIFY_FLAGS。

这两个标志有什么区别呢?

在 policy 中我们可以找到他们。

相关代码位置:

https://github.com/Bitcoin-ABC/bitcoin-abc/blob/f27da0752c0a3b7382df54a65ca3cf1c3629aad4/src/policy/policy.h#L108

static const uint32_t STANDARD_CHECKDATASIG_VERIFY_FLAGS =
    STANDARD_SCRIPT_VERIFY_FLAGS | SCRIPT_ENABLE_CHECKDATASIG;

所以我们可以见到,当仅使用了 STANDARD_SCRIPT_VERIFY_FLAGS 时,计算脚本中 SigOP 数量时,是不包含 OP_CHECKDATASIG 的。所以这个包含 20010 个 SigOP 的攻击载荷,在组块时,统计出来的 SigOP 数量为零。

因此,攻击载荷会在矿工组块的时候被包含进区块中,然而,由于其他代码正确地统计了 SigOP,节点会拒绝该区块,这导致了 BCH 无法出块。

总结

攻击者利用了 BCH 引入 OP_CHECKDATASIG 时产生的,又未完全修复的漏洞,巧妙地构造了攻击载荷。攻击者应该高度了解客户端代码,并熟悉 OP_CHECKDATASIG 漏洞。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK