Giải cấu trúc hợp đồng thông minh (3): Bộ chọn chức năng
猎豹区块链安全
2018-12-17 06:15
本文约4218字,阅读全文需要约17分钟
Cùng nhau mở mã cơ bản của phần "bộ chọn chức năng" của hợp đồng thông minh


Xin chào, chào mừng bạn tiếp tục giải cấu trúc hợp đồng thông minh với tôi. Bài viết này là phần thứ ba của loạt bài, vì vậy nếu bạn chưa đọc các bài viết trước, vui lòng xem qua:

(1)Lời nói đầu: mã cơ bản và phương thức hoạt động;

(2)Tạo và phân tích mã thời gian chạy;

Chúng tôi đang giải cấu trúc mã byte EVM của một hợp đồng thông minh Solidity đơn giản.

Trong bài viết trước, chúng tôi đã xác định rằng mã byte của hợp đồng thông minh được chia thành hai phần: tạo và vận hành, và chúng tôi biết lý do tại sao chúng tôi làm điều này. Sau khi hiểu sâu về phần tạo, đã đến lúc bắt đầu khám phá về phần thời gian chạy lên. Nếu bạn nhìn vào sơ đồ giải cấu trúc, trước tiên chúng ta có thể xem khối phân tách lớn thứ hai được gọi là BasicToken.evm (thời gian chạy).

Điều này có vẻ hơi đáng sợ, vì mã để chạy có kích thước ít nhất gấp bốn lần mã để tạo! Nhưng đừng lo lắng, các kỹ năng mà chúng tôi đã phát triển để hiểu mã EVM trong các bài viết trước, kết hợp với việc sử dụng chiến lược "chia để trị" vững chắc của chúng tôi, sẽ làm cho thử thách này trở nên có hệ thống hơn và thậm chí có thể dễ dàng hơn. Đây mới chỉ là khởi đầu, chúng tôi sẽ tiếp tục xác định các cấu trúc độc lập, và tiếp tục chia nhỏ cho đến khi nó được chia nhỏ thành các vấn đề có thể giải quyết được.

Trước tiên, hãy quay lại trình chỉnh sửa trực tuyến Remix và bắt đầu phiên sửa lỗi với mã byte thời gian chạy. Chúng ta làm điều đó như thế nào Lần trước, chúng tôi đã triển khai hợp đồng thông minh và gỡ lỗi giao dịch triển khai. Lần này, chúng tôi sẽ sử dụng một trong các chức năng để giao tiếp với hợp đồng thông minh đã triển khai và gỡ lỗi giao dịch.

Đầu tiên, hãy nhớ lại hợp đồng thông minh của chúng ta:

Chúng tôi đã kích hoạt Javascript VM với trình biên dịch tối ưu hóa, phiên bản v0.4.24 và 10000 làm nguồn cung cấp ban đầu. Sau khi triển khai hợp đồng thông minh, bạn sẽ thấy nó được liệt kê trong phần "Hợp đồng đã triển khai" của bảng điều khiển "chạy" trên Remix. Nhấp vào nó để mở rộng để xem giao diện của hợp đồng thông minh.

Giao diện này là gì? Đây là danh sách tất cả các phương thức công khai hoặc bên ngoài trong hợp đồng thông minh -Tức là bất kỳ tài khoản Ethereum hoặc hợp đồng thông minh nào cũng có thể tương tác với nó. Các phương thức riêng tư và nội bộ sẽ không được hiển thị ở đây, cách tương tác với các phần cụ thể của mã thời gian chạy hợp đồng thông minh sẽ là trọng tâm của việc giải cấu trúc của bài viết này.

Chúng ta có thể dùng thử bằng cách nhấp vào nút TotalSupply trong bảng "chạy" của Remix. Bạn sẽ thấy phản hồi ngay bên dưới nút, đó là điều chúng tôi mong đợi kể từ khi chúng tôi triển khai hợp đồng thông minh làm nguồn cung cấp mã thông báo ban đầu. Bây giờ, trong bảng điều khiển, nhấp vào nút Gỡ lỗi để bắt đầu phiên gỡ lỗi với giao dịch cụ thể này. Lưu ý rằng sẽ có nhiều nút Gỡ lỗi trong bảng điều khiển; đảm bảo bạn đang sử dụng phiên bản mới nhất.

