Ethernaut part 7 (Privacy & Gate keeper I)

Nattawat Songsom
4 min readJan 7, 2023

--

Capture The Fun

Privacy

โจทย์คือ ให้เรียก function unlock เพื่อเปลี่ยนค่า locked ให้เป็น false

ดูคร่าวๆ เหมือนจะต้องอ่าน storage data โดยการนับตำแหน่งเอาละนะ

โอเค ก่อนอื่น มาเริ่มจากการ clone hardhat foundry template กัน ตาม link

note: ต้องทำ git submodule init && git submodule update เพื่อให้เห็น repo ย่อย

โอเค จริงๆถ้าจะนับตำแหน่ง storage เองก็ได้ แต่เราจะให้ solc gen ให้ไปเลยดีกว่า โดยใช้คำสั่ง

จะได้ข้อมูลมาละว่า data อยู่ใน slot 3 4 5 (slot ละ 32 bytes เลยใช้ 3 slot)

โอเค ลองเขียน local test ดู ได้ประมาณนี้

บรรทัดที่ 4 เราอ่านค่าจาก storage

บรรทัดที่ 5 เราหั่นค่าที่ได้เป็น 16 bytes

โอเคใน local test โอเคละ มาเขียน script จริงกัน ได้ประมาณนี้

code ก็เหมือนตอนเขียน test ละนะ

โอเคผ่านละ มี resource มาให้ด้วย ถ้ามี optimize มาด้วย อาจจะต้องเปลี่ยนวิธีอ่าน storage แหะ

GateKeeper One

โอเค โจทย์น่าจะเป็นทำให้ entrant มีค่าไม่เท่ากับ address zero ละนะ

โอเค มาเคลียร์ไปทีละด่านกัน เราจะเริ่มจาก gateOne ก่อน

การที่ msg.sender != tx.origin แสดงว่ามี proxy contract มาคั่น ดังนั้น ลองเขียน contract attack ประมาณนี้

test ด่านแรกผ่านละ

โอเค มาดูด่านสองกัน

เราจะทำให้ gas ที่เหลือ ณ ขั้นนี้ หาร 8191 ลงตัว ได้ยังไง ?

คงต้องใส่ gas ไปสักเลขนึง แต่จะกะเลขยังไงละ ?

น่าจะต้องสุ่มไปก่อน แล้ว debug ว่าจากเลขที่ใส่ไป พอไปถึงขั้นนั้นแล้วเหลือ gas เท่าไหร่ จะได้รู้ว่าควรเติมหรือลด gas

โอเค ก่อนอื่นมาแก้ code กันก่อน

โดย (8191 * 3) หาร 8191 ลงตัวอยู่แล้ว ดังนั้นเราแค่ต้องป้อนค่า gas ที่ gasToUse ให้พอดีก็พอ

โอเค ลองสุ่ม 242 ไปก่อน

พอ error แล้วก็มา debug gas กัน

โดยพอ debug ไปเรื่อยๆ จนถึงการหา gasleft()

เราจะดูค่าแก๊สที่เหลือได้จาก stack

โดย 0x5f45 = 24389

แสดงว่าเราต้องเปลี่ยน 242 เป็น 426 เพื่อให้ลงตัว

โอเค ผ่าน gate 2 ละ

มาดู gate 3 กัน

โอเค จาก uint32(uint64(_gateKey)) == uint16(uint64(_gateKey))

การทำ uint64 เป็นแค่การ cast type เฉยๆ และมีทั้งสองข้าง ดังนั้นข้ามไปได้เลย

uint32(x) == uint16(x)

จริงๆใส่ x = 0 ไปก็ผ่านแล้วอะนะ แต่เพื่อมี require อื่นพ่วงตามหลังมาด้วย งั้นหาสูตรที่จะทำให้ค่าใดๆผ่านกัน

โดย int16 bit ใช้ 4 หลักท้ายสุดในเลขฐานสอง

ดังนั้นถ้า input ของเราเป็น bytes8

เราต้องนำ input มา && กับ 0x0000FFFF เพื่อให้ uint32(input) == uint16(input)

โอเค มาทดสอบกัน

ถ้ามี code แบบนี้

แล้วเราใส่ input เต็ม max ลงไป จะ error

แต่ถ้าแก้ให้ code มีการ && 0x0000FFFF

error แหะ ต้องเติมให้ 0x0000FFFF เต็ม 8 bytes

จะได้เป็น && 0xFFFFFFFF0000FFFF เพื่อให้เลขเดิมข้างหน้าไม่เปลี่ยนละนะ

โอเค ผ่าน require แรกละ

มาดู require ที่สองกัน

int32 จะใช้ 5 bit สุดท้ายของเลขฐานสอง

ดังนั้นจะได้ว่า 0x…5 bit สุดท้ายของinput != 0xinput

ตรงนี้เรามี 2 option คือ

  1. เราจะบังคับ bit ที่ 16–6 bit ใด bit หนึ่งให้ไม่เท่ากับให้เป็น 0 ก็ได้

แต่จะเป็นปัญหาใน require ที่ 3 เพราะ input จะเพี้ยน

2. ใส่ input ที่มี bit ที่ 16–6 bit ใด bit หนึ่งไม่เท่ากับ 0 แล้วอย่าไปเปลี่ยน input นั้น

ซึ่งจาก && 0xFFFFFFFF0000FFFF เราก็ไม่ได้เปลี่ยน input นั้น

แสดงว่า require ที่ 2 ไม่ต้องแก้ code เลย

โอเค ไปต่อ require ที่ 3 กัน

ตรง uint32 = uint16 เราเคยทำไปแล้วใน require 1

ง่ายๆคือให้ใส่ gateKey มาเป็น address eoa && 0xFFFFFFFF0000FFFF ละนะ

แก้ code ได้ตามนี้

โอเคมาทดสอบกัน

ใน local chain ผ่านละ

แต่พอลองบน testnet ดันไม่ได้แหะ น่าจะเป็นที่ gas price ไม่เท่ากัน

โอเค น่าเสียดาย แต่ต้องไปข้อต่อไปละ สองข้อนี้ก็ประมาณนี้ละนะ

Referrences

--

--

No responses yet