Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >以太坊DAO攻击解决方案代码解析

以太坊DAO攻击解决方案代码解析

作者头像
Tiny熊
发布于 2019-04-28 02:54:26
发布于 2019-04-28 02:54:26
1.2K00
代码可运行
举报
运行总次数:0
代码可运行

虽然 The DAO 攻击发生在2016年,但解除攻击的方案依然值得学习。

区块链本是去中心化架构,在以太坊首次遭遇严重黑客攻击智能合约事件时,采用的解决方案却破坏了去中心化理念。

这里不讨论其是否违背区块链精神,本文重点介绍解决方案的技术实施细节。方案中涉及网络隔离技术和矿工共识投票技术。且只是从软件上处理,未破坏共识协议。解决方案的成功实施,为区块链分叉提供了实操经验,值得公链开发者学习。

什么是 The DAO 攻击

简单地讲,在2016年4月30日开始,一个名为“The DAO”的初创团队,在以太坊上通过智能合约进行ICO众筹。28天时间,筹得1.5亿美元,成为历史上最大的众筹项目。

THE DAO创始人之一Stephan TualTual在6月12日宣布,他们发现了软件中存在的“递归调用漏洞”问题。 不幸的是,在程序员修复这一漏洞及其他问题的期间,一个不知名的黑客开始利用这一途径收集THE DAO代币销售中所得的以太币。6月18日,黑客成功挖到超过360万个以太币,并投入到一个DAO子组织中,这个组织和THE DAO有着同样的结构。

THE DAO持有近15%的以太币总数,因此THE DAO这次的问题对以太坊网络及其加密币都产生了负面影响。

6月17日,以太坊基金会的Vitalik Buterin更新一项重要报告,他表示,DAO正在遭到攻击,不过他已经研究出了解决方案:

现在提出了软件分叉解决方案,通过这种软件分叉,任何调用代码或委托调用的交易——借助代码hash0x7278d050619a624f84f51987149ddb439cdaadfba5966f7cfaea7ad44340a4ba(也就是DAO和子DAO)来减少账户余额——都会视为无效……

最终因为社交的不同意见,最终以太坊分裂出支持继续维持原状的以太经典 ETC,同意软件分叉解决方案的在以太坊当前网络实施。

以上内容整理自文章The DAO 攻击[1]。

解决方案

因为投资者已经将以太币投入了 The DAO 合约或者其子合约中,在攻击后无法立刻撤回。 需要让投资者快速撤回投资,且能封锁黑客转移资产。

V神公布的解决方案是,在程序中植入转移合约以太币代码,让矿工选择是否支持分叉。在分叉点到达时则将 The DAO 和其子合约中的以太币转移到一个新的安全的可取款合约中。全部转移后,原投资者则可以直接从取款合约中快速的拿回以太币。取款合约在讨论方案时,已经部署到主网。合约地址是[0xbf4ed7b27f1d666546e30d74d50d173d20bca754][WithdrawDAO]。

取款合约代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Deployed on mainnet at 0xbf4ed7b27f1d666546e30d74d50d173d20bca754

contract DAO {
    function balanceOf(address addr) returns (uint);
    function transferFrom(address from, address to, uint balance) returns (bool);
    uint public totalSupply;
}

contract WithdrawDAO {
    DAO constant public mainDAO = DAO(0xbb9bc244d798123fde783fcc1c72d3bb8c189413);
    address public trustee = 0xda4a4626d3e16e094de3225a751aab7128e96526;

    function withdraw(){
        uint balance = mainDAO.balanceOf(msg.sender);

        if (!mainDAO.transferFrom(msg.sender, this, balance) || !msg.sender.send(balance))
            throw;
    }

    function trusteeWithdraw() {
        trustee.send((this.balance + mainDAO.balanceOf(this)) - mainDAO.totalSupply());
    }
}

同时,为照顾两个阵营,软件提供硬分叉开关,选择权则交给社区。支持分叉的矿工会在X区块到X+9区块出块时,在区块extradata字段中写入0x64616f2d686172642d666f726b(“dao-hard-fork”的十六进制数)。从分叉点开始,如果连续10个区块均有硬分叉投票,则表示硬分叉成功。

矿工投票与区块头校验

首先,选择权交给社区。因此是否同意硬分叉,可通过参数进行选择。但是在当前版本中,社区已完成硬分叉,所以已移除开关类代码。

当前,主网已默认配置支持DAO分叉,并设定了开始硬分叉高度 1920000,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// params/config.go:38
MainnetChainConfig = &ChainConfig{ 
        DAOForkBlock:        big.NewInt(1920000),
        DAOForkSupport:      true, 
    }

