DAO攻击事件回顾

博客转移新地址
渣渣学习以太坊,参考大佬文章并综述,综述2333
参考列表:
区块链安全 - DAO攻击事件解析(模拟过程)
智能合约初体验(基础概念)
从技术角度剖析针对THE DAO的攻击手法(实际攻击分析)

基础概念

 1.以太坊两种不同的账户

  外部账户,由人类控制(通过拥有私钥)
  合约账户,由代码控制(代码即法律)只有合约账户才有fallback功能

 2.合约编程语言

  Solidity: 类JavaScript,这是以太坊推荐的旗舰语言,也是最流行的智能合约语言。具体用法参加Solidity文档

 3.以太币和Gas

  以太币: ETH,以太坊中的虚拟货币,可以购买和使用,也可以与真实货币交易。以太币的走势图
  Gas:相当于手续费。在以太坊执行程序以保存数据都要消耗一定量的以太币。这个机制可以控制区块链中计算的数量,保证效率。

 4.智能合约与DApp

  DApp——Dimensional Assessment Of Personality Pathology
DApp流程:
  用Solidity(或其他语言)编写智能合约(后缀为.sol)
  用solc编译器将.sol合约编译成EVM字节码
  编译好的字节码回送给dapp前端
  前端将编译好的智能合约部署到区块链中
  区块链返回智能合约地址+ABI(合约接口的二进制表示。合约接口用JSON表示,包括变量,事件和可以调用的方法)
  前端通过Address+ABI+nonce,调用智能合约。智能合约开始处理。

基础知识说明

 1.跨合约调用问题

  智能合约之间的调用本质上是外部调用,可以使用message call或者创建智能合约对象的形式进行调用。

  1. 使用message call
    比如合约1调用合约2的某个方法:
    bytes4 methodId = bytes4(keccak256("increaseAge(string,uint256)"));
    return addr.call(methodId,"jack", 1);

  2. 还原智能合约对象 如果已知合约的地址,可以通过如下方式获取到合约对象:
    Contract1 c = Contract1(AddressOfContract1) ; c.foo() ; //跨合约调用

 2.智能合约发送ETH

  我们可以在智能合约中用代码向某个地址(这个地址可以是人,也可以是智能合约)发送以太币,比较常见的两个方式是:

  1. 调用send函数
    比如:msg.sender.send(100)

  2. 使用message call
    msg.sender.call.value(100)()

  这两个方式不同的是发送的gas数量,gas就是执行opcode需要花费的一种币,称作为gas也特别形象。当调用send方法时,只会发送一部分gas,准确地来讲,是2300gas,一旦gas耗尽就可能抛出异常。
  而使用message call的时候,则是发送全部的gas,执行完之后剩余的gas会退还给发起调用的合约。
  我理解的是,发多少用多少全部发返剩余的关系。

 3.fallback函数

  智能合约中可以有唯一的一个未命名函数,称为fallback函数。该函数不能有实参,不能返回任何值。如果其他函数都不能匹配给定的函数标识符,则执行fallback函数。
  当合约接收到以太币但是不调用任何函数的时候,就会执行fallback函数。如果一个合约接收了以太币但是内部没有fallback函数,那么就会抛出异常,然后将以太币退还给发送方。