Trong trường hợp này, chúng tôi đã không gỡ lỗi giao dịch đến địa chỉ 0x0 này, như chúng tôi đã thấy trong bài đăng trước, một hợp đồng thông minh đã được tạo. Thay vào đó, chúng tôi đang gỡ lỗi các giao dịch cho chính hợp đồng thông minh - tức là mã thời gian chạy của nó.

Nếu bảng Hướng dẫn bật lên, bạn sẽ có thể xác minh rằng Remix liệt kê các hướng dẫn giống như trong phần BasicToken.evm (thời gian chạy) của biểu đồ giải cấu trúc. Nếu chúng không khớp, đã xảy ra sự cố. Hãy thử bắt đầu lại và đảm bảo rằng bạn đang sử dụng các cài đặt chính xác như trên.

Điều đầu tiên bạn có thể nhận thấy là trình gỡ lỗi đặt bạn ở hướng dẫn 246 và thanh trượt giao dịch chiếm khoảng 60% mã byte. Tại sao? Bởi vì Remix là một chương trình rất hào phóng, nó đưa bạn trực tiếp đến phần EVM sắp thực thi phần thân của hàm totalSupply. Tuy nhiên, rất nhiều điều đã xảy ra trước đó và đó là điều mà chúng tôi ở đây cần lưu ý. Trên thực tế, chúng ta thậm chí sẽ không xem xét việc thực thi thân hàm trong bài viết này.Mối quan tâm duy nhất của chúng tôi là cách mã EVM do Solidity tạo định tuyến các giao dịch đến, đây là công việc mà chúng tôi sẽ hiểu là "bộ chọn chức năng" của hợp đồng.

Vì vậy, hãy nắm lấy thanh trượt và kéo nó sang bên trái để chúng ta bắt đầu từ hướng dẫn số 0. Như chúng ta đã thấy trước đây, EVM luôn thực thi mã từ lệnh 0, không có ngoại lệ và sau đó chạy qua phần còn lại của mã. Hãy xem qua các opcode để thực hiện opcode này.

Cấu trúc đầu tiên xuất hiện là cấu trúc chúng ta đã thấy trước đây (thực ra chúng ta sẽ thấy rất nhiều):

Hình 1. Con trỏ bộ nhớ trống

Đây là điều mà mã EVM do Solidity tạo sẽ luôn thực hiện trước khi gọi bất kỳ lệnh nào: lưu một điểm trong bộ nhớ để sử dụng sau.

Hãy xem điều gì xảy ra tiếp theo:

Hình 2. Kiểm tra độ dài calldata.

Nếu bạn mở bảng điều khiển Ngăn xếp của Remix trong tab Gỡ lỗi và bỏ qua hướng dẫn từ 5 đến 7, bạn sẽ thấy ngăn xếp hiện chứa các số hai lần. Nếu bạn gặp khó khăn khi đọc các số dài thêm này, hãy lưu ý rằng bạn điều chỉnh độ rộng của bảng gỡ lỗi Remix sao cho các số vừa khít trên một dòng. Cái đầu tiên xuất phát từ một lần đẩy thông thường, nhưng cái thứ hai là kết quả của việc thực thi một opcode, như tờ báo màu vàng nói, không có đối số và trả về kích thước của "dữ liệu đầu vào trong môi trường hiện tại" hoặc những gì chúng ta thường gọi dữ liệu cuộc gọi: 4CALLDATASIZE

calldata là gì? Như đã giải thích trong tài liệu của Solidity về đặc tả ABI, calldata là một khối mã hóa gồm các số thập lục phân chứa thông tin về chức năng hợp đồng thông minh mà chúng ta muốn gọi và các tham số hoặc dữ liệu của nó. Nói một cách đơn giản, nó bao gồm một "id hàm", được tạo bằng cách băm chữ ký của hàm (được cắt bớt thành bốn byte đầu tiên) và sau đó nén dữ liệu tham số. Bạn có thể nghiên cứu chi tiết các liên kết tài liệu nếu muốn, nhưng đừng lo lắng về cách hoạt động của trình bao bọc này đến từng chi tiết nhỏ nhất. Nó được giải thích trong các tài liệu, nhưng thỉnh thoảng hơi khó nắm bắt. Nó dễ dàng hơn nhiều để hiểu nó với các ví dụ thực tế.

