博客轉(zhuǎn)移新地址
渣渣學(xué)習(xí)以太坊,參考大佬文章并綜述,綜述2333
參考列表:
區(qū)塊鏈安全 - DAO攻擊事件解析(模擬過程)
智能合約初體驗(yàn)(基礎(chǔ)概念)
從技術(shù)角度剖析針對(duì)THE DAO的攻擊手法(實(shí)際攻擊分析)
基礎(chǔ)概念
?1.以太坊兩種不同的賬戶
??外部賬戶,由人類控制(通過擁有私鑰)
??合約賬戶,由代碼控制(代碼即法律)只有合約賬戶才有fallback功能
?2.合約編程語言
??Solidity: 類JavaScript,這是以太坊推薦的旗艦語言,也是最流行的智能合約語言。具體用法參加Solidity文檔。
?3.以太幣和Gas
??以太幣: ETH,以太坊中的虛擬貨幣,可以購買和使用,也可以與真實(shí)貨幣交易。以太幣的走勢圖
??Gas:相當(dāng)于手續(xù)費(fèi)。在以太坊執(zhí)行程序以保存數(shù)據(jù)都要消耗一定量的以太幣。這個(gè)機(jī)制可以控制區(qū)塊鏈中計(jì)算的數(shù)量,保證效率。
?4.智能合約與DApp
??DApp——Dimensional Assessment Of Personality Pathology
DApp流程:
??用Solidity(或其他語言)編寫智能合約(后綴為.sol)
??用solc編譯器將.sol合約編譯成EVM字節(jié)碼
??編譯好的字節(jié)碼回送給dapp前端
??前端將編譯好的智能合約部署到區(qū)塊鏈中
??區(qū)塊鏈返回智能合約地址+ABI(合約接口的二進(jìn)制表示。合約接口用JSON表示,包括變量,事件和可以調(diào)用的方法)
??前端通過Address+ABI+nonce,調(diào)用智能合約。智能合約開始處理。
基礎(chǔ)知識(shí)說明
?1.跨合約調(diào)用問題
??智能合約之間的調(diào)用本質(zhì)上是外部調(diào)用,可以使用message call或者創(chuàng)建智能合約對(duì)象的形式進(jìn)行調(diào)用。
使用message call
比如合約1調(diào)用合約2的某個(gè)方法:
bytes4 methodId = bytes4(keccak256("increaseAge(string,uint256)"));
return addr.call(methodId,"jack", 1);還原智能合約對(duì)象 如果已知合約的地址,可以通過如下方式獲取到合約對(duì)象:
Contract1 c = Contract1(AddressOfContract1) ; c.foo() ; //跨合約調(diào)用
?2.智能合約發(fā)送ETH
??我們可以在智能合約中用代碼向某個(gè)地址(這個(gè)地址可以是人,也可以是智能合約)發(fā)送以太幣,比較常見的兩個(gè)方式是:
調(diào)用send函數(shù)
比如:msg.sender.send(100)使用message call
msg.sender.call.value(100)()
??這兩個(gè)方式不同的是發(fā)送的gas數(shù)量,gas就是執(zhí)行opcode需要花費(fèi)的一種幣,稱作為gas也特別形象。當(dāng)調(diào)用send方法時(shí),只會(huì)發(fā)送一部分gas,準(zhǔn)確地來講,是2300gas,一旦gas耗盡就可能拋出異常。
??而使用message call的時(shí)候,則是發(fā)送全部的gas,執(zhí)行完之后剩余的gas會(huì)退還給發(fā)起調(diào)用的合約。
??我理解的是,發(fā)多少用多少和全部發(fā)返剩余的關(guān)系。
?3.fallback函數(shù)
??智能合約中可以有唯一的一個(gè)未命名函數(shù),稱為fallback函數(shù)。該函數(shù)不能有實(shí)參,不能返回任何值。如果其他函數(shù)都不能匹配給定的函數(shù)標(biāo)識(shí)符,則執(zhí)行fallback函數(shù)。
??當(dāng)合約接收到以太幣但是不調(diào)用任何函數(shù)的時(shí)候,就會(huì)執(zhí)行fallback函數(shù)。如果一個(gè)合約接收了以太幣但是內(nèi)部沒有fallback函數(shù),那么就會(huì)拋出異常,然后將以太幣退還給發(fā)送方。
下面就是一個(gè)fallback函數(shù)的代碼示例:
?contract Sample{ function () payable{ // your code here } }
一般單純使用message call或者send函數(shù)發(fā)送以太幣給合約的時(shí)候,沒有指明調(diào)用合約的某個(gè)方法,這種情況下就會(huì)調(diào)用合約的fallback函數(shù)。
攻擊模擬
首先是存在漏洞的智能合約代碼,Bank:

??說明:用戶可以通過addToBalance方法存入一定量的以太幣到這個(gè)智能合約,通過withdrawBalance方法可以提現(xiàn)以太坊,通過getUserBalance可以獲取到賬戶余額。
感受一下gas的代價(jià):

??出問題的是withdrawBalance方法,特別是在修改保存在區(qū)塊鏈的balances的代碼是放在了發(fā)送以太幣之后。 攻擊代碼如下:

??這里的deposit函數(shù)是往Bank合約中發(fā)送10wei。withdraw是通過調(diào)用Bank合約的withdrawBalance函數(shù)把以太幣提取出來。注意看這里的fallback函數(shù),這里循環(huán)調(diào)用了兩次Bank合約的withdrawBalance方法。
攻擊模擬過程
(1)假設(shè)Bank合約中有100wei,攻擊者Attack合約中有10wei
(2)Attack合約先調(diào)用deposit方法向Bank合約發(fā)送10wei
(3)之后Attack合約調(diào)用withdraw方法,從而調(diào)用了Bank的withdrawBalance方法。
(4)Bank的withdrawBalance方法發(fā)送給了Attack合約10wei
(5)Attack收到10wei之后,又會(huì)觸發(fā)調(diào)用fallback函數(shù)
(6)這時(shí),fallback函數(shù)又調(diào)用了兩次Bank合約的withdrawBalance,從而轉(zhuǎn)走了20wei
(7)之后Bank合約才修改Attack合約的balance,將其置為0
通過上面的步驟,攻擊者實(shí)際上從Bank合約轉(zhuǎn)走了30wei,Bank則損失了20wei,如果攻擊者多嵌套調(diào)用幾次withdrawBalance,完全可以將Bank合約中的以太幣全部轉(zhuǎn)走。
攻擊手法還原
節(jié)選自從技術(shù)角度剖析針對(duì)THE DAO的攻擊手法
??實(shí)際DAO攻擊的產(chǎn)生是由于攻擊者創(chuàng)建了childDAO并將Ether持續(xù)轉(zhuǎn)入其中,這是目前唯一可行的提取Ether的機(jī)制,所以關(guān)注點(diǎn)從splitDAO函數(shù)開始。
??splitDAO會(huì)創(chuàng)建childDAO(如果不存在的話),將分裂者擁有的Ether轉(zhuǎn)入childDAO中,根據(jù)白皮書的設(shè)計(jì),splitDAO的本意是要保護(hù)投票中處于弱勢地位的少數(shù)派防止他們被多數(shù)派通過投票的方式合法剝削。通過分裂出一個(gè)小規(guī)模的DAO,給予他們一個(gè)有效投票的機(jī)制,同時(shí)仍然確保他們可以獲取分裂前進(jìn)行的對(duì)外資助產(chǎn)生的可能收益。但通往地獄的道路就這樣用鮮花鋪就了。根據(jù)BLOG 2,在DAO.sol中,function splitDAO函數(shù)有這樣一行:

??先回退,后歸零,和模擬案例的情況就是一樣的。來看看withdrawRewardFor函數(shù):

??paidOut[_account] += reward 在原來代碼里面放在payOut函數(shù)調(diào)用之后,最新github代碼中被移到了payOut之前。
??再看payOut函數(shù)調(diào)用。rewardAccount的類型是ManagedAccount,在ManagedAccount.sol中可以看到:

??對(duì)_recipient發(fā)出call調(diào)用,轉(zhuǎn)賬_amount個(gè)Wei,call調(diào)用默認(rèn)會(huì)使用當(dāng)前剩余的所有g(shù)as,此時(shí)call調(diào)用所執(zhí)行的代碼步驟數(shù)可能很多,基本只受攻擊者所發(fā)消息的可用的gas限制。
攻擊就很簡單了,黑客創(chuàng)建自己的黑客合約HC,該合約帶有一個(gè)匿名的fallback函數(shù)。根據(jù)solidity的規(guī)范,fallback函數(shù)將在HC收到Ether(不帶data)時(shí)自動(dòng)執(zhí)行。此fallback函數(shù)將通過遞歸觸發(fā)對(duì)THE DAO的splitDAO函數(shù)的多次調(diào)用(但不會(huì)次數(shù)太多以避免gas不夠),過程中還應(yīng)該需要記錄當(dāng)前調(diào)用深度以控制堆棧使用情況。
提交split proposal:
?—> splitDAO函數(shù)(No. 1)
?—> withdrawRewardFor函數(shù) (No. 1,黑客的dao余額和dao總量此時(shí)沒變!)
?—> payOut 函數(shù)(No. 1,向HC發(fā)送以太第一次)
?—> HC的fallback函數(shù) (No. 1)
?—->如果遞歸未達(dá)預(yù)設(shè)深度:調(diào)用splitDAO函數(shù)(No. 2)
?—> withdrawRewardFor函數(shù)(No. 2, 黑客dao余額等仍然沒變?。?br>
?—> payOut函數(shù)(No. 2, 向HC發(fā)送以太第二次)
?—> HC的fallback函數(shù) (No. 2)
?—> (繼續(xù)遞歸)
轉(zhuǎn)入childDAO的錢在一定時(shí)間后根據(jù)原合約可以提取,黑客收割韭菜的時(shí)候到了。
防范和思考
攻擊得逞的因素有二:一是dao余額扣減和Ether轉(zhuǎn)賬這兩步操作的順序有誤,二是不受限制地執(zhí)行未知代碼。
應(yīng)用代碼順序方面,應(yīng)先扣減dao的余額再轉(zhuǎn)賬Ether,因?yàn)閐ao的余額檢查作為轉(zhuǎn)賬Ether的先決條件,要求dao的余額狀況必須能夠及時(shí)反映最新狀況。在問題代碼實(shí)現(xiàn)中,盡管最深的遞歸返回并成功扣減黑客的dao余額,但此時(shí)對(duì)黑客dao余額的扣減已經(jīng)無濟(jì)于事,因?yàn)槠渖细鲗舆f歸調(diào)用中余額檢查都已成功告終,已經(jīng)不會(huì)再有機(jī)會(huì)判斷最新余額了。
不受限制地執(zhí)行未知代碼方面,雖然黑客當(dāng)前是利用了solidity提供的匿名fallback函數(shù),但這種對(duì)未知代碼的執(zhí)行原則上可以發(fā)生在更多場景下,因?yàn)楹霞s之間的消息傳遞完全類似于面向?qū)ο蟪绦蜷_發(fā)中的方法調(diào)用,而提供接口等待回調(diào)是設(shè)計(jì)模式中常見的手法,所以完全有可能執(zhí)行一個(gè)未知的普通函數(shù)。