Исследование

Интерпретации байткода для x86 процессоров: подводные камни

В процессе работы по созданию эффективной системы generic-детектирования шелл-кода и проверки результатов на сгенерированных случайным образом входных данных мне пришлось применять фаззинг (fuzzing) для тестирования различных дизассемблеров с открытым исходным кодом. В качестве дизассемблера для своего нынешнего проекта я выбрал библиотеку libdasm, ориентируясь на относительно давнюю историю ее развития и лицензию, относящую ее к категории общественного достояния. Однако очевидно, что написание качественного и полного дизассемблера для x86 процессоров – нетривиальная задача в силу сложности набора команд процессоров, основанных на архитектуре x86.

В прошлом для libdasm были характерны проблемы, связанные с некорректным дизассемблированием некоторых инструкций с плавающей точкой, но они были вызваны простой ошибкой, которая заключалась в сдвиге значений в таблицах опкодов на три единицы (были пропущены три ряда со значениями NULL), и поэтому исправить ошибку было относительно легко.

Но сегодня я наткнулся на проблему, которая, по-видимому, связана не с опкодами, а с ошибкой, приводящей к неверной расшифровке инструкций. При дизассемблировании инструкций, использующих префикс размера адреса, libdasm его не учитывает, что приводит к получению неверного значения длины инструкции, вследствие некорректного распознования размера операнда, использующего непосредственный адрес:

Инструкция по адресу 008fe0f0 виртуализированной памяти расшифровывается неверно:

  • 67 это вышеупомянутый префикс размера адреса
  • a0 это код операции mov al, moffs8
  • 2232 это 16-разрядный адрес, который должен интерпретироваться как операнд
  • e830 не относится к данной инструкции

Исходя из принципа, что при обнаружении экзотической болезни следует проконсультироваться со вторым врачом, я решил попытать счастья еще с одним дизассемблером – библиотекой udis86:

Отлично – в этот раз инструкция mov дизассемблировалась правильно. И, поскольку последовательность байт e830 уже не интерпретируется как часть операнда, указывающего на память инструкции mov, она теперь верно дизассемблируется как инструкция call rel32. К сожалению, udis86 – дизассемблер, поддерживающий набор команд x86-64, и он расширяет операнд инструкции call, до 64 битов с сохранением знака, вновь приводя к ошибке при дизассемблировании.

Какие же инструкции в действительности «видит» и исполняет мой процессор? Поскольку мы в любом случае имеем дело с эмуляцией кода, мы можем просто установить точку останова (int 3) в начале блока и начать пошаговую отладку под управлением gdb (за вычетом кое-какого мусора):

Таким образом, процессор действительно видит инструкцию call и пытается ее выполнить. В данном случае это могло бы привести к катастрофическим результатам, поскольку позволило бы коду, использующему уязвимость, дающую возможность повышения привилегий для выполнения произвольного кода (вероятнее всего, шелл-коду) преодолеть защиту, обеспечиваемую виртуализацией. Чтобы можно было корректно обработать файл в виртуализаторе необходимо в программе заменить все инструкции, изменяющие регистр EIP, такие как CALL. Однако если такая инструкция не встречается в дизассемблерном листинге, то мы её не сможем корректно обработать.

После установки патча для libdasm (которая, как оказалось, вообще игнорирует префиксы размера адреса при разборе операндов) получаем правильно дизассемблированный код:

Усвоенные сегодня уроки:

  • Включение в процесс тестирования ПО фаззинга (fuzzing) с использованием произвольных вводных данных – отличная мысль, позволяющая (как в данном случае) выявлять интересные уязвимости. В данном случае использовать уязвимость все равно было бы очень непросто, поскольку сегменты кода и данных указывали на разные базовые адреса, но опытный злоумышленник, возможно, смог бы успешно осуществить атаку.
  • Общедоступная версия libdasm неверно дизассемблирует все инструкции с префиксом размера адреса, порождая интересные версии атаки на некоторые проекты, использующие эту библиотеку. Ждите выпуска патча для libdasm!

Интерпретации байткода для x86 процессоров: подводные камни

Ваш e-mail не будет опубликован. Обязательные поля помечены *

 

Отчеты

CloudSorcerer: новая APT-угроза, нацеленная на российские государственные организации

«Лаборатория Касперского» обнаружила новую APT-угрозу CloudSorcerer, нацеленную на российские государственные организации и использующую облачные службы в качестве командных серверов аналогично APT CloudWizard.

StripedFly: двуликий и незаметный

Разбираем фреймворк StripedFly для целевых атак, использовавший собственную версию эксплойта EternalBlue и успешно прикрывавшийся майнером.

Подпишитесь на еженедельную рассылку

Самая актуальная аналитика – в вашем почтовом ящике