В области смарт-контрактов «Ethereum Virtual Machine EVM» и ее алгоритмы и структуры данных являются первыми принципами.
Эта статья начинается с того, почему контракты должны быть классифицированы, и комбинирует, с какими злонамеренными атаками может столкнуться каждый сценарий, и, наконец, дает набор относительно безопасных алгоритмов анализа классификации контрактов.
**Хотя техническое содержание высокое, его также можно использовать в качестве материала для чтения для разных разговоров.**Взгляните на темный лес игр между децентрализованными системами.
1. Почему контракты следует классифицировать?
Поскольку это так важно, можно сказать, что это краеугольный камень Dapps, таких как биржи, кошельки, браузеры блокчейнов, платформы анализа данных и т. д.!
Причина, по которой транзакция является переводом ERC20, заключается в том, что его поведение соответствует стандарту ERC20, по крайней мере:
Статус транзакции — успех
Адрес «Кому» — это контракт, соответствующий стандарту ERC20.
Функция Transfer вызывается, и характеристика в том, что первые 4 цифры CallData транзакции 0xa9059cbb
После выполнения на адрес To отправляется событие Transfer
Если классификация неверна, поведение транзакции будет оценено неверно
С учетом поведения транзакции в качестве краеугольного камня вопрос о том, можно ли точно классифицировать адрес Кому, приведет к совершенно другому выводу в отношении его CallData. Для Dapp передача информации в цепочке и за ее пределами сильно зависит от мониторинга событий транзакций, и одному и тому же коду события можно доверять только в том случае, если он отправляется в контракте, который соответствует стандартам.
Если классификация неверна, транзакция по ошибке уйдет в черную дыру
Если пользователь переводит Токен в определенный контракт, если в контракте нет предустановленного функционального метода для передачи Токенов, средства будут заблокированы так же, как при сжигании, и их нельзя будет контролировать.
И теперь, когда большое количество проектов начали добавлять встроенную поддержку кошелька, неизбежно управление кошельками для пользователей.Необходимо классифицировать последние развернутые контракты из цепочки в реальном времени в любое время, могут ли они соответствовать стандарты активов.
2. Каковы риски классификации?
**Сеть — это место, где нет ни идентичности, ни верховенства права.Вы не можете остановить обычную транзакцию, даже если она злонамеренная. **
Он может быть волком, притворяющимся бабушкой, совершая большинство действий, которые вы ожидаете от бабушки, но с целью проникнуть в дом и ограбить.
** Заявленный стандарт, но на самом деле может не соответствовать **
Распространенным методом классификации является прямое использование стандарта EIP-165 для определения того, поддерживает ли адрес ERC20 и т. д. Конечно, это эффективный метод, но в конце концов договор контролируется другой стороной, поэтому заявление может быть подделано ведь.
Стандартный запрос 165 — это всего лишь метод предотвращения перевода средств в «черные дыры» с наименьшей стоимостью среди ограниченных кодов операций в цепочке.
Вот почему, когда мы анализировали NFT ранее, мы специально упомянули, что в стандарте будет метод SafeTransferFrom, где Safe относится к использованию стандарта 165 для определения того, что другая сторона имеет возможность передавать NFT.
Только начиная с байт-кода контракта, выполняя статический анализ на уровне исходного кода и исходя из ожидаемого поведения контракта, он может быть более точным.
3. Разработка схемы классификации контрактов
Далее мы систематически проанализируем общий план и отметим, что нашей конечной целью являются два основных показателя «точность» и «эффективность». **
Вы должны знать, что даже если направление правильное, путь до другой стороны океана не ясен.Первой остановкой для анализа байт-кода является получение кода
3.1 Как получить код?
С точки зрения выхода в цепочку есть метод RPC getCode, который может получить байткод с указанного в цепочке адреса, очень быстрый в плане чтения, т. к. codeHash помещается в структуру аккаунта EVM в самом верху.
Но этот метод равносилен получению одного только определенного адреса.Хотите еще больше повысить точность и эффективность?
Если это транзакция развертывания контракта, как получить развернутый код сразу после его выполнения или даже когда он все еще находится в пуле памяти?
Если транзакция находится в режиме фабрики контрактов, есть ли исходный код в Calldata транзакции?
В конце концов, мой способ состоит в том, чтобы классифицировать в решетоподобном режиме.
Для транзакций, не развернутых по контракту, напрямую используйте getCode для получения задействованных адресов для классификации.
Для последних транзакций пула памяти отфильтруйте транзакции, чей адрес to пуст и чьи CallData являются исходным кодом с помощью конструктора.
Для транзакции режима фабрики контрактов, поскольку контракт, развернутый контрактом, может быть повторно использован для вызова других контрактов для выполнения развертывания, он будет рекурсивно анализировать подтранзакции транзакции и записывать каждый вызов, тип которого CREATE. или СОЗДАТЬ2 .
Когда я сделал демонстрационную реализацию, я обнаружил, что версия rpc сейчас относительно высока, потому что самая сложная часть всего процесса — это как рекурсивно найти вызов указанного типа при выполнении 3. Метод нижнего уровня — восстановить контекст через опкод.Я был ошеломлен!
К счастью, в текущей версии geth есть метод debug_traceTransaction, который может помочь отсортировать контекстную информацию каждого вызова в коде операции кода операции и отсортировать основные поля.
В итоге можно получить исходные байт-коды различных режимов развертывания (прямое развертывание, одиночное развертывание в фабричном режиме, пакетное развертывание в фабричном режиме).
3.2 Как классифицировать по коду?
Самый простой, но небезопасный способ — это прямое сопоставление строк с кодом.На примере ERC20 функция, соответствующая стандарту, имеет
После имени функции следует сигнатура функции. Как упоминалось в предыдущем анализе, транзакция зависит от совпадения первых 4 цифр callData, чтобы найти целевую функцию. Дальнейшее чтение:
Следовательно, подписи этих 6 функций должны храниться в байт-коде контракта.
Конечно, этот способ очень быстрый и можно найти все 6, но небезопасный фактор в том, что если я воспользуюсь контрактом солидности и спроектирую переменную со значением хранилища 0x18160ddd, то он подумает, что у меня есть эта функция.
3.3 Повышение точности 1- декомпиляция
Еще один точный метод — декомпилировать Opcode! Декомпиляция — это процесс преобразования полученных байт-кодов в коды операций, а более продвинутая декомпиляция — преобразование их в псевдокоды, что более удобно для чтения человеком.На этот раз нам это не нужно.Метод декомпиляции указан в приложении на конец статьи.
Мы можем явно найти функцию, сигнатура функции будет выполняться опкодом PUSH4, поэтому дальнейший метод — извлечь содержимое после PUSH4 из полного текста и сопоставить его со стандартом функции.
Я также провел простой эксперимент с производительностью, и я должен сказать, что язык Go очень эффективен, и для 10 000 раз декомпиляции требуется всего 220 мс.
Дальше будет сложно
3.4 Повышение точности 2-найти кодовый блок
Приведенный выше уровень точности был улучшен, но этого недостаточно, потому что это полнотекстовый поиск PUSH4, потому что мы все еще можем создать переменную типа byte4, которая также вызовет команду PUSH4.
Когда заморачивался, думал о реализации каких-то опенсорсных проектов.ETL это инструмент чтения данных по цепочке для анализа.Он будет анализировать передачу ERC20 и 721 в отдельные таблицы,поэтому должен иметь возможность классифицировать контракты.
После анализа можно обнаружить, что он основан на классификации блоков кода и обрабатывает только первые базовые_блоки. [0] Инструкция push4 в
** Возникает вопрос, как точно оценить кодовый блок **
Концепция блока кода исходит из двух последовательных кодов операций REVERT + JUMPDEST, Здесь должно быть два последовательных кода операций, потому что в интервале кода операции всего селектора функций, если функций слишком много, появится логика перелистывания страниц. Затем также появится команда JUMPDEST.
3.5 Повышение точности 3-Переключатель функций поиска
Функция селектора функций состоит в том, чтобы прочитать первые 4 байта Calldata транзакции и сопоставить их с сигнатурой контрактной функции, заданной в коде, и помочь инструкции перейти к ячейке памяти, указанной методом функции.
Давайте попробуем минимальное фиктивное выполнение
Эта часть представляет собой хранилище селекторов (uint 256) и извлечение() двух функций, а сигнатура может быть рассчитана как 2e64cec1, 6057361d.
После декомпиляции вы получите следующую строку кода операции, которая, можно сказать, разделена на две части.
первая часть:
В компиляторе только часть контракта, посвященная селектору функций, получит содержимое callData, что означает получение сигнатуры вызова функции ее CallData, как показано на рисунке ниже.
Мы можем увидеть эффект, моделируя изменение пула памяти EVM.
Вторая часть:
Процесс оценки того, соответствует ли он значению селектора
Передайте 4-байтовую сигнатуру функции (0x2e64cec1) функции retrieve() в стек,
Код операции EQ извлекает 2 переменные из области стека, а именно 0x2e64cec1 и 0x6057361d, и проверяет, равны ли они.
PUSH2 передает в стек 2 байта данных (здесь 0x003b, в десятичном виде 59. В области стека находится программный счетчик, который указывает позицию следующей команды выполнения в байт-коде. Здесь мы устанавливаем 59, потому что именно здесь начинается байт-код retrieve().
JUMPI расшифровывается как «Перейти к, если...», он извлекает 2 значения из стека в качестве входных данных, и если условие истинно, счетчик программ будет обновлен до 59.
Вот как EVM определяет расположение байт-кода функции, которую необходимо выполнить, на основе вызова функции в контракте.
На самом деле это всего лишь простой набор операторов if для каждой функции в контракте и того, куда они переходят.
4. Резюме схемы
В общем кратко так
Каждый адрес контракта может получить байт-код после развертывания через rpcgetcode или debug_traceTransaction, используя библиотеки VM и ASM в GO, и получить опкод после декомпиляции.
В принципе работы EVM контракт будет иметь следующие характеристики
Используйте REVERT+JUMPDEST в качестве отличия блока кода
Контракт должен иметь функцию селектора функций, и эта функция также должна быть в первом кодовом блоке
В селекторе функций все методы функций используют PUSH4 в качестве кода операции,
В опкоде, содержащемся в этом селекторе, будут последовательно PUSH1 00;CALLDATALOAD;PUSH1 e0;SHR;DUP1.Основная функция — загрузка данных callDate и выполнение операций смещения.Из функции контракта другой синтаксис не сгенерируется
Соответствующая сигнатура функции определена в eip, и есть обязательные и необязательные четкие инструкции
4.1 Доказательство уникальности
На данный момент можно сказать, что высокоэффективный и высокоточный метод анализа контрактов в основном реализован.Конечно, поскольку он так долго был строгим, мы могли бы быть более строгими.В приведенной выше схеме мы используйте REVER + JUMPDEST, чтобы блоки кода различались, и объедините неизбежную загрузку и смещение CallDate, чтобы сделать уникальное суждение.Существует ли, что я могу использовать контракт Solidity для реализации подобной последовательности кода операции?
Я провел контрольный эксперимент.Хотя есть методы получения CallData типа msg.sig с уровня грамматики солидности, методы реализации опкода после компиляции разные.
Посмотреть Оригинал
Содержание носит исключительно справочный характер и не является предложением или офертой. Консультации по инвестициям, налогообложению или юридическим вопросам не предоставляются. Более подробную информацию о рисках см. в разделе «Дисклеймер».
Углубленный EVM - риски, связанные с тривиальной классификацией контрактов
В области смарт-контрактов «Ethereum Virtual Machine EVM» и ее алгоритмы и структуры данных являются первыми принципами.
Эта статья начинается с того, почему контракты должны быть классифицированы, и комбинирует, с какими злонамеренными атаками может столкнуться каждый сценарий, и, наконец, дает набор относительно безопасных алгоритмов анализа классификации контрактов.
**Хотя техническое содержание высокое, его также можно использовать в качестве материала для чтения для разных разговоров.**Взгляните на темный лес игр между децентрализованными системами.
1. Почему контракты следует классифицировать?
Поскольку это так важно, можно сказать, что это краеугольный камень Dapps, таких как биржи, кошельки, браузеры блокчейнов, платформы анализа данных и т. д.!
Причина, по которой транзакция является переводом ERC20, заключается в том, что его поведение соответствует стандарту ERC20, по крайней мере:
Если классификация неверна, поведение транзакции будет оценено неверно
С учетом поведения транзакции в качестве краеугольного камня вопрос о том, можно ли точно классифицировать адрес Кому, приведет к совершенно другому выводу в отношении его CallData. Для Dapp передача информации в цепочке и за ее пределами сильно зависит от мониторинга событий транзакций, и одному и тому же коду события можно доверять только в том случае, если он отправляется в контракте, который соответствует стандартам.
Если классификация неверна, транзакция по ошибке уйдет в черную дыру
Если пользователь переводит Токен в определенный контракт, если в контракте нет предустановленного функционального метода для передачи Токенов, средства будут заблокированы так же, как при сжигании, и их нельзя будет контролировать.
И теперь, когда большое количество проектов начали добавлять встроенную поддержку кошелька, неизбежно управление кошельками для пользователей.Необходимо классифицировать последние развернутые контракты из цепочки в реальном времени в любое время, могут ли они соответствовать стандарты активов.
2. Каковы риски классификации?
**Сеть — это место, где нет ни идентичности, ни верховенства права.Вы не можете остановить обычную транзакцию, даже если она злонамеренная. **
Он может быть волком, притворяющимся бабушкой, совершая большинство действий, которые вы ожидаете от бабушки, но с целью проникнуть в дом и ограбить.
** Заявленный стандарт, но на самом деле может не соответствовать **
Распространенным методом классификации является прямое использование стандарта EIP-165 для определения того, поддерживает ли адрес ERC20 и т. д. Конечно, это эффективный метод, но в конце концов договор контролируется другой стороной, поэтому заявление может быть подделано ведь.
Стандартный запрос 165 — это всего лишь метод предотвращения перевода средств в «черные дыры» с наименьшей стоимостью среди ограниченных кодов операций в цепочке.
Вот почему, когда мы анализировали NFT ранее, мы специально упомянули, что в стандарте будет метод SafeTransferFrom, где Safe относится к использованию стандарта 165 для определения того, что другая сторона имеет возможность передавать NFT.
Только начиная с байт-кода контракта, выполняя статический анализ на уровне исходного кода и исходя из ожидаемого поведения контракта, он может быть более точным.
3. Разработка схемы классификации контрактов
Далее мы систематически проанализируем общий план и отметим, что нашей конечной целью являются два основных показателя «точность» и «эффективность». **
Вы должны знать, что даже если направление правильное, путь до другой стороны океана не ясен.Первой остановкой для анализа байт-кода является получение кода
3.1 Как получить код?
С точки зрения выхода в цепочку есть метод RPC getCode, который может получить байткод с указанного в цепочке адреса, очень быстрый в плане чтения, т. к. codeHash помещается в структуру аккаунта EVM в самом верху.
Но этот метод равносилен получению одного только определенного адреса.Хотите еще больше повысить точность и эффективность?
Если это транзакция развертывания контракта, как получить развернутый код сразу после его выполнения или даже когда он все еще находится в пуле памяти?
Если транзакция находится в режиме фабрики контрактов, есть ли исходный код в Calldata транзакции?
В конце концов, мой способ состоит в том, чтобы классифицировать в решетоподобном режиме.
Когда я сделал демонстрационную реализацию, я обнаружил, что версия rpc сейчас относительно высока, потому что самая сложная часть всего процесса — это как рекурсивно найти вызов указанного типа при выполнении 3. Метод нижнего уровня — восстановить контекст через опкод.Я был ошеломлен!
К счастью, в текущей версии geth есть метод debug_traceTransaction, который может помочь отсортировать контекстную информацию каждого вызова в коде операции кода операции и отсортировать основные поля.
В итоге можно получить исходные байт-коды различных режимов развертывания (прямое развертывание, одиночное развертывание в фабричном режиме, пакетное развертывание в фабричном режиме).
3.2 Как классифицировать по коду?
Самый простой, но небезопасный способ — это прямое сопоставление строк с кодом.На примере ERC20 функция, соответствующая стандарту, имеет
После имени функции следует сигнатура функции. Как упоминалось в предыдущем анализе, транзакция зависит от совпадения первых 4 цифр callData, чтобы найти целевую функцию. Дальнейшее чтение:
Следовательно, подписи этих 6 функций должны храниться в байт-коде контракта.
Конечно, этот способ очень быстрый и можно найти все 6, но небезопасный фактор в том, что если я воспользуюсь контрактом солидности и спроектирую переменную со значением хранилища 0x18160ddd, то он подумает, что у меня есть эта функция.
3.3 Повышение точности 1- декомпиляция
Еще один точный метод — декомпилировать Opcode! Декомпиляция — это процесс преобразования полученных байт-кодов в коды операций, а более продвинутая декомпиляция — преобразование их в псевдокоды, что более удобно для чтения человеком.На этот раз нам это не нужно.Метод декомпиляции указан в приложении на конец статьи.
Solidity (язык высокого уровня) -> bytecode (байткод) -> opcode (код операции)
Мы можем явно найти функцию, сигнатура функции будет выполняться опкодом PUSH4, поэтому дальнейший метод — извлечь содержимое после PUSH4 из полного текста и сопоставить его со стандартом функции.
Я также провел простой эксперимент с производительностью, и я должен сказать, что язык Go очень эффективен, и для 10 000 раз декомпиляции требуется всего 220 мс.
Дальше будет сложно
3.4 Повышение точности 2-найти кодовый блок
Приведенный выше уровень точности был улучшен, но этого недостаточно, потому что это полнотекстовый поиск PUSH4, потому что мы все еще можем создать переменную типа byte4, которая также вызовет команду PUSH4.
Когда заморачивался, думал о реализации каких-то опенсорсных проектов.ETL это инструмент чтения данных по цепочке для анализа.Он будет анализировать передачу ERC20 и 721 в отдельные таблицы,поэтому должен иметь возможность классифицировать контракты.
После анализа можно обнаружить, что он основан на классификации блоков кода и обрабатывает только первые базовые_блоки. [0] Инструкция push4 в
** Возникает вопрос, как точно оценить кодовый блок **
Концепция блока кода исходит из двух последовательных кодов операций REVERT + JUMPDEST, Здесь должно быть два последовательных кода операций, потому что в интервале кода операции всего селектора функций, если функций слишком много, появится логика перелистывания страниц. Затем также появится команда JUMPDEST.
3.5 Повышение точности 3-Переключатель функций поиска
Функция селектора функций состоит в том, чтобы прочитать первые 4 байта Calldata транзакции и сопоставить их с сигнатурой контрактной функции, заданной в коде, и помочь инструкции перейти к ячейке памяти, указанной методом функции.
Давайте попробуем минимальное фиктивное выполнение
Эта часть представляет собой хранилище селекторов (uint 256) и извлечение() двух функций, а сигнатура может быть рассчитана как 2e64cec1, 6057361d.
После декомпиляции вы получите следующую строку кода операции, которая, можно сказать, разделена на две части.
первая часть:
В компиляторе только часть контракта, посвященная селектору функций, получит содержимое callData, что означает получение сигнатуры вызова функции ее CallData, как показано на рисунке ниже.
Мы можем увидеть эффект, моделируя изменение пула памяти EVM.
Вторая часть:
Процесс оценки того, соответствует ли он значению селектора
Передайте 4-байтовую сигнатуру функции (0x2e64cec1) функции retrieve() в стек,
Код операции EQ извлекает 2 переменные из области стека, а именно 0x2e64cec1 и 0x6057361d, и проверяет, равны ли они.
PUSH2 передает в стек 2 байта данных (здесь 0x003b, в десятичном виде 59. В области стека находится программный счетчик, который указывает позицию следующей команды выполнения в байт-коде. Здесь мы устанавливаем 59, потому что именно здесь начинается байт-код retrieve().
JUMPI расшифровывается как «Перейти к, если...», он извлекает 2 значения из стека в качестве входных данных, и если условие истинно, счетчик программ будет обновлен до 59.
Вот как EVM определяет расположение байт-кода функции, которую необходимо выполнить, на основе вызова функции в контракте.
На самом деле это всего лишь простой набор операторов if для каждой функции в контракте и того, куда они переходят.
4. Резюме схемы
В общем кратко так
4.1 Доказательство уникальности
На данный момент можно сказать, что высокоэффективный и высокоточный метод анализа контрактов в основном реализован.Конечно, поскольку он так долго был строгим, мы могли бы быть более строгими.В приведенной выше схеме мы используйте REVER + JUMPDEST, чтобы блоки кода различались, и объедините неизбежную загрузку и смещение CallDate, чтобы сделать уникальное суждение.Существует ли, что я могу использовать контракт Solidity для реализации подобной последовательности кода операции?
Я провел контрольный эксперимент.Хотя есть методы получения CallData типа msg.sig с уровня грамматики солидности, методы реализации опкода после компиляции разные.