note: KyberSwap Hack (23/11/2023)

Nattawat Songsom
3 min readNov 26, 2023

--

พอดีไปอ่าน case hacking CLMM มา เลยขอ note ไว้ก่อนครับ

โอเค

วันที่ 23/11/2023 มี attacker drain เหรียญออกจาก KyberSwap pool

โดยเทคนิคที่ attacker ใช้ เกี่ยวกับการทำงานของ CLMM (Concentrated Liquidity Market Maker)

ดังนั้น ก่อนอื่น CLMM คืออะไร ?

ปกติแล้วเราจะคุ้นกับกราฟอันนี้

ซึ่งกราฟนี้เป็นกราฟของ uniswap V2 โดย เส้นโค้งแสดงถึง liquidity ที่ถูกกระจายไปทั่วทุกๆช่วงราคา

ดังนั้น เมื่อเราวางเหรียญใน AMM ตัวเหรียญก็จะถูกกระจายไปในทุกๆช่วงราคา

แต่ CLMM ไม่ได้ทำงานแบบนั้น

เมื่อมีคนมาวางเหรียญใน CLMM เขาสามารถเลือกได้ว่าจะวางในช่วงราคาไหน

เช่น จากกราฟเราจะเห็นได้ว่า

แต่ละคน เลือกวางเหรียญในช่วงราคาต่างกัน

นาย A วางช่วง 30–700 (สีเขียว)

นาย B วางช่วง 100–800 (สีม่วง)

เป็นต้น

โดยในช่วงราคา 300–600 เราจะพบว่า กราฟพีระมิดขึ้นไปสูงสุด

เนื่องจากมีคนมาวางเหรียญมาทับในช่วงราคานี้กันเยอะนั่นเอง

เอาละ

แล้วเวลา swap บน CLMM ละ จะเกิดอะไรขึ้นบ้าง

สมมติว่าจากกราฟ

ราคาปัจจุบันอยู่ที่ tick 250 (เราจะไม่ลงลึกเรื่อง tick 250 เท่ากับราคาเท่าไหร่ก่อนนะ ทับศัพท์ไปก่อน)

โดยถ้ามีคนมา swap ตัวเหรียญออกไป

สภาพคล่องในจุด tick 250 (สีเขียว + ม่วง) ก็จะถูกเอาไปใช้ในการ swap

และการ swap เหรียญออก ก็ทำให้ราคาเหรียญขึ้น

ดังนั้น เมื่อราคาเหรียญขึ้นไปถึง 300

สภาพคล่องอีกก้อนนึง (สีเขียว + ม่วง + แดง) ก็จะถูกเอามาใช้แทน

โดยตัว code CLMM จะมีกลไกการคำนวณค่าสภาพคล่อง (liquidity) ของราคาปัจจุบันให้สัมพันธ์กับ liquidity ที่มีคนมาวาง pool ไว้

แต่เมื่อการคำนวณพลาด ก็ทำให้เกิดการ attack ได้

โอเค มาดูการ attack KyberSwap ที่เกิดขึ้นกัน

  1. เริ่มจากการที่ attacker ไป flash loan 10,000 wstETH มา

2. จากนั้นทำการเติม swap in 2,800 wstETH ลงไปใน ETH/wstETH pool

โดยทำให้ราคา wstETH ตกลงมาจาก 1.05 ETH เป็น 0.000152 ETH

note: นี่เป็นช่วงราคาที่ไม่มี liquidity เหลือแล้ว

3. จากนั้นทำการ mint (เป็นคำเรียกของการ วาง pool เพิ่ม) 3.4 wstETH เข้าไปใน pool

โดยวาง pool เพิ่มในช่วงราคา 0.0000146 — 0.0000153

และมีการถอน 0.56 wstETH ออกจาก pool ในช่วงราคาเดิมด้วย

4. เอาละ จากนั้น attacker ทำการ swap 1,056 wstETH เป็น 0.0157 ETH

โดยการขายนี้ทำให้ราคาปัจจุบันของ wstETH ตกลงมาเลยช่วงที่เขาวาง pool ไว้นิดหน่อย (ตกลงมาต่ำกว่า 0.0000146 ETHนิดนึง)

แต่ประเด็นคือ การคิดค่า liquidity ณ ราคาปัจจุบัน ที่เราพูดถึงกันก่อนหน้านี้ ดันไม่ทำงาน

เช่น จากรูป ถ้าราคาตกลงมาต่ำกว่า 300 แล้ว liquidity ก้อนสีแดง ก็ไม่ควรถูกเอามา swap … แต่ให้ให้แค่ก้อนสีม่วงกับเขียวเท่านั้น

แต่พอกลไกอัพเดทค่า liquidity ปัจจุบันไม่ทำงาน ทำให้ในตัวแปรนั้น มีค่า liquidity จากช่วงราคาเก่า ค้าง อยู่ด้วย

โอเค key takeaway ของ step นี้คือ มีค่า liquidity ของช่วงราคาเก่าค้างอยู่ในตัวแปรที่เก็บค่า liquidity ของราคาปัจจุบัน

5. จากนั้น attacker ทำการ swap 3911 wstETH ออกมาด้วย 0.06 ETH

ก่อนอื่น จะเห็นได้ว่า attacker swap 3911 wstETH ออกมาได้ ทั้งๆที่ใน step 4 เขาเอาเข้าไปใน pool แค่ 1,056 wstETH เท่านั้น

ทำไม attacker ถึง drain wstETH ส่วนเกินออกมาได้ ?

ทั้งๆที่ attacker ก็ swap ไปมาในช่วงที่เขาวาง liquidity เอง

เขาก็ไม่น่าจะดึง liquidity ส่วนอื่น ออกมาได้รึเปล่า ?

โอเค ก่อนเรา swap ใน step นี้ ราคาอยู่ต่ำกว่า range ที่เราวาง pool เอาไว้

แต่พอเราเริ่ม swap จนราคาเข้ามาใน range (swap 3911 wstETH ออกมาด้วย 0.06 ETH) … ทำให้ราคาเข้าไปใน range ของที่เราวาง pool เอาไว้

เมื่อราคาเข้ามาใน range … ก็ทำให้ตัวแปร liquidity ปัจจุบันก็จะมีค่าเพิ่มขึ้น

ประเด็นอยู่ตรงที่ว่า ตอน swap รอบแรก (ใน step 4) ที่เราออกจาก range … เราไม่ได้ลบ liquidity ส่วนนี้ออกไปจากค่า liquidity ปัจจุบันด้วยซ้ำ

แล้วพอเราเข้ามาใน range เรายังบวกค่า liquidity เข้ามาเพิ่มอีก (เพราะเราคิดว่าเคยลบออกไปแล้ว เลยบวกเพิ่มให้พอดีกัน)

นี่ทำให้ attacker สามารถดึงเหรียญส่วนที่ไม่ใช่ของ range ที่ตัวเองวาง pool ได้ เนื่องจาก contract คำนวณค่า liquidity ของ range ใน CLMM ผิด

โอเค สั้นๆประมาณนี้ ไว้มีโอกาสจะมาอธิบาย code ครับ

Resources

--

--

No responses yet