BTC
ETH
HTX
SOL
BNB
ดูตลาด
简中
繁中
English
日本語
한국어
ภาษาไทย
Tiếng Việt

การตรวจสอบช่องโหว่ 0day (zero-day) ของ etherscan

星球君的朋友们
Odaily资深作者
บทความนี้มีประมาณ 4717 คำ การอ่านทั้งหมดใช้เวลาประมาณ 7 นาที
คุณจะทำอย่างไรหากพบช่องโหว่ 0day (zero-day) ใน etherscan?
สรุปโดย AI
คุณจะทำอย่างไรหากพบช่องโหว่ 0day (zero-day) ใน etherscan?

"คุณจะทำอย่างไรถ้าคุณพบ 0 วันใน etherscan ฉันตัดสินใจสร้าง 'เครื่องพินบอล'"—— samczsun หุ้นส่วนการวิจัยกระบวนทัศน์

หมายเหตุ: ผู้เขียนต้นฉบับคือ samczsun

ฉันชอบท้าทายสมมติฐาน

ฉันชอบลองทำสิ่งที่เป็นไปไม่ได้ ค้นหาสิ่งที่คนอื่นพลาด ทำให้พวกเขาประหลาดใจด้วยสิ่งที่พวกเขาไม่เคยเห็น ปีที่แล้ว ฉันได้เขียนคำท้าสำหรับ Paradigm CTF 2021 โดยอ้างอิงจากช่องโหว่ Solidity ที่ซับซ้อนมาก แม้ว่ารูปแบบต่างๆ จะถูกเปิดเผยต่อสาธารณะ แต่ช่องโหว่ที่ฉันพบกลับไม่เคยถูกพูดถึงเลย เป็นผลให้เกือบทุกคนที่พยายามท้าทายมันรู้สึกงุนงงกับธรรมชาติที่ดูเหมือนจะเป็นไปไม่ได้

เรากำลังหารือเกี่ยวกับแผนสำหรับ Paradigm CTF 2022 เมื่อไม่กี่สัปดาห์ก่อน เมื่อ Georgios ทวีตเกี่ยวกับปริศนา และฉันคิดว่าการโพสต์ความท้าทายในวันเดียวกับที่เริ่มการแข่งขันจะดีมาก อย่างไรก็ตาม นี่ไม่ใช่แค่ความท้าทายซ้ำซาก ฉันต้องการบางอย่างจากโลกนี้ บางอย่างที่ไม่มีใครเห็น บางอย่างที่เกินขอบเขตที่ผู้คนจะจินตนาการได้ ฉันต้องการเขียนความท้าทาย Ethereum CTF ครั้งแรกเพื่อใช้ประโยชน์จากช่องโหว่ 0day (zero-day)

1

วิธีสร้างช่องโหว่ 0 วัน (ศูนย์วัน)

ในฐานะนักวิจัยด้านความปลอดภัย เพื่อเพิ่มประสิทธิภาพเวลาของเรา เราได้ตั้งสมมติฐานพื้นฐานบางประการ หนึ่งคือซอร์สโค้ดที่เรากำลังอ่านสร้างสัญญาที่เรากำลังวิเคราะห์ แน่นอนว่า ข้อสันนิษฐานนี้จะคงอยู่ก็ต่อเมื่อเราอ่านซอร์สโค้ดจากแหล่งที่เชื่อถือได้ เช่น Etherscan ดังนั้นหากฉันสามารถหาวิธีให้ Etherscan ตรวจสอบบางสิ่งที่ไม่ถูกต้องได้ ฉันจะสามารถสร้างปริศนาที่คดเคี้ยวรอบๆ ได้

เพื่อหาวิธีใช้ระบบการตรวจสอบสัญญาของ Etherscan ฉันต้องตรวจสอบสัญญาบางรายการ ฉันปรับใช้สัญญาบางอย่างบน Ropsten testnet เพื่อโยนและพยายามตรวจสอบความถูกต้อง ในไม่ช้าฉันเห็นอินเทอร์เฟซด้านล่าง

