KaoyaSwap 是 BSC 鏈上的一個自動做市商 AMM。然後,現在他們的官網 https://www.kaoyaswap.com/ 已經打不開了(如果我開啟方式沒錯的話)。所以就直接進行攻擊事件的分析吧。
攻擊交易:https://bscscan.com/tx/0xc8db3b620656408a5004844703aa92d895eb3527da057153f0b09f0b58208d74
攻擊者在進行攻擊之前,自己構建了兩個代幣協助完成攻擊,下面將他們分別稱為 TA 和 TB 。
TA address:0x74eF69Defe8bae1Fe660fB93265FC1bc79c9bDa8
TB address:0xD84379C4eeA25d05574f9F0B99E3Bf73500Ca4B4
因為攻擊是發生在 AMM 上的,所以我們可以根據代幣的流向先大概分析一下攻擊者在這筆交易中都做了些什麼,看看能不能看出有什麼奇怪的地方。下面綠色框圖是 Tokens Transferred 的內容,紅色框圖的是 Internal Txns 的內容。
[TA, WBNB, TB, TA, WBNB]
的地址。這個函數還涉及到 BNB 的轉賬,但是函數 Output 中沒有體現。(劇透一下,轉出了 1019 BNB 給攻擊地址。具體情況後面的程式碼分析會解釋)。Input:
"amountIn": "8000000000000000000000",
"amountOutMin": "1",
"path": [
"0x74ef69defe8bae1fe660fb93265fc1bc79c9bda8",
"0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c",
"0xd84379c4eea25d05574f9f0b99e3bf73500ca4b4",
"0x74ef69defe8bae1fe660fb93265fc1bc79c9bda8",
"0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c"
],
"to": "0xa722ca7bf032de8f7a675da75dfec661bc89ace9",
"deadline": "1661293930「
Output:
0x
總的來看,攻擊者主要投入了 1026 BNB 到 WBNB 和 TA 的池子中,然後通過 swapExactTokensForETHSupportingFeeOnTransferTokens 函數以 8000 TA 獲得 1019 BNB,並且通過 removeLiquidityETHSupportingFeeOnTransferTokens 函數移除 WBNB 和 TB 池子的流動性獲得 1020 BNB。
投入 1026 BNB,獲得 (1019 + 1020) BNB,其中必有蹊蹺。
首先來分析一下 swapExactTokensForETHSupportingFeeOnTransferTokens
函數,先呼叫 _transferIn
轉入 TA,然後呼叫 _swapSupportingFeeOnTransferTokens
函數按照 path
進行一系列 swap 操作,計算 _pools[TA,WBNB][WBNB]
的變化,根據差值給 to
地址傳送相等數量的 BNB。
需要關注的函數:_transferIn
和 _swapSupportingFeeOnTransferTokens
。這些函都涉及到了一個關鍵的變數 _pools
,它是這次攻擊的關鍵點。
先來看 _transferIn
函數,它的作用是把 path
中的第一個代幣轉入合約中,並修改對應的 _pools
值。
_swapSupportingFeeOnTransferTokens
函數根據 path
所提供的代幣地址進行 swap,並將對應的 _pools
變數進行修改。
其中 _transferOut
函數的作用就是向 to
地址傳送 amount
數量的 token。
當 swapExactTokensForETHSupportingFeeOnTransferTokens
函數中 path = [TA, WBNB, TB, TA, WBNB]
時所發生的情況是(下面簡稱 [A, W, B, A, W]
):
首先在 _transferrIn
函數中:
_pool[AW][A] + amountIn
然後在 _swapSupportingFeeOnTransferTokens
函數中(注意 _pool[AW][W]
做了兩次減法操作):
i = 0 , _pool[AW][W] – amountOutput1, _pool[WB][W] + amountOutput1
i = 1 , _pool[WB][B] – amountOutput2, _pool[BA][B] + amountOutput2
i = 2 , _pool[BA][A] – amountOutput3, _pool[AW][A] + amountOutput3
i = 3 , _pool[AW][W] – amountOutput4
轉賬的金額只通過最後一對 pair
的 _pool
數值 _pool[AW][W]
的減少量(從 AW 這個池子裡換出了多少 W)來確定。正確的方法應該是隻關注最後一步中 _pool[AW][W]
的減少量,而不是整個 swap 過程中 _pool[AW][W]
的總(累計)減少量。如果採用總(累計)減少量計算,則當 path 中有重複的 pair ([A, W]
)不連續出現時,會多次計算 _pool[AW][W]
的減少量,最終導致 balanceBefore.sub(balanceAfter)
的值大於實際值。
我們看到第 1 次 TA 換 WBNB 的時候,換出來 1019.797089459257413406 WBNB,第 2 次 TA 換 WBNB 的時候,換出來 0.000395070241992122 WBNB,累加得到 1019.797484529499405528 WBNB,吻合之前提到的值。而正常情況下應該轉出的 WBNB 因該為 0.000395070241992122 WBNB,同時將 [WBNB, TB] 池子中 TB 的幣價大幅提高(轉入了大量 WBNB)。
綜上所屬,利用了 _pools
變數在整個 swap 過程中累計減少量的漏洞,攻擊者用一筆錢,在提高了 [WBNB, TB] 池子中 TB 的幣價的同時, swap 出了超量的 WBNB。然後再移除 [WBNB, TB] 池子的流動性, 獲取大量的 BNB。