Hãy xem calldata này là gì. Mở bảng Dữ liệu cuộc gọi trong trình gỡ lỗi của Remix và xem: 0x18160ddd. Đây chính xác là bốn byte được tạo bằng cách áp dụng thuật toán trên chữ ký hàm keccak256 dưới dạng chuỗi"totalSupply()"và thực hiện phép cắt ngắn nói trên. Vì chức năng cụ thể này không có tham số, nên nó chỉ là: id hàm bốn byte. Khi CALLDATASIZE được gọi, nó chỉ đẩy 4 thứ hai vào ngăn xếp.

Sau đó, hướng dẫn 8 LT được sử dụng để xác minh rằng kích thước calldata nhỏ hơn 4. Nếu có, hai lệnh sau thực hiện lệnh JUMPI 86 (0x0056). Đó là ít hơn bốn byte, vì vậy sẽ không có bước nhảy trong trường hợp này và luồng thực thi sẽ tiếp tục đến hướng dẫn 13. Nhưng trước khi chúng tôi làm điều đó, hãy giả sử chúng tôi gọi hợp đồng thông minh của mình với một calldata trống - cũng tức là, 0x0 thay vì 0x18160ddd. Bạn không thể làm điều đó với Remix btw, nhưng bạn có thể tạo giao dịch theo cách thủ công.

Trong trường hợp này, chúng ta kết thúc ở hướng dẫn 86, về cơ bản đẩy một vài số 0 vào ngăn xếp và đưa chúng vào mã lệnh REVERT. Tại sao? Chà, bởi vì hợp đồng thông minh này không có chức năng dự phòng. Nếu mã byte không nhận ra dữ liệu đến, nó sẽ chuyển luồng sang chức năng dự phòng và nếu cấu trúc đó không "bắt" lệnh gọi, thì cấu trúc khôi phục này sẽ chấm dứt thực thi mà hoàn toàn không có khôi phục.Nếu không có gì để trả lại, thì không có gì phải làm và cuộc gọi được khôi phục hoàn toàn.

Bây giờ, hãy làm điều gì đó thú vị hơn. Quay lại tab Run của Remix, sao chép địa chỉ Tài khoản và sử dụng nó làm đối số để gọi số dư của nó thay vì TotalSupply để gỡ lỗi giao dịch. Đây là một phiên gỡ lỗi hoàn toàn mới; hãy tạm quên TotalSupply đi. Điều hướng đến hướng dẫn 8, CALLDATASIZE hiện đẩy 36 (0x24) vào ngăn xếp. Nếu bạn nhìn vào calldata, thì bây giờ là 0x70a08231000000000000000000000000ca35b7d915458ef540ade6068dfe2f44e8fa733c.

Calldata mới này thực sự rất dễ phân tách: bốn byte đầu tiên 70a08231 là hàm băm của chữ ký, tiếp theo là"balanceOf(address)"32 byte chứa địa chỉ mà chúng tôi chuyển làm đối số. Tại sao lại là 32 byte, nếu địa chỉ Ethereum chỉ dài 20 byte, độc giả tò mò có thể hỏi? ABI luôn sử dụng các "từ" hoặc "khe" 32 byte để giữ các đối số được sử dụng trong các lệnh gọi hàm.

Tiếp tục trong môi trường gọi điện balanceOf của chúng ta, hãy bắt đầu từ nơi chúng ta đã dừng lại ở hướng dẫn 13, không có gì trong ngăn xếp vào thời điểm này. Hướng dẫn 13 sau đó đẩy 0xffffffff vào ngăn xếp và hướng dẫn tiếp theo đẩy số 0x000000001000...000 dài 29 byte vào ngăn xếp. Chúng ta sẽ thấy lý do tại sao trong giây lát. Bây giờ, chỉ cần lưu ý rằng một chứa bốn byte và cái kia chứa bốn byte của 0'.

CALLDATALOAD tiếp theo lấy một đối số (đối số được đẩy lên ngăn xếp ở hướng dẫn 48) và đọc một đoạn 32 byte từ calldata tại vị trí đó, trong trường hợp này ở Yul sẽ là:

calldataload(0)