ฉันเลือกการตั้งค่าที่ถูกต้องและดำเนินการต่อไปยังหน้าถัดไป ที่นี่ฉันถูกขอให้ระบุซอร์สโค้ดสัญญาของฉัน

ฉันป้อนซอร์สโค้ดและคลิกปุ่มยืนยัน และแน่นอนว่าซอร์สโค้ดของฉันแนบมากับสัญญาแล้ว

ตอนนี้ฉันรู้วิธีการทำงานแล้ว ฉันก็เริ่มทดลองกับกระบวนการยืนยันได้ สิ่งแรกที่ฉันลองคือปรับใช้สัญญาใหม่ เปลี่ยน foo เป็น bar และตรวจสอบสัญญาด้วยซอร์สโค้ดดั้งเดิม ไม่น่าแปลกใจที่ Etherscan ปฏิเสธที่จะตรวจสอบสัญญาของฉัน

อย่างไรก็ตาม เมื่อฉันเปรียบเทียบเอาต์พุต bytecode ทั้งสองแบบด้วยตนเอง ฉันสังเกตเห็นบางสิ่งที่แปลกไป รหัสไบต์ของสัญญาควรเป็นเลขฐานสิบหก แต่ดูเหมือนว่าบางส่วนไม่ใช่

ฉันรู้ว่า Solidity ต่อท้ายข้อมูลเมตาของสัญญากับ bytecode ที่ปรับใช้ แต่ฉันไม่เคยพิจารณาจริงๆ ว่าสิ่งนั้นส่งผลต่อการตรวจสอบสัญญาอย่างไร เห็นได้ชัดว่า Etherscan กำลังสแกน bytecode สำหรับข้อมูลเมตาและแทนที่ด้วยเครื่องหมายที่ระบุว่า "อะไรก็ตามในพื้นที่นี้อาจแตกต่างออกไปและเราจะยังคงคิดว่าเป็น bytecode เดียวกัน"

ดูเหมือนว่าจะเป็นโอกาสในการหาประโยชน์ที่อาจเกิดขึ้นจาก 0day (zero-day) หากฉันหลอกให้ Etherscan ตีความข้อมูลที่ไม่ใช่ข้อมูลเมตาเป็นข้อมูลเมตาได้ ฉันจะสามารถปรับแต่ง bytecode ที่ฉันปรับใช้ในภูมิภาคที่มีเครื่องหมาย {ipfs} ในขณะที่ยังคงยืนยันว่าเป็น bytecode ที่ถูกต้อง

วิธีที่ง่ายที่สุดที่ฉันคิดได้คือรวมรหัสไบต์ตามอำเภอใจในธุรกรรมการสร้างคือเข้ารหัสเป็นพารามิเตอร์คอนสตรัคเตอร์ Solidity เข้ารหัสพารามิเตอร์ตัวสร้างโดยการแนบแบบฟอร์มที่เข้ารหัส ABI โดยตรงกับข้อมูลธุรกรรมการสร้าง

อย่างไรก็ตาม Etherscan นั้นฉลาดมากจนไม่รวมพารามิเตอร์คอนสตรัคเตอร์จากการดมข้อมูลเมตาทุกประเภท คุณจะเห็นว่าพารามิเตอร์ตัวสร้างเป็นตัวเอียงเพื่อแสดงว่าแยกจากตัวโค้ด

ซึ่งหมายความว่าฉันต้องหลอกคอมไพเลอร์ Solidity ให้ปล่อยลำดับของไบต์ที่ฉันควบคุมเพื่อให้คล้ายกับข้อมูลเมตาที่ฝังไว้ อย่างไรก็ตาม ดูเหมือนว่าจะเป็นปัญหาที่ยากที่จะเข้าใจ เนื่องจากฉันควบคุมได้น้อยมากว่า opcodes หรือ bytes Solidity ใดที่เลือกใช้โดยไม่มีการโต้เถียงกับคอมไพเลอร์อย่างจริงจัง และซอร์สโค้ดก็ดูน่าสงสัยในภายหลัง

