EVM chuyên sâu-những rủi ro đằng sau vấn đề tầm thường về phân loại hợp đồng
Trong lĩnh vực hợp đồng thông minh,"Máy ảo Ethereum EVM"Và các thuật toán và cấu trúc dữ liệu của nó là những nguyên tắc đầu tiên.
Bài viết này bắt đầu từ lý do tại sao nên phân loại hợp đồng và kết hợp loại tấn công nguy hiểm mà mỗi kịch bản có thể gặp phải, và cuối cùng đưa ra một bộ thuật toán phân tích phân loại hợp đồng tương đối an toàn.
Mặc dù nội dung kỹ thuật cao, nhưng nó cũng có thể được sử dụng làm tài liệu đọc cho các cuộc nói chuyện linh tinh.tiêu đề cấp đầu tiên
1. Tại sao phải phân loại hợp đồng?
Vì nó rất quan trọng nên có thể nói nó là nền tảng của Dapps như sàn giao dịch, ví, trình duyệt blockchain, nền tảng phân tích dữ liệu, v.v.!
Lý do giao dịch là chuyển khoản ERC 20 là do hành vi của anh ta tuân theo tiêu chuẩn ERC 20, ít nhất là:
Trạng thái của giao dịch là THÀNH CÔNG
Địa chỉ Đến là một hợp đồng tuân theo tiêu chuẩn ERC 20
Được gọi là chức năng Chuyển giao, được đặc trưng bởi thực tế là 4 chữ số đầu tiên của CallData giao dịch là 0x a 9059 cbb
Sau khi thực hiện, một sự kiện chuyển được gửi trên địa chỉ Tới
Nếu phân loại sai, hành vi giao dịch sẽ bị đánh giá sai
Với hành vi giao dịch là nền tảng, liệu địa chỉ Đến có thể được phân loại chính xác hay không sẽ dẫn đến một kết luận hoàn toàn khác trong phán đoán về CallData của nó. Đối với Dapp, việc truyền thông tin trong và ngoài chuỗi phụ thuộc nhiều vào việc giám sát các sự kiện giao dịch và cùng một mã sự kiện chỉ có thể được tin cậy nếu nó được gửi trong một hợp đồng đáp ứng các tiêu chuẩn.
Nếu phân loại sai, giao dịch sẽ đi vào lỗ đen do nhầm lẫn
Nếu người dùng chuyển Token vào một hợp đồng nhất định, nếu hợp đồng không có phương thức chức năng đặt trước để chuyển Token, tiền sẽ bị khóa giống như Burn và không thể kiểm soát được
Và hiện tại, một số lượng lớn các dự án đã bắt đầu bổ sung hỗ trợ ví tích hợp, việc quản lý ví cho người dùng là điều tất yếu, cần phải phân loại các hợp đồng được triển khai mới nhất từ chuỗi theo thời gian thực, xem chúng có đáp ứng được không tiêu chuẩn tài sản
Đọc thêm đoạn 1:[Giải thích hợp đồng] CryptoPunk là thị trường giao dịch NFT phi tập trung đầu tiên trên thế giới

2. Phân loại rủi ro như thế nào?
Chuỗi là nơi không có danh tính và không có luật lệ, bạn không thể dừng một giao dịch bình thường, ngay cả khi đó là giao dịch độc hại.
Anh ta có thể là một con sói giả làm bà ngoại, thực hiện hầu hết các hành vi mà bạn mong đợi một bà ngoại sẽ làm, nhưng với mục đích đột nhập vào nhà và cướp.
Công bố tiêu chuẩn nhưng có thể không đạt về thực chất
Một phương pháp phân loại phổ biến là áp dụng trực tiếp tiêu chuẩn EIP-165 để đọc xem địa chỉ có hỗ trợ ERC 20 hay không, v.v. Tất nhiên, đây là một phương pháp hiệu quả, nhưng xét cho cùng, hợp đồng được kiểm soát bởi bên kia, vì vậy một tuyên bố có thể được giả mạo sau khi tất cả.
Truy vấn tiêu chuẩn 165 chỉ là một cách để ngăn tiền chuyển vào lỗ đen với chi phí thấp nhất trong các mã hoạt động hạn chế trên chuỗi.
Đây là lý do tại sao khi chúng tôi phân tích NFT trước đây, chúng tôi đã đề cập cụ thể rằng sẽ có phương pháp SafeTransferFrom trong tiêu chuẩn, trong đó Safe đề cập đến việc sử dụng tiêu chuẩn 165 để xác định rằng bên kia có khả năng chuyển NFT.
Đọc thêm đoạn 2.2:[Giải thích mã nguồn] NFT bạn đã mua chính xác là gì?
Chỉ bằng cách bắt đầu từ mã byte của hợp đồng, thực hiện phân tích tĩnh ở cấp mã nguồn và bắt đầu từ hành vi dự kiến của hợp đồng, nó mới có thể chính xác hơn.
3. Thiết kế sơ đồ phân loại hợp đồng
Tiếp theo, chúng tôi sẽ phân tích một cách có hệ thống kế hoạch tổng thể, chú ý đếnMục tiêu là hai chỉ tiêu cốt lõi “chính xác” và “hiệu quả”.
Bạn phải biết rằng ngay cả khi hướng đi là đúng, thì con đường đến bên kia đại dương cũng không rõ ràng. Điểm dừng đầu tiên để thực hiện phân tích mã byte là lấy mã
3.1.Làm sao để lấy code?
Từ quan điểm đi đến chuỗi, có getCode, một phương thức RPC, có thể lấy mã byte từ địa chỉ được chỉ định trên chuỗi. Về mặt đọc, nó rất nhanh vì codeHash được đặt trong cấu trúc tài khoản của EVM.ở trên cùng.

