มาลองแกะ code Oyente กันเถอะ (Part 2 construct static edges)

Nattawat Songsom
4 min readDec 9, 2022

--

มาเริ่มจากการเข้าใจการทำงานของ evm เมื่อรันแต่ละ opcode กันก่อน

EVM ทำงานยังไง ?

evm แบ่งพื้นที่เป็น 3 ส่วนคือ

  • stack
  • memory
  • storage

stack ?

อันนี้ simple สุดเลย คือเป็นพื้นที่ไว้เก็บ data โดยการนำ data เข้าออกจะเป็นแบบ LIFO

เช่น ถ้าเราเริ่มต้นด้วย stack และ opcode แบบนี้

เมื่อรัน PUSH1 ก็จะนำข้อมูลเข้าไปใน stack

PUSH2 จะคล้ายกัน แต่จะนำเข้าไปทีละ 2 bytes

นอกจากการนำข้อมูลเข้า stack OPCODE ยังสามารถเปลี่ยนค่าใน stack ได้ด้วยเช่น SWAP2 ที่จะสลับค่าบนสุดของ stack กับ data ที่อยู่ต่ำลงไป 2 ตำแหน่ง

เช่น ก่อนรัน SWAP2 0x09 อยู่บนสุด และ 0x03 อยู่ตำ่ลงไป 2 ตำแหน่ง

เมื่อรันแล้ว ทั้ง 2 ก็จะสลับกัน

สิ่งที่น่าสนใจเกี่ยวกับ SWAP คือ มีแค่ SWAP1 ถึง SWAP16 เท่านั้น เป็นที่มาของ compile error stack too depp ซึ่งเกิดขึ้นเมื่อเราพยายาม SWAP data ที่อยู่ไกลกันเกินไป (เช่นใน case ของ function ที่มี ตัวแปร หรือ parameter มากเกินไป ซึ่งเวลาคำนวณด้วย Opcode ทั้งหลาย ตัวอย่างเช่น ADD จะทำการบวก 2 ค่าบนสุดของ stack เข้าด้วยกัน ซึ่งการที่เราต้องไป SWAP เอาค่าที่เราอยาก ADD มาไว้บน stack ก่อน แล้วค่านั้นอยู่ลึกลงไปเกิน 16 ชั้น นั่นเอง)

โดย ซึ่งแต่ละชั้นของ stack มีความจุแค่ 32 byte เท่านั้น

ซึ่งแปลว่า ถ้าต้องคำนวณค่าที่มีขนาดใหญ่กว่านั้น เช่น uint256 จะซับซ้อนมากๆ ต้อง SWAP กันรัวๆ

ซึ่ง memory จะเข้ามาช่วยในส่วนนี้

memory ?

เป็นพื้นที่เอาไว้เก็บข้อมูลจาก stack

เช่น เมื่อเราต้องการเก็บค่า 0x03 ลง stack จะใช้ opcode MSTORE

โดยต้องระบุตำแหน่งของ memory ที่จะเขียนด้วย

โดย MSTORE จะใช้ค่าบนสุดของ stack เป็นตำแหน่งต่อท้ายของ memory ที่เราต้องการเขียน เช่นต่อท้าย 0x20 คือ 0x40

และใช้ค่าถัดมาเป็น data ที่ต้องการเขียน นั่นคือ 0x03

โดยเอาค่าไปเก็บที่ memory แล้ว stack ก็จะว่างขึ้น

เมื่อต้องการอ่านค่าจาก memory มาเก็บไว้ใน stack ก็ให้ระบุตำแหน่งที่อยากอ่าน แล้วใช้คำสั่ง MLOAD

โดยข้อมูลใน memory นั้นจะ access ได้เฉพาะใน tx เดียวกัน

แต่ถ้าเราต้องใช้ค่านั้นอีกใน tx อื่นๆที่ตามมาหลังจากนี้ละ

ใน case นี้เราจะต้องใช้พื้นที่อีกประเภทของ evm นั่นคือ storage

storage?