ฉันคิดเกี่ยวกับเรื่องนี้มาระยะหนึ่งจนกระทั่งฉันรู้ว่า: จริงๆแล้วมันค่อนข้างง่ายที่จะทำให้ Solidity ปล่อย (เกือบ) ไบต์โดยพลการ รหัสต่อไปนี้จะทำให้ Solidity ปล่อย 0xAA 32 ไบต์

bytes32 value = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa;

ได้รับการสนับสนุน ฉันรีบเขียนสัญญาขนาดเล็กที่จะพุชชุดของค่าคงที่เพื่อให้ Solidity ปล่อย bytecode ที่ดูเหมือนกับข้อมูลเมตาที่ฝังไว้ทุกประการ

เพื่อความสุขของฉัน Etherscan ทำเครื่องหมายการมีอยู่ของแฮช IPFS ในระหว่างสัญญาของฉัน

ฉันคัดลอกโค้ดไบต์ที่คาดไว้อย่างรวดเร็วและแทนที่แฮช IPFS ด้วยไบต์สุ่ม จากนั้นปรับใช้สัญญาผลลัพธ์ แน่นอนว่า Etherscan พิจารณาธุรกิจแบบไบต์ต่างๆ ตามปกติ และอนุญาตให้ยืนยันสัญญาของฉันได้

ด้วยสัญญานี้ ซอร์สโค้ดแนะนำว่าการเรียก example() ควรส่งคืนออบเจกต์ไบต์อย่างง่าย อย่างไรก็ตาม นี่คือสิ่งที่เกิดขึ้นหากคุณพยายามโทรหามันจริงๆ

$ seth call 0x3cd2138cabfb03c8ed9687561a0ef5c9a153923f 'example()'
seth-rpc: {"id":1,"jsonrpc":"2.0","method":"eth_call","params":[{"data":"0x54353f2f","to":"0x3CD2138CAbfB03c8eD9687561a0ef5C9a153923f"},"latest"]}
seth-rpc: error: code -32000
seth-rpc: error: message stack underflow (5 <=> 16)

ฉันพบช่องโหว่ 0day (zero-day) ใน Etherscan ได้สำเร็จ และตอนนี้ฉันสามารถตรวจสอบสัญญาที่ทำงานแตกต่างไปจากที่ซอร์สโค้ดแนะนำอย่างสิ้นเชิง และตอนนี้ฉันแค่ต้องออกแบบเกมไขปริศนารอบๆ

2

การเริ่มต้นที่ผิดพลาด

เห็นได้ชัดว่าเกมถอดรหัสนี้จะเกี่ยวข้องกับแนวคิดที่ว่าซอร์สโค้ดที่เห็นใน Etherscan ไม่ได้บ่งบอกว่าสัญญาทำงานอย่างไร ฉันยังต้องการให้แน่ใจว่าผู้เล่นไม่สามารถเล่นธุรกรรมซ้ำได้โดยตรง ดังนั้นวิธีแก้ปัญหาจึงต้องไม่ซ้ำกันสำหรับแต่ละที่อยู่ วิธีที่ดีที่สุดคือการขอลายเซ็น

แต่ภายใต้สถานการณ์ใดบ้างที่ผู้เล่นจะถูกขอให้ลงนามในข้อมูลบางอย่าง? การออกแบบครั้งแรกของฉันคือปริศนาง่ายๆ ที่มีฟังก์ชันสัมผัสเดียว ผู้เล่นจะเรียกใช้ฟังก์ชันด้วยอินพุตบางอย่าง ลงนามในข้อมูลเพื่อพิสูจน์ว่าพวกเขาคิดวิธีแก้ปัญหาได้ และหากอินพุตนั้นผ่านการตรวจสอบต่างๆ ทั้งหมด พวกเขาจะถูกทำเครื่องหมายว่าเป็นผู้แก้ปัญหา อย่างไรก็ตาม ในขณะที่ฉันใช้เวลาสองสามชั่วโมงต่อมาในการออกแบบที่สมบูรณ์ ฉันเริ่มไม่พอใจอย่างรวดเร็วกับสิ่งที่เกิดขึ้น มันเริ่มรู้สึกเทอะทะและไม่สง่างามมาก และฉันก็ทนไม่ได้ที่จะไขปริศนาด้วยการออกแบบที่แย่แบบนี้ ทำลาย ช่างเป็นแนวคิดเรื่องช่องโหว่ 0day (zero-day) ที่ยอดเยี่ยม