如果矿工支持分叉,则需要在从高度 192000 到 192009,在区块头 extradata 写入指定信息 0x64616f2d686172642d666f726b ,以表示支持硬分叉。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//params/dao.go:28var DAOForkBlockExtra = common.FromHex("0x64616f2d686172642d666f726b")
// params/dao.go:32var DAOForkExtraRange = big.NewInt(10)

支持硬分叉时矿工写入固定的投票信息:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// miner/worker.go:857
if daoBlock := w.config.DAOForkBlock; daoBlock != nil { 
    // 检查是否区块是否仍然属于分叉处理期间:[DAOForkBlock,DAOForkBlock+10)
    limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange)
    if header.Number.Cmp(daoBlock) >= 0 && header.Number.Cmp(limit) < 0 {
        // 如果支持分叉,则覆盖Extra,写入保留的投票信息
        if w.config.DAOForkSupport {
        header.Extra = common.CopyBytes(params.DAOForkBlockExtra)
        } else if bytes.Equal(header.Extra, params.DAOForkBlockExtra) {
            // 如果矿工反对,则不能让其使用保留信息,覆盖它。
        header.Extra = []byte{}  
        }
    }
}

需要连续10个区块的原因是为了防止矿工使用保留信息污染非分叉块和方便轻节点安全同步数据。 同时,所有节点在校验区块头时,必须安全地校验特殊字段信息,校验区块是否属于正确的分叉上。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// consensus/ethash/consensus.go:294 
if err := misc.VerifyDAOHeaderExtraData(chain.Config(), header); err != nil { //❶
    return err
}

// consensus/misc/dao.go:47 
func VerifyDAOHeaderExtraData(config *params.ChainConfig, header *types.Header) error { 
    if config.DAOForkBlock == nil {//❷
        return nil
    }
    limit := new(big.Int).Add(config.DAOForkBlock, params.DAOForkExtraRange) //❸
    if header.Number.Cmp(config.DAOForkBlock) < 0 || header.Number.Cmp(limit) >= 0 {
        return nil
    }
    if config.DAOForkSupport {
        if !bytes.Equal(header.Extra, params.DAOForkBlockExtra) { //❹
            return ErrBadProDAOExtra
        }
    } else {
        if bytes.Equal(header.Extra, params.DAOForkBlockExtra) {//❺
            return ErrBadNoDAOExtra
        }
    }
    // All ok, header has the same extra-data we expect
    return nil
}

•❶ 在校验区块头时增加 DAO 区块头识别校验。•❷ 如果节点未设置分叉点,则不校验。•❸ 确保只需在 DAO 分叉点的10个区块上校验。•❹ 如果节点允许分叉,则要求区块头 Extra 必须符合要求。•❺ 当然,如果节点不允许分叉,则也不能在区块头中加入非分叉链的 Extra 特殊信息。

这种 config.DAOForkBlock 开关,类似于互联网公司产品新功能灰度上线的功能开关。在区块链上,可以先实现功能代码逻辑。至于何时启用,则可以在社区、开发者讨论后,确定最终的开启时间。当然区块链上区块高度等价于时间戳,比如 DAO 分叉点 1920000 也是讨论后敲定。

如何分离网络?

如果分叉后不能快速地分离网络,会导致节点出现奇奇怪怪的问题。长远来说,为针对以后可能出现的分叉,应设计一种通用解决方案,已降低代码噪音。否则,你会发现代码中到处充斥着一些各种梗。但时间又非常紧急,这次的 The DAO 分叉处理是通过特定代码拦截实现。

在我看来,区块链项目不同于其他传统软件,一旦发现严重BUG是非常致命的。在上线后的代码修改,应保持尽可能少和充分测试。非常同意 the dao 的代码处理方式。不必为以后可能的分叉,而做出觉得“很棒”的功能,务实地解决问题才是正道。

不应该让节点同时成为两个阵营的中继点,应分离出两个网络,以让其互不干预。The DAO 硬分叉的处理方式是:节点连接握手后,向对方请求分叉区块头信息。在15秒必须响应,否则断开连接。

代码实现是在eth/handler.go文件中,在消息层进行拦截处理。节点握手后,开始15秒倒计时,一旦倒计时结束,则断开连接。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// eth/handler.go:300
    p.forkDrop = time.AfterFunc(daoChallengeTimeout, func() {
        p.Log().Debug("Timed out DAO fork-check, dropping")
        pm.removePeer(p.id)
    })

