【漏洞分析】KaoyaSwap 安全事件分析

2022-08-28 18:01:46

相關資訊

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 的內容。

  1. 首先閃電貸 1800 BNB
  2. 呼叫 swapExactTokensForTokens 函數,用 672 BNB 換出 125023 KY
  3. 呼叫 swapExactTokensForTokens 函數,用 100 BNB 換出 6666 BUSD
  4. 呼叫 addLiquidity 函數,新增 1026 BNB 和 50 TA,獲得 226 KALP 流動性代幣

  1. 呼叫 addLiquidity 函數,新增 1 BNB 和 1 TB,獲得 0.9 KALP 流動性代幣
  2. 呼叫 addLiquidity 函數,新增 1 TA 和 1 TB,獲得 0.9 KALP 流動性代幣

  1. 呼叫 swapExactTokensForETHSupportingFeeOnTransferTokens 函數,其中 path 引數為 [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

  1. 呼叫 removeLiquidityETHSupportingFeeOnTransferTokens 函數,銷燬 226 KALP,獲得 8050 TA,Output "amountETH": "6392515470500594443"
  2. 呼叫 removeLiquidityETHSupportingFeeOnTransferTokens 函數,銷燬 0.9 KALP,獲得 0.0009 TB,Output "amountETH": "1020797089459256392608"

  1. 呼叫 removeLiquidity 函數,傳入 0.9 KALP,獲取 0.5 TA 和 1.9 TB
  2. 呼叫 swapExactTokensForTokens 函數,傳入 83918 KY,獲得 25170 BUSD

  1. 在 pancakeswap 中把 17740 KY 換成 5457 BUSD,把 23364 KY 換成 24 WBNB。
  2. 歸還 1800 WBNB 閃電貸。
  3. 然後把 37294 BUSD 和 271 WBNB 轉移到 0xd87f 地址中

總的來看,攻擊者主要投入了 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。