ต้องยอมรับว่าฉันไม่สามารถทำสิ่งนี้ให้เสร็จภายในวันศุกร์ได้ ดังนั้นฉันจึงตัดสินใจนอนพักผ่อน

3

ปริศนาพินบอล

ฉันยังคงพยายามทำซ้ำในการออกแบบครั้งแรกในช่วงสุดสัปดาห์ที่ผ่านมา แต่ก็ไม่คืบหน้าไปกว่านี้ ราวกับว่าแนวทางปัจจุบันของฉันชนเข้ากับกำแพง และเท่าที่ฉันเกลียดที่จะยอมรับมัน ฉันรู้ว่าฉันอาจต้องเริ่มต้นใหม่หากต้องการสิ่งที่พอใจ

ในที่สุดฉันพบว่าตัวเองกำลังทบทวนปัญหาจากหลักการแรก สิ่งที่ฉันต้องการคือเกมปริศนาที่ผู้เล่นต้องทำการทดสอบความรู้ต่างๆ อย่างไรก็ตาม ฉันไม่ได้อ้างว่าการผ่านการทดสอบความรู้เป็นเงื่อนไขในการชนะ แต่อาจเป็นหนึ่งในหลายเส้นทางที่ผู้เล่นสามารถเลือกได้ บางทีผู้เล่นสามารถสะสมคะแนนตลอดทั้งปริศนา ใช้ประโยชน์จากข้อผิดพลาดนี้เพื่อรับรางวัลบางประเภท เงื่อนไขในการชนะเป็นเพียงคะแนนสูงสุด ดังนั้นจึงส่งเสริมการใช้การหาประโยชน์ทางอ้อม

ฉันจำความท้าทายที่ฉันออกแบบเมื่อปีที่แล้วได้ นั่นคือ Lockbox ซึ่งบังคับให้ผู้เล่นสร้างบล็อกข้อมูลชุดเดียวเพื่อให้เป็นไปตามข้อกำหนดของสัญญาที่แตกต่างกัน 6 ฉบับ สัญญาใช้ข้อจำกัดที่แตกต่างกันกับไบต์เดียวกัน ทำให้ผู้เล่นต้องฉลาดในการสร้างเพย์โหลดของตน ฉันรู้ว่าฉันต้องการทำสิ่งที่คล้ายกันที่นี่ โดยฉันจะขอให้ผู้เล่นส่งข้อมูลหยดเดียว และฉันจะให้คะแนนตามบางส่วนของข้อมูลที่ตรงตามข้อกำหนดบางอย่าง

เมื่อมาถึงจุดนี้เองที่ฉันรู้ว่าฉันกำลังอธิบายถึงพินบูลล์โดยพื้นฐานแล้ว ซึ่งเป็นความท้าทายที่ฉันเผชิญระหว่างรอบชิงชนะเลิศ DEFCON CTF 2020 กลไกของพินบูลล์คือเมื่อคุณดำเนินการไบนารี การดำเนินการจะกระดอนไปรอบ ๆ กราฟโฟลว์ควบคุม เหมือนกับลูกบอลที่กระดอนในเครื่องพินบอล ด้วยการจัดโครงสร้างอินพุตของคุณอย่างถูกต้อง คุณจะสามารถค้นหาส่วนรหัสเฉพาะและได้รับคะแนน แน่นอนว่ามีบั๊กอยู่ แต่พูดตามตรง ฉันลืมไปแล้วว่ามันคืออะไร และฉันจะไม่มองหามันอีก นอกจากนี้ ฉันยังมีช่องโหว่ที่ฉันต้องการใช้ประโยชน์