Nhưng phương pháp này tương đương với việc chỉ lấy một địa chỉ nhất định.Bạn muốn nâng cao hơn nữa độ chính xác và hiệu quả?
Nếu đó là một giao dịch triển khai hợp đồng, làm cách nào để lấy mã đã triển khai ngay sau khi nó được thực thi hoặc ngay cả khi nó vẫn còn trong nhóm bộ nhớ?
Nếu giao dịch ở chế độ nhà máy hợp đồng, có bất kỳ mã nguồn nào trong Calldata của giao dịch không?
Cuối cùng, cách của tôi là phân loại theo kiểu sàng
Đối với các giao dịch không được triển khai theo hợp đồng, hãy sử dụng trực tiếp getCode để lấy các địa chỉ liên quan để phân loại.
Đối với các giao dịch mempool mới nhất, hãy lọc ra các giao dịch có địa chỉ trống và có CallData là mã nguồn với hàm tạo
Đối với giao dịch của chế độ nhà máy hợp đồng, vì hợp đồng được triển khai bởi hợp đồng có thể được tái sử dụng để gọi các hợp đồng khác để thực hiện triển khai, sau đó phân tích đệ quy các giao dịch phụ của giao dịch và ghi lại từng Cuộc gọi có loại là TẠO hoặc TẠO 2 .
Khi tôi thực hiện triển khai demo, tôi thấy rằng thật may mắn là phiên bản rpc hiện nay tương đối cao, vì phần khó nhất trong toàn bộ quá trình là làm thế nào để tìm đệ quy lệnh gọi của loại được chỉ định khi thực thi 3. Phương pháp cấp dưới cùng là để khôi phục bối cảnh thông qua opcode.Tôi đã rất ngạc nhiên!
May mắn thay, có một phương thức debug_traceTransaction trong phiên bản geth hiện tại, phương thức này có thể giúp sắp xếp thông tin ngữ cảnh của mỗi cuộc gọi thông qua mã hoạt động opcode và sắp xếp các trường cốt lõi.
Cuối cùng, có thể thu được mã byte gốc của các chế độ triển khai khác nhau (triển khai trực tiếp, triển khai một lần ở chế độ xuất xưởng, triển khai hàng loạt ở chế độ xuất xưởng).
3.2.Cách phân loại từ mã?
Cách dễ nhất nhưng không an toàn là khớp trực tiếp mã với một chuỗi. Lấy ERC 20 làm ví dụ, hàm tuân thủ tiêu chuẩn có

Sau tên hàm, nó là chữ ký hàm của hàm. Như đã đề cập trong phần phân tích trước, giao dịch phụ thuộc vào việc khớp 4 chữ số đầu tiên của callData để tìm hàm mục tiêu. Đọc thêm:
Do đó, chữ ký của 6 chức năng này phải được lưu trữ trong mã byte của hợp đồng.
tiêu đề phụ
3.3. Cải thiện tỷ lệ chính xác 1- dịch ngược
Phương pháp chính xác hơn nữa là dịch ngược Opcode! Dịch ngược là quá trình chuyển đổi mã byte thu được thành mã opcode và dịch ngược nâng cao hơn là chuyển đổi chúng thành mã giả, điều này có lợi hơn cho việc đọc của con người. Lần này chúng ta không cần. Phương pháp dịch ngược được liệt kê trong phần phụ lục tại cuối bài viết.
solidity (ngôn ngữ cấp cao) -> bytecode (mã byte) -> opcode (mã thao tác)
Chúng ta có thể tìm thấy rõ ràng một tính năng, chữ ký chức năng sẽ được thực thi bởi opcode PUSH 4, vì vậy phương pháp tiếp theo là trích xuất nội dung sau PUSH 4 từ toàn văn và khớp nội dung đó với tiêu chuẩn chức năng.

