Ethernaut part 7 (Privacy & Gate keeper I)
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 คือ
- เราจะบังคับ 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 ไม่เท่ากัน
โอเค น่าเสียดาย แต่ต้องไปข้อต่อไปละ สองข้อนี้ก็ประมาณนี้ละนะ