เนื่องจากฉันกำลังจัดการกับการหาประโยชน์จาก Zero-day ฉันจึงตัดสินใจที่จะไขปริศนานี้ให้เร็วที่สุดเท่าที่จะทำได้ ในท้ายที่สุด ฉันใช้เวลา 2-3 ชั่วโมงเพื่อทบทวนตัวเองอีกครั้งว่าพินบูลล์ทำงานอย่างไร และใช้เวลาอีก 2-3 วันในการปรับใช้อีกครั้ง สิ่งนี้ช่วยแก้ปัญหาวงเล็บเหลี่ยมของปริศนาได้ ตอนนี้ฉันแค่ต้องผสานรวมการหาประโยชน์เข้าไว้ด้วยกัน

4

โจมตีช่องโหว่ซีโร่เดย์

วิธีที่ฉันได้รับ Solidity เพื่อส่งออกไบต์ที่ถูกต้องคือการโหลดค่าคงที่เล็กน้อยและให้ Solidity ออกคำสั่ง PUSH ที่สอดคล้องกัน อย่างไรก็ตาม ค่าคงที่ตามอำเภอใจเช่นนี้อาจเป็นธงสีแดงขนาดใหญ่ และฉันต้องการบางอย่างที่ผสมผสานกันได้ดีขึ้น ฉันยังต้องโหลดค่าคงที่ทั้งหมดในบรรทัดเดียว ซึ่งอธิบายได้ยากในโค้ดจริง

เนื่องจากฉันต้องฮาร์ดโค้ดลำดับเมจิกไบต์สองตัวเท่านั้น (0xa264...1220 และ 0x6473...0033) ฉันจึงตัดสินใจดูว่าฉันสามารถประกบโค้ดระหว่างพวกเขาแทนค่าคงที่ที่สามได้หรือไม่ ในสัญญาที่ปรับใช้ ฉันแค่ต้องแทนที่โค้ดที่ประกบด้วยคำสั่งอื่นๆ

address a = 0xa264...1220;
uint x = 1 + 1 + 1 + ... + 1;
address b = 0x6473...0033;

หลังจากการทดลองบางอย่าง ฉันพบว่าสิ่งนี้เป็นไปได้ แต่ถ้าเปิดใช้งานเครื่องมือเพิ่มประสิทธิภาพเท่านั้น มิฉะนั้น Solidity จะปล่อยรหัสการฆ่าเชื้อที่มีมูลค่ามากเกินไป สิ่งนี้เป็นที่ยอมรับได้ ดังนั้นฉันจึงดำเนินการปรับปรุงโค้ดต่อไป

ฉันสามารถแก้ไขโค้ดได้ภายในสองที่อยู่เท่านั้น แต่มันคงแปลกที่จะเห็นที่อยู่ห้อยต่องแต่งในตอนท้าย ดังนั้นฉันจึงตัดสินใจใช้มันในคำสั่งแบบมีเงื่อนไข ฉันต้องพิสูจน์ความจำเป็นของเงื่อนไขที่สองด้วย ดังนั้นในท้ายที่สุดฉันจึงโหวตคะแนนโบนัสเล็กน้อย ฉันได้ตรวจสอบเงื่อนไขครั้งแรกว่า tx.origin ตรงกับค่าฮาร์ดโค้ดเพื่อสร้างความประทับใจเบื้องต้นว่าไม่จำเป็นต้องติดตามเส้นทางโค้ดนี้อีก

if (tx.origin != 0x13378bd7CacfCAb2909Fa2646970667358221220) return true;
state.rand = 0x40;
state.location = 0x60;
if (msg.sender != 0x64736F6c6343A0FB380033c82951b4126BD95042) return true;
state.baseScore += 1500;

