Storage ABIEncoderV2 Array bug and how Slither detect it

Nattawat Songsom
4 min readNov 9, 2022

--

มาแกะ Slither กัน

ช่องโหว่ Storage ABIEncoderV2 Array คืออะไร ?

คือ bug ของ abi.encode โดยจาก code

expected output คือได้ decoded array ของ [[1,2], [3,4], [5,6]]

actual output คือได้ decoded array ของ [[1,2], [2,3], [3,4]]

โดย bug นี้จะเกิดใน compiler version 0.4.7–0.5.9 เท่านั้น

วิธีที่ Slither ใช้ในการ detect ช่องโหว่นี้

Slither คืออะไร ?

Slither คือ smart contract analyzer โดยคร่าวๆคือ

  • เป็น static analysis tool
  • พัฒนาด้วย python

เอาละ เราจะมาอธิบาย slither ไปพร้อมๆกับตัวอย่างเลยดีกว่า

case 1: simple state array decoded

เอาละ เรามาไล่ code แบบ top down กัน

อย่างแรกคือ ถ้าเราแยกชิ้นส่วน Slither ให้เหลือแค่ code ที่ต้องใช้เพื่อตรวจจับ ABIEncoderV2 Array จะออกมาเป็นยังไง ?

โอเค ตอนนี้เราแกะมาเฉพาะ code ส่วน detect ช่องโหว่นี้ได้ละ ผลลัพธ์การ detect จะดูยากหน่อย เพราะเราไม่ได้เอา code ส่วน format result มาด้วย

เอาละ แล้ว slither.register_detector(detectors[‘abiencoderv2-array’]) ไปเอา code มาจากส่วนไหน ?

ไฟล์นี้จะเป็น logic หลักของการ detect bug นี้เลย มาดู logic กัน

ขั้นแรกเป็นการตรวจเช็ค version compiler ว่าเป็น version ที่มี bug นี้รึเปล่า

note: Slither สามารถเช็ค version compiler ได้ด้วย self.compilation_unit.solc_version จาก class detector ต่างๆ

ต่อมาเป็นการเช็คว่ามีการใช้ pragma experimental ABIEncoderV2 รึเปล่า ซึ่งถ้าไม่ได้ใส่มาแสดงว่าไม่ได้ใช้ feature ABIEncoderV2 แสดงว่าไม่มี bug นี้

งั้นแปลว่า ถ้าไม่ได้ใช้ pragma experimental ABIEncoderV2 แล้ว abi.encode ของ version 0.4.7–0.5.9 จะไม่มี bug นี้

note: Slither สามารถเช็ค pragma ได้ด้วย self.compilation_unit.pragma_directives

ต่อมาจะเป็นการไล่ใส่ แต่ละ contracts ในไฟล์ sol ไปเช็คช่องโหว่

note: Slither สามารถดึงค่าแต่ละ contract ได้ด้วย self.contracts

โอเค การ detect ช่องโหว่ จะเริ่มจากการไล่ functions และ modifier ของ contract นั้น (ไม่ต้องไล่ดู state variable เพราะ ถ้าเป็น internal ก็ต้องเรียกใช้ใน function อยู่ดี แต่ถ้าเป็น public ก็จะมี function default สำหรับการ read ให้เหมือนกันอยู่ดี)

มาลองดู function และ modifier ที่ Slither ดึงมาได้กัน

slitherConstructorVariables มาจากไหน ? อันนี้จะเริ่มเข้าสู่เบื้องหลังของ Slither ละ

จริงๆแล้ว Slither ทำงานโดย

solidity code => แปลงเป็นภาษา SlithIR => วิเคราะห์ช่องโหว่จาก code ภาษา SlithIR

มาลองแปลง code solidity เป็น slithIR กัน

จาก code

ใช้คำสั่ง

slither <file.sol> --print slithir

โดยหนึ่งใน feature ของภาษา SlithIR คือการ list function ของ Solidity

โอเค list function และ modifier มาได้แล้วยังไงต่อ ?

ต่อมาจะเป็นการไล่ code ไปทีละบรรทัด ของแต่ละ function

โดยเราจะไม่ได้ไล่ code solidity ตรงๆ แต่จะใช้ node จาก SlithIR ซึ่งทำหน้าที่แทนแต่ละบรรทัดบน solidity

โอเคเริ่มจาก function bad2

จาก code

แปลงเป็น slithIR

โอเคจาก expression: b = abi.encode(bad_arr) จะนำไปวิเคราะห์ช่องโหว่ได้ยังไง ?

SlitherIR ใช้สิ่งที่เรียกว่า IRs ซึ่งเป็นการแยก expression ของภาษา solidiy ออกเป็นคำสั่งย่อยๆ เช่นจากรูป จะแยกได้เป็น 2 IRs

โดยเทคนิคนี้เรียกว่า SSA ถ้าอธิบายง่ายๆคือ code ที่แต่ละตัวแปร มีค่าได้แค่ค่าเดียว ไม่สามารถเปลี่ยนแปลงได้ ตัวอย่าง code ที่ simple กว่านี้ก็เช่น

จาก expresssion

x = 3

y = x++

x = y + x

แปลงเป็น SSA form ได้เป็น

x1 = 3

y1 = x1

x2 = x1 + 1

x3 = y1 + x2

โอเคกลับมาที่แต่ละ ir

เริ่มจาก ir แรก

ซึ่งเป็นการเช็คการเรียก function abi.encode ดังนั้น ir นี้ก็จะผ่านการเช็คเงื่อนไขด่านนี้ ด้วยการ short circuit

แต่คำถามคือ

abi.encode() ถูกเรียกด้วย array ของ array รึเปล่า ซึ่งนำมาสู่เงื่อนไขถัดไป

โดย args ของ abi.encode จะต้องเป็น array ของ array หรือ array ของ struct ซึ่งจาก code เราเป็น array ของ array

และเงื่อนไขสุดท้ายคือ args นั้นเป็น state variable หรือ local variable ที่มี flag is_storage

อันนี้เราเข้าเงื่อนไข state variable … local variable ที่มี flag is_storage ต้องเอาตัวอย่างที่เข้าไขเงื่อนไขมาวิเคราะห์อีกที

โอเค โดยสรุปแล้ว จาก code solidity ด้านบน

แปลงเป็น slithIR

จะเจอช่องโหว่ Storage ABIEncoderV2 Array ที่ function bad2 ใน expression b = abi.encode(bad_arr)

โอเค น่าจะประมาณนี้ก่อน จริงๆยังมี alternative case ของ ช่องโหว่ นี้ที่ตรงตาม logic ระบุไว้อีก แต่ยังมีอีกหลายช่องโหว่เลยเนี่ยสิ งั้นครั้งหน้า ไปช่องโหว่อื่นกันครับ

reference:

--

--

No responses yet