Tôi cũng đã thực hiện một thử nghiệm hiệu suất đơn giản và tôi phải nói rằng ngôn ngữ Go rất hiệu quả và chỉ mất 220 mili giây để dịch ngược 1 W lần.
Những gì tiếp theo sẽ khó khăn
3.4. Cải thiện tỷ lệ chính xác 2-Tìm khối mã
Tỷ lệ chính xác ở trên đã được cải thiện nhưng chưa đủ, bởi vì nó là tìm kiếm toàn văn PUSH 4, bởi vì chúng ta vẫn có thể xây dựng một biến kiểu byte 4, biến này cũng sẽ kích hoạt lệnh PUSH 4.
Trong lúc túng quẫn, tôi nghĩ đến việc triển khai một số dự án mã nguồn mở, ETL là công cụ đọc dữ liệu trên chuỗi để phân tích, nó sẽ phân tích chuyển ERC 20 và 721 thành các bảng riêng biệt nên nó phải có khả năng phân loại hợp đồng.

Sau khi phân tích, có thể thấy rằng nó dựa trên phân loại khối mã và chỉ xử lý lệnh đẩy 4 trong khối cơ bản đầu tiên[ 0 ]
Sau đó, câu hỏi đặt ra, làm thế nào để đánh giá chính xác khối mã
Khái niệm về khối mã xuất phát từ hai mã thao tác liên tiếp REVERT + JUMPDEST và hai mã thao tác liên tiếp là cần thiết ở đây, bởi vì trong khoảng thời gian mã thao tác của toàn bộ bộ chọn chức năng, nếu có quá nhiều chức năng, logic lật trang sẽ xuất hiện .Khi đó lệnh JUMPDEST cũng sẽ xuất hiện.

3.5. Cải thiện độ chính xác Bộ chọn chức năng 3-Tìm
Chức năng của bộ chọn chức năng là đọc 4 byte đầu tiên của Calldata của giao dịch và khớp nó với chữ ký chức năng hợp đồng được đặt trước trong mã và hỗ trợ lệnh chuyển đến vị trí bộ nhớ được chỉ định bởi phương thức chức năng
Hãy thử thực hiện mô phỏng tối thiểu
Phần này là bộ chọn store(uint 256) và retrieve() của hai hàm và chữ ký có thể được tính là 2 e 64 cec 1, 6057361 d