ตอนนี้ซอร์สโค้ดพร้อมแล้ว ฉันต้องเขียนแบ็คดอร์จริงๆ แบ็คดอร์ของฉันจำเป็นต้องยืนยันว่าผู้เล่นเปิดช่องโหว่อย่างถูกต้อง ล้มเหลวโดยไม่ต้องแจ้งให้ทราบหากไม่ทำ และให้รางวัลหากเปิดได้สำเร็จ ฉันต้องการให้แน่ใจว่าช่องโหว่ไม่สามารถเล่นซ้ำได้ง่ายๆ ดังนั้นฉันจึงตัดสินใจเพียงกำหนดให้ผู้เล่นลงนามในที่อยู่ของตนเองและส่งลายเซ็นในการทำธุรกรรม เพื่อเพิ่มสีสัน ฉันตัดสินใจกำหนดให้ลายเซ็นอยู่ที่ออฟเซ็ต 0x44 ในข้อมูลธุรกรรม ซึ่งตามปกติลูกบอลจะเริ่มขึ้น สิ่งนี้จะทำให้ผู้เล่นต้องเข้าใจวิธีการทำงานของการเข้ารหัส ABI และย้ายข้อมูลลูกบอลไปที่อื่นด้วยตนเอง

แต่ที่นี่ฉันพบปัญหาใหญ่: เป็นไปไม่ได้ที่จะใส่ตรรกะทั้งหมดนี้ลงในแอสเซมบลีที่เขียนด้วยมือ 31 ไบต์ โชคดีที่หลังจากคิดอยู่ดีๆ ฉันก็รู้ว่าฉันมีอีก 31 ไบต์ให้ใช้งานด้วย ท้ายที่สุด ข้อมูลเมตาที่ฝังจริงมีแฮช IPFS อื่นที่ Etherscan ละเว้นเช่นกัน

หลังจากเล่นโค้ดอยู่พักหนึ่ง ผมก็มาถึงประตูหลังที่ทำงาน ในแฮช IPFS แรก ฉันจะแสดงที่อยู่ที่เพิ่งพุชทันที จากนั้นข้ามไปที่แฮช IPFS ที่สอง ฉันจะแฮชผู้โทรและตั้งค่าหน่วยความจำ/สแต็กบางส่วนเพื่อโทร ecrecover จากนั้นฉันจะย้อนกลับไปที่แฮช IPFS แรก ซึ่งฉันตั้งค่าสแต็กให้เสร็จและดำเนินการเรียก สุดท้าย ฉันตั้งค่าตัวคูณคะแนนเป็น (msg.sender == ecrecover()) * 0x40 + 1 ซึ่งหมายความว่าไม่จำเป็นต้องมีสาขาเพิ่มเติม

หลังจากเข้ารหัสลับเป็นขนาดที่กำหนด ฉันทวีตที่อยู่ Rinkeby ของฉันเพื่อรับ testnet ETH จาก faucet จากนั้นบอกเป็นนัยกับทุกคนที่รับชมทวีตว่าอาจมีบางอย่างเกิดขึ้น ต่อไป ฉันปรับใช้สัญญาและตรวจสอบความถูกต้อง

สิ่งที่ต้องทำตอนนี้คือรอให้คนที่มีพรหมลิขิตมาค้นพบประตูหลังที่ซ่อนอยู่ในสายตา

หมายเหตุ: Kane Wallmann ผู้พัฒนาซอฟต์แวร์จาก Rocket Pool ได้ไขปริศนานี้แล้วในขณะนี้ นี่คือกระบวนการ: https://medium.com/@kanewallmann_71759/an-untrustworthy-pinball-machine-d9dcd07882c

บทความนี้มาจาก Tao of Yuan Universe ทำซ้ำโดยได้รับอนุญาต

บทความนี้มาจาก Tao of Yuan Universe ทำซ้ำโดยได้รับอนุญาต

ETH
ยินดีต้อนรับเข้าร่วมชุมชนทางการของ Odaily
กลุ่มสมาชิก:
https://t.me/Odaily_News
กลุ่มสนทนา:
https://t.me/Odaily_GoldenApe
บัญชีทางการ:
https://twitter.com/OdailyChina
สารบัญบทความ
อันดับบทความร้อน
Daily
Weekly
ดาวน์โหลดแอพ Odaily พลาเน็ตเดลี่
ให้คนบางกลุ่มเข้าใจ Web3.0 ก่อน
IOS
Android