เป็นพื้นที่คล้ายกับ memory แต่ไว้เก็บ data ที่ต้อง persistance หรือก็คือ ค่าที่ไม่ถูกล้างออกไปเมื่อสิ้นสุด tx โดยจะใช้พวก SSTORE SLOAD แทน MSTORE MLOAD

การแปลง solidity เป็น opcode

ก่อนอื่น เนื่องจากการอ่านคำสั่งบน stack ต้องคำนวณตำแหน่งที่จุกจิกเอาเรื่อง เช่น

คืออยากเขียน memory ตำแหน่งถัดจาก 0x20 ด้วยค่า 0x03

ซึ่งพอ stack ยาวขึ้น จะมึนเอาได้ว่าแต่ละคำสั่ง ใช้ค่าอะไรบ้าง

ดังนั้น ตอนนี้เราจะขอcแปลง opcode เป็นภาษา trime เพื่อให้อ่าน opcode ง่ายขึ้น ตามภาพ

โอเค ก่อนอื่น มาเริ่มกันจาก solidity syntax ที่มี opcode ตายตัว ตามภาพ

ต่อมา เนื่องจากใน solidity แต่ละ function ต้องใส่ payable เพื่อให้รับ native token เข้า contract ได้

แปลว่า ต้องมีการเช็คในแต่ละ function ว่ามีคำว่า payable มั้ย ถ้าไม่มี payable แล้ว ต้องเช็คต่อว่าส่ง native token มามั้ย ถ้าส่งมาก็ revert

ซึ่งสามารถเขียนเป็น TRIM ได้ดังนี้

โอเค มาลองแปลง TRIM กลับเป็น opcode กัน น่าจะได้ประมาณนี้

โอเค มาลองรันทีละบรรทัดกัน

เอาค่า CALLVALUE เข้า stack

เอาค่า 0x00 เข้า stack

POP ทั้ง 2 ค่าออกมา เทียบว่าเท่ากันมั้ย แล้ว PUSH ผลลัพธ์การเทียบกลับไปใน stack

PUSH ตำแหน่งของ opcode ที่อยากให้รัน เมื่อเงื่อนไขเป็นจริงเข้าไปใน stack เช่นถ้าอยากให้ revert ก็ push ตำแหน่ง opcode ของ revert ไปเก็บไว้ก่อน

จากนั้น POP 2 ค่าออกมาจาก stack แล้วเช็คดูว่าควรไปรัน opcode ตำแหน่งไหนต่อเช่นใน case นี้คือ ถ้า CALLVALUE == 0x00 ให้ไปกระโดดไปรัน opcode ตำแหน่ง 0x48 ต่อ แต่ถ้า CALLVALUE != 0x00 ให้รัน opcode ตำแหน่งถัดไป นั่นคือ 0x07

โอเค เอาจริงๆแล้ว ด้วยพื้นฐานประมาณนี้ เราจะสามารถเชื่อมแต่ละ basic block ให้กลายเป็น cfg ได้ในระดับนึงแล้ว ถ้าตำแหน่ง block ต่อไปถูก push เข้ามาใน stack แล้วเรียกพวก JUMP ทันที

โดยเทคนิคนี้เรียกว่า construct static edges

จากรูป เราสามารถลากเส้นเชื่อม basic block จากบทความ part ที่แล้ว ให้กลายเป็น cfg ได้ในระดับนึงด้วยเทคนิค construct static edges

แต่จะมีบาง block ที่เรายังลากเส้น cfg ไม่ได้อยู่

ซึ่งเกิดจากตำแหน่ง JUMP ไม่ได้ hardcode ลงมาใน opcode แต่ต้องคำนวณ และทดค่าบน stack เพื่อหาว่าต้อง JUMP มั้ย และต้อง JUMP ไปที่ไหน

โอเค part นี้จะจบที่การ construct static edges จาก basic block ก่อน ไว้ part ต่อไปเรามาดูการลากเส้น cfg เพิ่มด้วยเทคนิค dynamic analysis กัน

Referrences:

Useful resource:

--

--

No responses yet