สอนแชทด้วย ETH
คำนำ
ชื่อเรื่องรอง
คำนำเมื่อไม่นานมานี้ เหตุการณ์การโจรกรรม Poly Network มีการโอนที่อยู่ไปยังที่อยู่ของแฮ็กเกอร์และแจ้ง USDT ในข้อมูลอินพุตว่าถูกระงับ ดังนั้นอย่าใช้ USDT หลังจากที่แฮ็กเกอร์รู้เรื่องนี้ เขาก็โอน 13.37 ETH ไปยังที่อยู่รู้จัก Chuangyu Blockchain Security Lab
Input data
คำตอบสำหรับคุณ
ชื่อเรื่องรอง
ในโปรโตคอล Ethereum เมื่อธุรกรรม (ธุรกรรม) เป็นการสร้างสัญญา ข้อมูลอินพุตคือรหัส EVM ของโปรแกรมเริ่มต้นบัญชี
และเมื่อการทำธุรกรรมเป็นการเรียกข้อความ ข้อมูลเข้าจะเป็นข้อมูลการเรียกฟังก์ชันสัญญา
ภายใต้สถานการณ์ปกติ การส่งข้อความอย่างง่าย เช่น การเรียกใช้ฟังก์ชันการโอน จำเป็นต้องกรอกที่อยู่ _to ที่คุณต้องการโอนและจำนวน _amount ที่คุณต้องการโอน ข้อมูลพื้นฐานเหล่านี้จะรวมอยู่ในข้อมูลอินพุต
มาทำความเข้าใจโครงสร้างของข้อมูลอินพุตเมื่อมีการเรียกข้อความผ่านการวิเคราะห์เฉพาะของธุรกรรมการโอนที่เรียกสัญญา
แบบวิเคราะห์:
รูปแบบเดิม:
เราแบ่งข้อมูลอินพุตดั้งเดิมออกเป็นสามส่วนเพื่อการวิเคราะห์:
0xa9059cbb: ตัวระบุฟังก์ชัน
00000000000000000000000345d8e3a1f62ee6b1d483890976fd66168e390f2: พารามิเตอร์แรกคือที่อยู่ซึ่งเป็นที่อยู่ที่คุณต้องการถ่ายโอนและจะถูกเติมเป็น 32 ไบต์ซึ่งก็คือ 64 อักขระเลขฐานสิบหก
0000000000000000000000000000000000000000054b7d8ed70650b290000: พารามิเตอร์ที่สองคือค่า ซึ่งเป็นจำนวนที่คุณต้องการถ่ายโอน และจะถูกเติมเป็น 32 ไบต์ นั่นคือ 64 อักขระเลขฐานสิบหก
จากการวิเคราะห์เปรียบเทียบ เราพบว่าโครงสร้างพื้นฐานของข้อมูลอินพุตคือตัวระบุฟังก์ชัน + พารามิเตอร์
ตัวระบุฟังก์ชัน"transfer(adddress,uint256)"ตัวระบุฟังก์ชันในที่นี้คือตัวเลือกฟังก์ชัน ตามเอกสารทางการ ตัวเลือกฟังก์ชันคือ 4 ไบต์แรกของแฮช Keccak (SHA-3) ของลายเซ็นฟังก์ชัน
เราสามารถส่งรหัส bytess4(keccake256(
)) หรือเครื่องมือออนไลน์เพื่อรับลายเซ็นของฟังก์ชันนี้
รูปด้านล่างแสดงให้เห็นว่าสี่ไบต์แรกของผลลัพธ์การเข้ารหัส (a9059cbb) สอดคล้องกับตัวระบุฟังก์ชันในข้อมูลอินพุต
สาเหตุที่ลายเซ็นของฟังก์ชันถูกตัดเหลือสี่ไบต์คือการพิจารณาต้นทุนแก๊ส
ในการทำธุรกรรม 0 ไบต์ต้องจ่ายแก๊ส 4 ไบต์ ในขณะที่ไบต์ที่ไม่ใช่ศูนย์ต้องการแก๊ส 68 ไบต์ ซึ่งเป็น 17 เท่าของ 0 ไบต์
สตริงสุ่ม 32 ไบต์ที่สร้างขึ้นในการเข้ารหัส SHA-3 มีแนวโน้มที่จะมีไบต์ที่ไม่ใช่ศูนย์มากกว่า ดังนั้นต้นทุนโดยประมาณคือ 32x68=2176 แก๊ส และต้นทุนการตัดทอนประมาณ 4x68=272 แก๊ส ซึ่งแสดงว่าถูกตัดเหลือสี่ ไบต์ ประหยัดค่าน้ำมันได้ประมาณ 8 เท่า
หน้าที่ของตัวระบุฟังก์ชันคือการระบุว่าฟังก์ชันใดที่จะเรียกใช้ ในสัญญาเดียวกัน ความน่าจะเป็นที่ 4 ไบต์แรกของลายเซ็น SHA-3 ของสองฟังก์ชันที่ต่างกันเหมือนกันมีน้อยมาก ดังนั้น การตัดทอนให้เหลือสี่ไบต์จะไม่ ส่งผลต่อการเรียกใช้ฟังก์ชันจริง
พารามิเตอร์
สรุปประเภทสแตติกทั่วไป: uint, bool, Address, bytes[0-32], ประเภทอาร์เรย์ไดนามิก: bytes, string, address[], bytes32[].....
ประเภทคงที่
เราใช้ฟังก์ชันการเข้ารหัส ABI ของ pyethereum เพื่อศึกษาวิธีการเข้ารหัสข้อมูลประเภทต่างๆ
import rlp
from ethereum.abi import encode_abi
ประเภทคงที่
> encode_abi(["address", "uint256"],
[345d8e3a1f62ee6b1d483890976fd66168e390f2,1]).hex()
000000000000000000000000345d8e3a1f62ee6b1d483890976fd66168e390f2
0000000000000000000000000000000000000000000000000000000000000001
นำเข้าฟังก์ชัน encode_abi ก่อน
> encode_abi(["int8[3]"],[[1, 2, 3]).hex()
ลองใช้ฟังก์ชัน Transfer(address,uint 256) เป็นตัวอย่าง
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000000000000000000000000000000000000000000003
อาร์เรย์ความยาวคงที่ซึ่งมีขนาดเล็กกว่า 32 ไบต์จะถูกเติมเป็น 32 ไบต์โดยอัตโนมัติ:
// ป้อนอัตโนมัติ 0
> encode_abi(
["uint256[]", "uint256[]", "uint256[]"],[[0xa1, 0xa2, 0xa3], [0xb1, 0xb2, 0xb3], [0xc1, 0xc2, 0xc3]]
).hex()
ประเภทไดนามิก
การเข้ารหัสประเภทไดนามิกนั้นซับซ้อนกว่าเล็กน้อยและจำเป็นต้องคำนวณออฟเซ็ตก่อนสำหรับการประมวลผล placeholder ลองใช้ตัวอย่างง่ายๆ เพื่ออธิบาย
// ออฟเซ็ตของพารามิเตอร์ 1: 32*3=96 เลขฐานสิบหก 0x6000000000000000000000000000000000000000000000000000000000000060
// ออฟเซ็ตของพารามิเตอร์ 2 = ออฟเซ็ตของพารามิเตอร์ 1 + ความยาวส่วนข้อมูลของพารามิเตอร์ 1 = 96+32*4=224 เลขฐานสิบหก 0xE000000000000000000000000000000000000000000000000000000000000000000000
// ออฟเซ็ตของพารามิเตอร์ 3 = ออฟเซ็ตของพารามิเตอร์ 2 + ความยาวส่วนข้อมูลของพารามิเตอร์ 2 = 224+32*4=352 เลขฐานสิบหก 0x16000000000000000000000000000000000000000000000000000000000000160
// ข้อมูลของพารามิเตอร์ 1 เริ่มส่งผ่านที่ออฟเซ็ต 0x60
0000000000000000000000000000000000000000000000000000000000000003//องค์ประกอบ
0000000000000000000000000000000000000000000000000000000000000a1//องค์ประกอบอาร์เรย์ตัวแรก
0000000000000000000000000000000000000000000000000000000000000a2//องค์ประกอบอาร์เรย์ที่สอง
0000000000000000000000000000000000000000000000000000000000000003
00000000000000000000000000000000000000000000000000000000000000b1
00000000000000000000000000000000000000000000000000000000000000b2
00000000000000000000000000000000000000000000000000000000000000b3
0000000000000000000000000000000000000000000000000000000000000a3//องค์ประกอบอาร์เรย์ที่สาม
0000000000000000000000000000000000000000000000000000000000000003
00000000000000000000000000000000000000000000000000000000000000c1
00000000000000000000000000000000000000000000000000000000000000c2
00000000000000000000000000000000000000000000000000000000000000c3
// ตำแหน่ง 0xe0 ข้อมูลสำหรับพารามิเตอร์ 2
// ตำแหน่ง 0x160 ข้อมูลสำหรับพารามิเตอร์ 3
การโจมตีที่อยู่สั้น
หลังจากการวิเคราะห์ครั้งก่อน เมื่อความยาวของประเภทคงที่ เช่น ที่อยู่น้อยกว่า 32 ไบต์ EVM จะเติมความยาวเป็น 32 ไบต์ตามกฎ หากที่อยู่ของการถ่ายโอนลงท้ายด้วย 00 เช่น 0x641988625108585185752230bde001b3ebd0fc00 ให้ลบ ศูนย์สองตัวหลังที่อยู่เมื่อถ่ายโอน EVM จะยังคงคิดว่า address_to เป็น 32 บิต ดังนั้นจะใช้ 0 จากบิตสูงของ _value เพื่อเสริม และจำนวนบิตในจำนวนจะเป็นอีกสองบิต นั่นคือ มันจะ คูณด้วย 256
ขั้นตอนการโจมตีมีดังนี้:
ลบ 0 ในไบต์สุดท้ายของที่อยู่ถ่ายโอนที่เป็นอันตราย
000000000000000000000000641988625108585185752230bde001b3ebd0fc
ตัวระบุฟังก์ชัน: a9059cbb
00000000000000000000000000000000000000000000000000000000000000001
ที่อยู่การโอน:
จำนวนเงินโอน:
เนื่องจากกฎการกรอกข้อมูลของ EVM ผลลัพธ์การแยกวิเคราะห์คือ: 0xa9059cbb00000000000000000000000641988625108585185752230bde001b3ebd0fc00000000000000000000000000000000000000 00 0000000000000000000000000100
หลังจากแยกย่อย เราพบว่าจำนวนการโอนเพิ่มขึ้นสองหลัก นั่นคือ หนึ่งไบต์ ซึ่งเท่ากับ 256 เท่าของจำนวนการโอนเดิม
000000000000000000000000641988625108585185752230bde001b3ebd0fc00
ที่อยู่การโอน:
00000000000000000000000000000000000000000000000000000000000000100
จำนวนเงินโอน:
ชื่อเรื่องรอง
วิธีแนบข้อมูลกับข้อมูลที่ป้อน
ช่องข้อมูลอินพุตสำหรับธุรกรรมการโอนโดยตรงใน Ethereum ไม่มีเนื้อหาตามค่าเริ่มต้น แต่เราสามารถใช้ "ฟังก์ชันแชท" ที่จุดเริ่มต้นของบทความได้โดยการตั้งค่ากระเป๋าเงิน
เราใช้กระเป๋าเงิน MetaMask เป็นตัวอย่างเพื่อแสดงวิธีการแนบข้อมูลเพิ่มเติมบางอย่างกับฟิลด์ข้อมูลอินพุตผ่านการถ่ายโอน
3. หลังจากการถ่ายโอนสำเร็จ คุณสามารถดูข้อมูลที่แนบมาใน etherscan
สรุป
ชื่อเรื่องรอง

