由於 pooltokens 增發的量透過代幣 deposit 前後餘額的差值得出。所以第二次 deposit 的真實的 DAI 資產會被計算兩次用於鑄造 pooltokens。第一次是在惡意合約 0xe230 儲存的時候,第二次是在 DAI 儲存的時候。也就是說,如果第二次儲存的時候存入了 25K DAI, 那麼由於重入攻擊,總的鑄造的 pooltokens 將會是雙倍,也就是 50K DAI 。
以此類推,駭客總共發起 17次重入攻擊並獲得了總共 2,030,841.0177個DAI 資產。
值得注意的是,在攻擊的最開始,攻擊者還使用了 dYdX 的閃貸功能(https://etherscan.io/tx/0xddf8c15880a20efa0f3964207d345ff71fbb9400032b5d33b9346876bd131dc2)。
核心漏洞詳解:
接下來,我們分析下存在漏洞的代幣儲存邏輯。Akropolis 的使用者可以將代幣儲存入Delphi Savings Pools,而資金池會鑄造相應的 pooltokens 給使用者。核心邏輯在SavingsModule::deposit()(1,944行)。
第一步:攻擊者呼叫 deposit() 函式並提供 _tokens 引數。這個函式在進一步呼叫 depositToProtocol(_protocol, _tokens, _dnAmounts) 前後會計算代幣的餘額,並透過代幣餘額的變化來決定將要鑄造的 poolTokens 數目(第1,970行)。而 depositToProtocol() 函式會呼叫目標代幣的 safeTransferFrom()函式來進行代幣的轉賬(第2,004行)。然而 deposit() 函式沒有對重入攻擊進行檢測,也沒有檢查存入的代幣是否為惡意代幣;
第二步:在惡意代幣的 transferFrom() 函式被呼叫的時候,觸發鉤子函式,從而再次呼叫 deposit() 函式;
第三步:因為第二次呼叫 deposit() 函式的時候攻擊者存入了真正的 DAI 代幣使得池子的代幣餘額發生變化,所以攻擊者可以獲得資金池鑄造的 poolTokens;
第四步:當第二次 deposit() 函式呼叫結束的時候,程式碼執行流程將返回第一次儲存代幣呼叫 depositToProtocol() 函式的上下文。這個時候,代幣餘額變化將被再次計算。此時代幣餘額的變化和第二次呼叫 deposit() 函式代幣餘額變化一樣。因此攻擊者可以再次獲得相應數目的 poolTokens。
被盜資產情況:
這次攻擊的被盜資產目前被儲存在錢包[0x9f26](https://etherscan.io/address/0x9f26ae5cd245bfeeb5926d61497550f79d9c6c1c)中。PeckShield 旗下數字資產追蹤平臺 CoinHolmes 正在對該地址做全方位監控,並對其資金流向做進一步的鎖定分析和追蹤,以便協助專案方挽回被盜資產。