在本篇文章中,我將通過一個攻擊事件引出 Reflection Token 攻擊事件的一個通用分析思路。
關於 Reflection Token 的其他案例分析,可以參考BEVO代幣攻擊事件分析及復現一文。
TomInu Token 是一個反射型代幣 reflection token,於2023-01-26遭到駭客攻擊,攻擊者獲利35577美元。
TomInu(被攻擊合約): 0x2d0e64b6bf13660a4c0de42a0b88144a7c10991f
攻擊交易: https://phalcon.blocksec.com/tx/eth/0x6200bf5c43c214caa1177c3676293442059b4f39eb5dbae6cfd4e6ad16305668
攻擊過程較為簡單,攻擊者通過幾個常規操作就完成獲利。
本次攻擊事件通過推文告警並且進行了分析,但是很可惜分析的結論略顯含糊的。紅框標註的部分並不是攻擊者獲利的真正原因。(為什麼不是真的)
推文:https://twitter.com/QuintenDes/status/1618730379447508998
並且,目前在在網路上搜尋到的所有關於 reflection token 攻擊事件的成因分析中提到:「由於攻擊者 deliver 了一筆 token ,導致了 pair 中的 token 升值,從而能夠 skim 出更多的 token 進行獲利」。這類分析大多是理所當然地下結論,沒有通過實際的計算推導,妄下結論誤導讀者。(為什麼這麼說)
攻擊過程很簡單,先 deliver 然後 skim,就能夠獲利了。根據這個攻擊過程的特徵,我們直接找 rToken增發程式碼,定位漏洞點。(為什麼可以這麼做)
以上的幾個為什麼都將會在後面「為什麼」這一章節進行解釋,讀者可以先帶著疑問進行閱讀。
在 _transferStandard
函數中可以看出,TomInu 代幣在進行轉賬 rAmount
時需要收取 team
和 fee
兩種手續費,並將扣除了手續費後的 rTransferAmount
轉給收款人。
其中 team 手續費 rteam
留存在本合約中,fee 手續費 rfee
則是直接銷燬。
此時他們的數量關係應該為:rTransferAmount = rAmount - rteam - rfee
問題出現在 _getRValues
函數中,該函數在計算 rTransferAmount
的過程中忽略了 rteam
引數,計算 rTransferAmount = rAmount - rfee
得到的結果比實際結果要大,造成了 rToken 的增發。也就是說,因為這個計算問題,市場上實際流通的 rAmount
總和是要大於 rTotal
的值的。
代幣合約在執行 _transfer()
函數的時候,會將本合約的代幣 swap 成 ETH,這個操作使得增發的代幣一直累計在 pair 中。
在這個章節中,會對前面的暴言暴論進行解釋
首先我將舉幾個例子來模擬整個 deliver-skim 的過程,為了使得這個例子儘可能的簡單,這個過程中將不考慮任何手續費的收取。
場景1:
只有 attacker 和 pair 持有所有的 token
rTotal 1000, tTotal 100, rate 10
pair: rAmount 500, tAmount 50
attacker: rAmount 500, tAmount 50
attacker deliver 500 rAmount
rTotal 500, tTotal 100, rate 5
pair: rAmount 500, tAmount 100
attacker: rAmount 0, tAmount 0
此時,pair 的 tAmount 從 50 變成了 100。接下來 attacker 將呼叫 skim 來獲利了是不是?
attacker calls pair.skim()
rTotal 500, tTotal 100, rate 5
pair: rAmount 250, tAmount 50
attacker: rAmount 250, tAmount 50
attacker 如願以償獲利了嗎?沒有,attacker 和 pair 又回到了最初的 50 tAmount,並不能通過這個操作來進行獲利。
場景2:
attacker, pair 以及一些其他使用者共同持有所有的 token
rTotal 1000, tTotal 100, rate 10
pair: rAmount 250, tAmount 25
attacker: rAmount 500, tAmount 50
others: rAmount 250, tAmount 25
attacker deliver 500 rAmount
rTotal 500, tTotal 100, rate 5
pair: rAmount 250, tAmount 50
attacker: rAmount 0, tAmount 0
others: rAmount 250, tAmount 50
attacker calls pair.skim()
rTotal 500, tTotal 100, rate 5
pair: rAmount 125, tAmount 25
attacker: rAmount 125, tAmount 25
others: rAmount 250, tAmount 50
pair 回到了原始的 25 tAmount,而 attacker 由原來的 50 虧損到了 25 tAmount。堅定持有的 others 由 25 上漲到了 50 tAmount。
通過上面的兩個例子,我們可以得出結論,只有當 attacker 和 pair 所持有的代幣份額合計 100% 的情況下,deliver-skim 的操作 attacker 才不會虧損。而兩者份額不足 100% 的情況下,deliver-skim 的操作反而會導致 attacker 遭受損失。也就是說 attacker 通過 deliver-skim 的操作無論怎麼樣都是不賺的,最好的情況是 attacker 和 pair 所持有的代幣份額合計 100% 的情況下才不至於虧損。
那麼…有沒有更好的情況呢?好到…兩者持有的代幣份額合計起來…超過100%?
比如,發生了代幣增發?
場景3:
由於程式碼存在 rToken 相關的計算錯誤,導致代幣增發的發生,具體表現為 rToken 的實際流通量大於 rTotal 的數量。
rTotal 1000, tTotal 100, rate 10
pair: rAmount 400, tAmount 40
attacker: rAmount 800, tAmount 80
others: rAmount 400, tAmount 40
sum_rAmount = 1600 > rTotal = 1000
pair.rAmount + attacker.rAmount = 1200 > rTotal = 1000
attacker deliver 800 rAmount
rTotal 200, tTotal 100, rate 2
pair: rAmount 400, tAmount 200
attacker: rAmount 0, tAmount 0
others: rAmount 400, tAmount 200
attacker calls pair.skim()
rTotal 200, tTotal 100, rate 2
pair: rAmount 80, tAmount 40
attacker: rAmount 320, tAmount 160
others: rAmount 400, tAmount 200
至此,attacker 從原來的 80 tAmount,通過 deliver-skim 操作獲利達到 160 tAmount。
通過這個場景,也就可以解釋為什麼看到攻擊過程中通過 deliver-skim 操作獲利時,首先想到的就是去找程式碼中使得 rAmount 增發的計算操作。因為只有 rAmount 發生了增發,pair 和 attacker 的份額大於 100%,且增發部分需要留存在 pair 合約中,才能夠滿足通過 deliver-skim 操作進行獲利的基礎條件。
在 TomInu 攻擊事件發生的4個月後,存在相同漏洞的 ADU token 再次被攻擊。
ADU token attack tx:https://explorer.phalcon.xyz/tx/bsc/0xc6f6b70e9e35770b699da9b60244d461d02db66859df42319c3207d76931423c
為什麼會寫這篇文章,因為當我想對這些攻擊事件進行學習與分析的時候,我檢視了網路上的分析文章,他們給出的漏洞成因含糊不清毫無根據。我讀了很多篇分析文章,說辭都是大同小異地糊弄。還沒分析清楚就胡亂指點,最終被忽悠的就是真心想研究清楚的人。走了不少彎路,把彎路總結成這篇文章,感謝你的閱讀。