在倒计时前,需要向对方索要区块头信息,以进行分叉校验。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// eth/handler.go:297
    if err := p.RequestHeadersByNumber(daoBlock.Uint64(), 1, 0, false); err != nil {
        return err
    }

此时,对方在接收到请求时,如果存在此区块头则返回,否则忽略。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// eth/handler.go:348
    case msg.Code == GetBlockHeadersMsg:  
        var query getBlockHeadersData
        if err := msg.Decode(&query); err != nil {
            return errResp(ErrDecode, "%v: %v", msg, err)
        }
        hashMode := query.Origin.Hash != (common.Hash{})
        first := true
        maxNonCanonical := uint64(100) 
        var (
            bytes   common.StorageSize
            headers []*types.Header
            unknown bool
        )
        //省略一部分 ...
        return p.SendBlockHeaders(headers)

这样,有几种情况出现。根据不同情况分别处理:

1.有返回区块头:

如果返回的区块头不一致,则校验不通过,等待倒计时结束。如果区块头一致,则根据前面提到的校验分叉区块方式检查。校验失败,此直接断开连接,说明已经属于不同分叉。校验通过,则关闭倒计时,完成校验。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// eth/handler.go:465
if p.forkDrop != nil && pm.chainconfig.DAOForkBlock.Cmp(headers[0].Number) == 0 { 
                p.forkDrop.Stop()
                p.forkDrop = nil

                if err := misc.VerifyDAOHeaderExtraData(pm.chainconfig, headers[0]); err != nil {
                    p.Log().Debug("Verified to be on the other side of the DAO fork, dropping")
                    return err
                }
                p.Log().Debug("Verified to be on the same side of the DAO fork")
                return nil
            }

1.没有返回区块头:

如果自己也没有到达分叉高度,则不校验,假定双方在同一个网络。但我自己已经到达分叉高度,则考虑对方的TD是否高于我的分叉块。如果是,则包容,暂时认为属于同一网络。否则,则校验失败。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// eth/handler.go:442 
if len(headers) == 0 && p.forkDrop != nil { 
    verifyDAO := true

    if daoHeader := pm.blockchain.GetHeaderByNumber(pm.chainconfig.DAOForkBlock.Uint64()); daoHeader != nil {
        if _, td := p.Head(); td.Cmp(pm.blockchain.GetTd(daoHeader.Hash(), daoHeader.Number.Uint64())) >= 0 {
            verifyDAO = false
        }
    } 
    if verifyDAO {
        p.Log().Debug("Seems to be on the same side of the DAO fork")
        p.forkDrop.Stop()
        p.forkDrop = nil
        return nil
    }
}

转移资产

上述所做的一切均为安全、稳定的硬分叉,隔离两个网络。硬分叉的目的是,以人为介入的方式拦截攻击者资产。一旦到达分叉点,则立即激活资产转移操作。首先,矿工在挖到分叉点时,需执行转移操作:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

// miner/worker.go:877
func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) {
    // ...
// Create the current work task and check any fork transitions needed
    env := w.current
    if w.config.DAOForkSupport && w.config.DAOForkBlock != nil && w.config.DAOForkBlock.Cmp(header.Number) == 0 {
        misc.ApplyDAOHardFork(env.state)
    }
    // ...
}

其次,任何节点在接收区块,进行本地处理校验时同样需要在分叉点执行:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// core/state_processor.go:66
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) {
    //...
    // Mutate the block and state according to any hard-fork specs
    if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 {
        misc.ApplyDAOHardFork(statedb)
    }
    //...
}

转移资金也是通过取款合约处理。将The DAO 合约包括子合约的资金,全部转移到新合约中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func ApplyDAOHardFork(statedb *state.StateDB) {
    // Retrieve the contract to refund balances into
    if !statedb.Exist(params.DAORefundContract) {
        statedb.CreateAccount(params.DAORefundContract)
    }

    // Move every DAO account and extra-balance account funds into the refund contract
    for _, addr := range params.DAODrainList() {
        statedb.AddBalance(params.DAORefundContract, statedb.GetBalance(addr))
        statedb.SetBalance(addr, new(big.Int))
    }
}

至此,合约资金已全部强制转移到新合约。

References