Về cơ bản đẩy toàn bộ calldata của tôi lên ngăn xếp. Bây giờ đến phần thú vị. DIV sử dụng hai tham số từ ngăn xếp, lấy calldata và chia nó cho số lạ 0x000000001000...000, lọc hiệu quả mọi thứ trừ chữ ký hàm trong calldata và để nó trên ngăn xếp: 0x000...000070a08231. Lệnh tiếp theo sử dụng AND, lệnh này cũng sử dụng hai phần tử trên ngăn xếp: id hàm của chúng ta và số f có bốn byte. Điều này là để đảm bảo rằng hàm băm chữ ký có độ dài chính xác là tám byte và để che dấu bất kỳ thứ gì khác nếu có. Tôi nghĩ rằng, các biện pháp bảo mật được sử dụng bởi Solidity.

Nói tóm lại, chúng ta chỉ kiểm tra xem calldata có quá ngắn không, nếu có thì hoàn nguyên, sau đó cải thiện nó một chút để chúng ta có chức năng của mình trên ngăn xếp,

Thêm vào đó, chúng tôi gần như đã hoàn thành. Phần tiếp theo sẽ dễ hiểu:

Hình 3. Bộ chọn chức năng

Ở hướng dẫn 53, mã đẩy 18160ddd (id hàm totalSuppy) vào ngăn xếp, sau đó sử dụng DUP2 để nhân bản giá trị dữ liệu cuộc gọi đến là 70a08231 hiện nằm ở vị trí thứ hai trên ngăn xếp. Tại sao sao chép? Bởi vì opcode ở hướng dẫn EQ 59 sẽ tiêu thụ hai giá trị từ ngăn xếp và chúng tôi muốn giữ giá trị 70a08231 vì chúng tôi đã gặp sự cố khi trích xuất nó từ calldata.

Bây giờ mã sẽ cố khớp id hàm trong calldata với một trong các id hàm đã biết. Vì 70a08231 xuất hiện, nó sẽ không khớp với 18160ddd, bỏ qua JUMPI ở hướng dẫn 63. Nhưng nó sẽ khớp ở lần kiểm tra tiếp theo và chuyển sang JUMPI ở hướng dẫn 74.

Hãy dành một chút thời gian để quan sát cách các kiểm tra tính bình đẳng này được thực hiện trên từng chức năng công khai hoặc bên ngoài của hợp đồng thông minh.Đây là cốt lõi của bộ chọn chức năng: hoạt động như một loại câu lệnh chuyển đổi chỉ đơn giản là định tuyến việc thực thi đến đúng phần mã của bạn. Đây là "trung tâm" của chúng tôi.

Vì vậy, vì trường hợp cuối cùng là khớp, luồng thực thi đưa chúng ta đến JUMPDEST ở vị trí 130, như chúng ta sẽ thấy trong phần tiếp theo của loạt bài này, là một "trình bao bọc" ABI cho hàm balanceOf. Như chúng ta sẽ thấy, trình bao bọc này sẽ chịu trách nhiệm mở gói dữ liệu của giao dịch để phần thân chức năng sử dụng.

Tiếp tục cố gắng chuyển chức năng sửa lỗi này. Thực sự không có gì bí ẩn đối với bộ chọn tính năng. Đó là một cấu trúc đơn giản nhưng hiệu quả nằm ở cửa của mọi hợp đồng (ít nhất là tất cả những hợp đồng được biên dịch từ Solidity) và chuyển hướng thực thi đến vị trí thích hợp trong mã. Đây là cách Solidity cung cấp mã byte của hợp đồng thông minh với khả năng mô phỏng nhiều điểm vào và do đó tạo giao diện.

Nhìn vào sơ đồ giải cấu trúc, đây là những gì chúng ta vừa giải cấu trúc:

Hình 4. Bộ chọn chức năng và điểm nhập chính cho mã thời gian chạy của hợp đồng thông minh.

Nói chung, trước khi bạn biết điều đó, bạn đã quen thuộc với mã cơ bản của solidity hơn hầu hết mọi người, hãy kiên trì với nó và bạn sẽ có thể mở nó hoàn toàn.


*Bài viết này được xuất bản lần đầu trên phương tiện bởi Alejandro Santander, được dịch và tổ chức bởi Cheetah Blockchain*

Bảo mật chuỗi khối Cheetah dựa trên công nghệ của Kingsoft Internet Security, kết hợp với trí tuệ nhân tạo, nlp và các công nghệ khác, để cung cấp cho người dùng chuỗi khối các dịch vụ bảo mật sinh thái như kiểm toán hợp đồng và phân tích tình cảm.

Trang web chính thức của Ratingtokenhttps://www.ratingtoken.net/?from=z

猎豹区块链安全
作者文库