下面就是一个fallback函数的代码示例:
contract Sample{ function () payable{ // your code here } }

一般单纯使用message call或者send函数发送以太币给合约的时候,没有指明调用合约的某个方法,这种情况下就会调用合约的fallback函数。

攻击模拟

首先是存在漏洞的智能合约代码,Bank:

contract bank

  说明:用户可以通过addToBalance方法存入一定量的以太币到这个智能合约,通过withdrawBalance方法可以提现以太坊,通过getUserBalance可以获取到账户余额。
感受一下gas的代价:


代码操作费用表

  出问题的是withdrawBalance方法,特别是在修改保存在区块链的balances的代码是放在了发送以太币之后。 攻击代码如下:

image.png

  这里的deposit函数是往Bank合约中发送10wei。withdraw是通过调用Bank合约的withdrawBalance函数把以太币提取出来。注意看这里的fallback函数,这里循环调用了两次Bank合约的withdrawBalance方法。

攻击模拟过程

(1)假设Bank合约中有100wei,攻击者Attack合约中有10wei

(2)Attack合约先调用deposit方法向Bank合约发送10wei

(3)之后Attack合约调用withdraw方法,从而调用了Bank的withdrawBalance方法。

(4)Bank的withdrawBalance方法发送给了Attack合约10wei

(5)Attack收到10wei之后,又会触发调用fallback函数

(6)这时,fallback函数又调用了两次Bank合约的withdrawBalance,从而转走了20wei

(7)之后Bank合约才修改Attack合约的balance,将其置为0

通过上面的步骤,攻击者实际上从Bank合约转走了30wei,Bank则损失了20wei,如果攻击者多嵌套调用几次withdrawBalance,完全可以将Bank合约中的以太币全部转走。

攻击手法还原

节选自从技术角度剖析针对THE DAO的攻击手法
  实际DAO攻击的产生是由于攻击者创建了childDAO并将Ether持续转入其中,这是目前唯一可行的提取Ether的机制,所以关注点从splitDAO函数开始。
  splitDAO会创建childDAO(如果不存在的话),将分裂者拥有的Ether转入childDAO中,根据白皮书的设计,splitDAO的本意是要保护投票中处于弱势地位的少数派防止他们被多数派通过投票的方式合法剥削。通过分裂出一个小规模的DAO,给予他们一个有效投票的机制,同时仍然确保他们可以获取分裂前进行的对外资助产生的可能收益。但通往地狱的道路就这样用鲜花铺就了。根据BLOG 2,在DAO.sol中,function splitDAO函数有这样一行:

splitDAO代码片段

  先回退,后归零,和模拟案例的情况就是一样的。来看看withdrawRewardFor函数:

withdrawRewardFor

  paidOut[_account] += reward 在原来代码里面放在payOut函数调用之后,最新github代码中被移到了payOut之前。
  再看payOut函数调用。rewardAccount的类型是ManagedAccount,在ManagedAccount.sol中可以看到:

payOut函数

  对_recipient发出call调用,转账_amount个Wei,call调用默认会使用当前剩余的所有gas,此时call调用所执行的代码步骤数可能很多,基本只受攻击者所发消息的可用的gas限制。

攻击就很简单了,黑客创建自己的黑客合约HC,该合约带有一个匿名的fallback函数。根据solidity的规范,fallback函数将在HC收到Ether(不带data)时自动执行。此fallback函数将通过递归触发对THE DAO的splitDAO函数的多次调用(但不会次数太多以避免gas不够),过程中还应该需要记录当前调用深度以控制堆栈使用情况。
提交split proposal:
 —> splitDAO函数(No. 1)
 —> withdrawRewardFor函数 (No. 1,黑客的dao余额和dao总量此时没变!)
 —> payOut 函数(No. 1,向HC发送以太第一次)
 —> HC的fallback函数 (No. 1)
 —->如果递归未达预设深度:调用splitDAO函数(No. 2)
 —> withdrawRewardFor函数(No. 2, 黑客dao余额等仍然没变!)
 —> payOut函数(No. 2, 向HC发送以太第二次)
 —> HC的fallback函数 (No. 2)
 —> (继续递归)

转入childDAO的钱在一定时间后根据原合约可以提取,黑客收割韭菜的时候到了。

防范和思考

攻击得逞的因素有二:一是dao余额扣减和Ether转账这两步操作的顺序有误,二是不受限制地执行未知代码。
应用代码顺序方面,应先扣减dao的余额再转账Ether,因为dao的余额检查作为转账Ether的先决条件,要求dao的余额状况必须能够及时反映最新状况。在问题代码实现中,尽管最深的递归返回并成功扣减黑客的dao余额,但此时对黑客dao余额的扣减已经无济于事,因为其上各层递归调用中余额检查都已成功告终,已经不会再有机会判断最新余额了。

不受限制地执行未知代码方面,虽然黑客当前是利用了solidity提供的匿名fallback函数,但这种对未知代码的执行原则上可以发生在更多场景下,因为合约之间的消息传递完全类似于面向对象程序开发中的方法调用,而提供接口等待回调是设计模式中常见的手法,所以完全有可能执行一个未知的普通函数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342