[1] The DAO 攻击: http://chainb.com/?P=Cont&id=1290

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-04-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 深入浅出区块链技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
从零开发区块链应用(十二)--以太坊余额查询
Block.Header.Root 就是 stateRoot,是一棵 PMT 树,存储了所有账户的当前最新的状态信息,比如账户余额。
Tiny熊
2022/02/22
1.5K0
The DAO后传:以太坊新旧链的平行世界
上次说到,以太坊社区通过硬分叉(hard fork)技术,“夺回”了黑客控制的The DAO的资金,The DAO退款之后也就曲终人散了。事情本该就此归于沉寂,却不曾料到,在金盆洗手之后盆却破了个洞,对黑客的最后一击却匪夷所思地将以太坊硬生生裂变成两个平行世界!
Henry Zhang
2019/04/12
1.5K0
The DAO后传:以太坊新旧链的平行世界
以太坊虚拟机(上篇)
区块链上的虚拟机(Virtual Machine)是指建立在去中心化的区块链上的代码运行环境,目前市面上比较主流的便是以太坊虚拟机(Ethereum Virtual Machine,EVM)和类以太坊虚拟机,它基于Account账户模型将智能合约代码以对外完全隔离的方式在内部运行,实现了图灵完备的智能合约体系,本篇文章将从源码角度对其工作原理进行简要分析~
Al1ex
2021/07/21
1.5K0
以太坊虚拟机(上篇)
软硬兼施,完币归赵-史上最大众筹项目The DAO黯然落幕
为了解决The DAO大量资金被盗的问题,尽管争议颇多,以太坊官方还是推出了针对The DAO的软分叉(soft fork)版本Geth v1.4.8,该版本增加了一些规则以锁定黑客控制的以太币,以便有更多时间留给社区去解决The DAO的问题。在六月底的数据显示,绝大多数矿工都升级了这个版本的软件,眼看着软分叉就要大功告成了。
Henry Zhang
2019/04/12
9610
软硬兼施,完币归赵-史上最大众筹项目The DAO黯然落幕
以太坊挖矿源码:clique算法
上文我们总结了以太坊最主要的共识算法:ethash算法,本文将重点分析以太坊的另一个共识算法:clique。 关键字:clique,共识算法,puppeth,以太坊地址原理,区块校验,认证结点,POA,选举投票,snapshot,Comma-ok断言 clique 以太坊的官方共识算法是ethash算法,这在前文已经有了详细的分析: 它是基于POW的共识机制的,矿工需要通过计算nonce值,会消耗大量算力来匹配target值。 如果在联盟链或者私链的方案里,继续使用ethash就会浪费算力
文彬
2018/05/03
3.2K0
以太坊共识算法
共识算法是区块链项目的核心之一,每一个运行着的区块链都需要一个共识算法来保证出块的有效性和有序性,在以太坊的官方源码中,有两个共识算法—clique和ethash,它们位于以太坊项目的consensus目录下,其中clique是PoA(权威证明,Proof of Authority)共识,它主要用于测试网络,ethash是目前以太坊主网Homestead版本的PoW(工作量证明,Proof of Work)共识算法,用于正式网络。
Al1ex
2021/09/01
4.1K0
以太坊共识算法
​【刘文彬】以太坊RPC机制与API实例
原文链接:醒者呆的博客园,https://www.cnblogs.com/Evsward/p/eth-rpc.html
圆方圆学院
2018/12/24
1.9K0
以太坊区块设计
区块链是由包含交易的区块按照时间先后顺序依次连接起来的数据结构,这种数据结构是一个形象的链表结构,所有数据有序地链接在同一条区块链上,每个区块通过一个hash指针指向前一个区块,hash指针其实是前一个区块头进行SHA256哈希计算得到的,通过这个哈希值,可以唯一的识别一个区块,然后将每个区块连接到其区块头中前一个区块哈希值代币的区块后面,从而构建出一条完整的区块链。
Al1ex
2023/05/26
2.2K0
以太坊区块设计
如何防止以太坊智能合约攻击-源码分析
合约通常要处理 ether,经常会转移 ether 到各种外部用户地址。这些操作需要合约提交外部调用。这些外部调用可能被攻击者劫持,从而强制合约执行进一步的代码(通过 fallback 函数),包括调用自己。
Tiny熊
2022/04/08
8520
浅谈EKT多链技术是如何应对黑客攻击的
摘要:随着智能合约飞速发展,越来越多的项目基于以太坊发行token,链上资产的类别和规模呈指数级增长,“虚拟世界”中的数字资产也点燃了黑客们的“热情”。以太坊区块链被认为是区块链的2.0时代,各种各样新的数字资产都基于以太坊发行早期代币甚至实现部分功能,虽然国外区块链社区甚至认为以太坊体量变得太大,已经不可轻易战胜,但以太坊也是数字货币历史上产生最多安全问题的币种,从2016年的The DAO事件,到最近的BEC,EDU,SMT的安全漏洞,以太坊的智能合约可以说充满安全漏洞。大多数的代币都在自己主网上线前使用以太坊代币,作为投资者,为了自身资产的安全着想,熟悉智能合约的漏洞概念变得尤为重要。
风中凌乱的靓仔
2019/03/20
6960
浅谈EKT多链技术是如何应对黑客攻击的
以太坊网络架构解析
区块链的火热程度一直以直线上升,其中以区块链 2.0 —— 以太坊为代表,不断的为传统行业带来革新,同时也推动区块链技术发展。
Seebug漏洞平台
2018/07/12
1.7K0
以太坊 --- 交易池的特点 与 中断恢复
最近比特币和以太坊的价格也已然起飞,现在一个 BTC 已能全款辆某斯拉 model 3汽车。离谱。
林冠宏-指尖下的幽灵
2021/01/13
5820
以太坊-发展历程
区块链(公链)发展简史 比特币(1.0)-- 以太坊(2.0)-- ?(3.0) 以太坊的出现 2014 年1月, Vitalik Buterin在自己任编辑的比特币杂志(Bitcoin Maga
cwl_java
2020/04/08
1.9K0
以太坊-发展历程
理解以太坊: Go-Ethereum 源码剖析(1): Account and State
我们常常听到这样一个说法,"Ethereum 和 Bitcoin 最大的不同之一是二者使用链上数据模型不同。其中,Bitcoin 是基于 UTXO 模型的 Blockchain/Ledger 系统,Ethereum 是基于 Account/State 模型的系统"。那么,这个另辟蹊径的 Account/State 模型究竟不同在何处呢?在本文,我们就来探索一下以太坊中的基本数据单元(Metadata)之一的Account。
Tiny熊
2023/01/09
1.8K0
以太坊智能合约安全漏洞(1):重入攻击
虽然仍然处于起步阶段,但 Solidity 已被广泛采用,成为事实上的智能合约标准,新的区块链项目不少都兼容了 Solidity 语言, Solidity 已经用于编写了大量的以太坊智能合约。由于语言和EVM的细微差别,开发人员和用户都体会到了许多深刻的经验教训。本文目的是作为相对深入和最新的介绍性文章,详细阐述 Solidity 开发人员如何吸取前人踩坑的教训,避免重蹈覆辙。
Henry Zhang
2019/04/12
2.2K0
以太坊智能合约安全漏洞(1):重入攻击
以太坊源码机制:挖矿
狗年吉祥,开工利是,我们继续研究以太坊源码。从本篇文章开始,我们会深入到以太坊核心源码中去,进而分析与研究以太坊的核心技术。 关键字:拜占庭,挖矿,矿工,分叉,源码分析,uncle叔块,agent,worker,事件监听 本文基于go-ethereum 1.7.3-stable源码版本。源码范围主要在miner pkg。 miner.start() miner即矿工的意思,矿工要做的工作就是“挖矿”,挖矿就是将一系列最新未封装到块中的交易封装到一个新的区块的过程。学习以太坊挖矿之前,我们要
文彬
2018/05/03
1.8K0
知道创宇区块链安全实验室|深入理解以太坊交易处理机制
区块链是一个以"去中心化"、"去信任化"方式集体维护的分布式账本,这里的"分布式"不仅体现在数据的分布式存储,也体现在数据的分布式记录,即由系统参与者共同维护,作为"账本"的区块链自然少不了记账,而交易自然而然的成为了重中之重。
Al1ex
2021/07/21
1.6K0
知道创宇区块链安全实验室|深入理解以太坊交易处理机制
DAO可盗,非常道-惊天魔盗团和以太坊激战史上最大众筹
2013年,魔术犯罪题材影片《惊天魔盗团》在全球惊艳上映并斩获了票房口碑双赢。三年后的2016年6月,《惊天魔盗团2》将在中国大陆隆重上映。就在公映前一周的6月17日下午,互联网上的惊天魔盗团居然力抢头条,而这次的作案目标,竟然对准了全球最大的众筹项目-The DAO!
Henry Zhang
2019/04/12
6470
DAO可盗,非常道-惊天魔盗团和以太坊激战史上最大众筹
以太坊发展简史
一起来看看以太坊都经历了哪些升级或分叉, 他们都发生的时间在什么时间,加入了哪些特性。
Tiny熊
2019/06/24
2.2K0
以太坊 2.0 路线图
上一篇文章:以太坊发展简史[1]介绍了当前以太坊经历了哪些升级和硬分叉,本文将继续介绍:什么是Serenity?ETH 2.0将于何时经历哪些阶段?
Tiny熊
2019/06/24
1.2K0
相关推荐
从零开发区块链应用(十二)--以太坊余额查询
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验