Sau khi dịch ngược bạn sẽ được chuỗi opcode sau, có thể nói chuỗi này được chia làm 2 phần
phần đầu tiên:
Trong trình biên dịch, chỉ phần chọn chức năng của hợp đồng sẽ lấy nội dung của callData, nghĩa là lấy chữ ký lệnh gọi hàm của CallData của nó, như thể hiện trong hình bên dưới.
Chúng ta có thể thấy hiệu ứng bằng cách mô phỏng các thay đổi vùng bộ nhớ của EVM
phần thứ hai:
Quá trình đánh giá xem nó có khớp với giá trị của bộ chọn hay không
1. Chuyển chữ ký hàm 4 byte (0x 2 e 64 cec 1) của hàm lấy () vào ngăn xếp,
2. Opcode EQ bật 2 biến từ khu vực ngăn xếp, cụ thể là 0x 2 e 64 cec 1 và 0x 6057361 d và kiểm tra xem chúng có bằng nhau không
3. PUSH 2 chuyển 2 byte dữ liệu (0x 003 b ở đây, 59 ở dạng thập phân) vào ngăn xếp và có một bộ đếm chương trình trong khu vực ngăn xếp, chỉ định vị trí của lệnh thực hiện tiếp theo trong mã byte. Ở đây chúng tôi đặt 59 vì đó là nơi mã byte lấy() bắt đầu
4. Đại diện JUMPI"Nếu..., thì nhảy tới...", bật 2 giá trị từ ngăn xếp làm đầu vào và nếu điều kiện là đúng, bộ đếm chương trình sẽ được cập nhật thành 59.
Đây là cách EVM xác định vị trí của mã byte chức năng mà nó cần thực thi dựa trên lệnh gọi hàm trong hợp đồng.
Trên thực tế, nó chỉ là một tập hợp đơn giản các "câu lệnh if" cho mọi chức năng trong hợp đồng và nơi chúng chuyển đến.
4. Tóm tắt kế hoạch
Tóm tắt tổng thể như sau
Mỗi địa chỉ hợp đồng có thể nhận mã byte đã triển khai thông qua rpc getcode hoặc debug_traceTransaction, sử dụng thư viện VM và ASM trong GO và nhận opcode sau khi dịch ngược
Về nguyên tắc hoạt động của EVM, hợp đồng sẽ có những đặc điểm sau
Sử dụng hai opcodes liên tiếp REVERT+JUMPDEST để phân biệt khối mã
Hợp đồng phải có chức năng của bộ chọn chức năng và chức năng này cũng phải có trên khối mã đầu tiên
Trong bộ chọn chức năng, tất cả các phương thức chức năng của nó đều sử dụng PUSH 4 làm mã lệnh,
Trong mã lệnh chứa trong bộ chọn này, sẽ có các liên tiếp PUSH 1 00; CALLDATALOAD; PUSH 1 e 0; SHR; DUP 1 , chức năng cốt lõi là tải dữ liệu callDate và thực hiện các thao tác dịch chuyển, còn các cú pháp khác sẽ không tạo ra
3. Chữ ký chức năng tương ứng được xác định trong eip và có các hướng dẫn rõ ràng bắt buộc và tùy chọn
4.1.Chứng minh tính duy nhất
Tại thời điểm này, chúng ta có thể nói rằng về cơ bản chúng ta đã đạt được một phương pháp phân tích hợp đồng có hiệu quả cao và độ chính xác cao. đã sử dụng REVER+JUMPDEST để phân biệt các khối mã và kết hợp tải và dịch chuyển CallDate không thể tránh khỏi để đưa ra phán đoán duy nhất. Tôi có thể sử dụng hợp đồng solidity để thực hiện một chuỗi opcode tương tự không?
Tôi đã làm một thử nghiệm so sánh.Mặc dù có các phương pháp lấy CallData chẳng hạn như msg.sig từ cấp độ ngữ pháp vững chắc, nhưng các phương pháp triển khai opcode sau khi biên dịch là khác nhau.
5. Tóm tắt
Sau một phân tích hùng hồn như vậy, 3 ngày trôi qua.
Mặc dù nó rất tỉ mỉ, mặc dù nó có thể là một sự sụt giảm đối với các hợp đồng sử dụng byte 4 để gây nhầm lẫn một cách ác ý liệu chúng có đáp ứng các tiêu chuẩn trong cuộc sống hàng ngày hay không.
Vì vậy, trên thực tế, ROI của việc đầu tư vào phân tích trong 3 ngày này là rất thấp. Nhưng trong dòng sông thời gian vô tận, những việc dù có xác suất nhỏ nhất cuối cùng cũng sẽ xảy ra.
Chỉ với nguyên tắc không tin tưởng, chúng ta mới có thể tiến xa hơn trong thế giới web3.
Hôm nay một người bạn hỏi tôi một chủ đề rất đáng suy nghĩ,Mục đích cuối cùng của việc trở thành một KOL trong ngành là gì? Mô hình kinh doanh của bạn là gì?
Dù biết rằng lượng truy cập của các bài viết kỹ thuật luôn ảm đạm nhưng lượng truy cập không phải là mục đích, tôi hy vọng rằng nó sẽ luôn đúng với tầm nhìn ban đầu:
chữ
chữ
Nếu bạn cần các mẫu opcode và mã mẫu dịch ngược Go, bạn có thể lấy chúng bằng cách trả lời "Phân loại hợp đồng" trong nền của tài khoản chính thức.
Thư viện phụ thuộc:
https://pkg.go.dev/github.com/ethereum/go-ethereum@v1.11.6/core/asm#Compiler.Feed
https://pkg.go.dev/github.com/ethereum/go-ethereum@v1.11.6/core/vm
Phân tích nguyên tắc:
https://whileydave.com/2023/01/04/disassembling-evm-bytecode-the-basics/
https://yanniss.github.io/elipmoc-oopsla 22.pdf
https://yanniss.github.io/gigahorse-icse 19.pdf
https://www.evm.codes/? fork=shanghai
https://learnblockchain.cn/article/4800
https://learnblockchain.cn/article/3647
Tham khảo mã nguồn dự án nguồn mở:
https://github.com/blockchain-etl/ethereum-etl/blob/2d a 9 d 050 f 4 ae 4 fa 4 e 818 bbfb 22 d5 cfb 5234 b 2 e 29/ethereumetl/service/eth_contract_service.py#L2 9-L 43
https://github.com/ethereum/evmdasmhttps://github.com/tintinweb/ethereum-dasm/blob/master/ethereum_dasm/evmdasm.py#L3 42
Thích và theo dõi 14, mang lại cho bạn giá trị từ góc độ kỹ thuật


