ОРИГИНАЛЬНАЯ СТАТЬЯ
ПЕРЕВЕДЕНО СПЕЦИАЛЬНО ДЛЯ xss.pro
$600 на SSD для Jolah Milovski ---> 0x5B1f2Ac9cF5616D9d7F1819d1519912e85eb5C09 для поднятия ноды ETHEREUM и тестов
С момента своего появления в 2005 году возвратно-ориентированное программирование (ROP) было основным способом предотвращения W ^ X во время использования повреждения памяти.
Хотя технология предотвращения выполнения данных (DEP) была разработана для блокирования атак путем внедрения простого кода из определенных областей памяти, злоумышленники быстро адаптировались и вместо того, чтобы внедрять всю полезную нагрузку кода, прибегли к повторному использованию нескольких фрагментов кода из разрешенных DEP страниц памяти, называемых ROP - модулями. Эти фрагменты кода берутся из уже существующего кода в целевом приложении и объединяются в цепочку, чтобы напоминать желаемую полезную нагрузку злоумышленника или просто отключать DEP для каждой страницы, чтобы разрешить выполнение существующих полезных нагрузок кода.
Чтобы навсегда заблокировать ROP-атаки, Intel разработала новую аппаратную защиту целостности потока управления под названием Control Enforcement Technology (CET), которая впервые появилась в системах Windows примерно два года назад. Сначала появление CET рисовало мрачную картину будущего для разработчиков эксплойтов и их зависимости от методов, основанных на ROP. была сформулирована новая методика повторного использования кода под названием «Поддельное объектно-ориентированное программирование» (COOP), Однако в 2015 году в статье, которая показалась весьма многообещающей для преодоления защиты целостности потока управления (CFI).
Ниже кратко расскажем, как работает защита от CFI, включая CET, и как мы можем использовать COOP для эффективного обхода Intel CET в последних выпусках Windows.
CFI "спереди и сзади"
Механизмы обеспечения целостности потока управления можно разделить на две основные категории: forward-edge и backward-edge.
Forward-edge CFI, как Microsoft CFG, защищает косвенные вызовы функций путем использования проверенных адресов функций. Например, если мы перезапишем указатель, разыменованный в инструкции CALL [rax], адресом ROP, CFG заблокирует наш эксплойт, выбросив исключение. Напротив, CFI с backward-edge, например, CET от Intel, защищает адрес возврата функции, сравнивая его с ранее сохраненной версией адреса, которая хранится в Shadow Stack. Если исходный адрес возврата будет перезаписан во время атаки на повреждение памяти, сравнение адресов неизбежно завершится неудачей, и приложение будет завершено. Учитывая, что атаки на основе ROP выполняют инструкцию "RET" без предварительной инструкции "CALL", значения стека выполняющегося потока и shadow stack не совпадают, поэтому CFI с обратным доступом, такие как CET, эффективно блокируют эту технику атаки. Intel CET был разработан для борьбы с атаками ROP как через теневой стек, так и через COP/JOP посредством косвенного отслеживания ветвлений (IBT). Однако, поскольку последняя технология еще не реализована в Windows, в этой статье мы будем называть "Intel CET" реализацией, в которой включен только Shadow Stack. Внутреннее устройство CET было подробно описано в предыдущих исследованиях, и мы уже продемонстрировали, как работает смягчение последствий и на каких основных программах CET была развернута. На момент написания этой статьи картина не сильно изменилась, и все основные браузеры продолжают запускать CET на всех процессах, кроме процесса рендеринга. Это означает, что нынешняя реализация CET неэффективна, поскольку процесс рендеринга - это тот процесс, который обычно эксплуатируется. Несмотря на то, что CET все еще не широко применяется в браузерах, мы должны ожидать, что в ближайшие годы он будет применяться к каждому процессу. Чтобы не оказаться застигнутыми врасплох, нам, как исследователям, следует совершенствовать свое ремесло и постоянно узнавать о новых разработанных углах атаки. Кроме того, независимо от того, насколько препятствующим может быть смягчение, нам нужно расширять горизонт, если мы хотим найти слабые места.
Подделка объектов
Как уже упоминалось в начале этой заметки, в 2015 году Феликс Шустер разработал новую технику повторного использования кода под названием Counterfeit Object-Oriented Programming или COOP. Следует отметить, что эта работа носит теоретический характер. Эта техника не была реализована в дикой природе или в раскрытых эксплойтах. Наша цель в этом блоге - попытаться использовать этот теоретический подход и реализовать его в доказательстве концепции, чтобы обойти Intel CET. Основная идея этой техники заключается в подделке, то есть создании новых объектов в памяти из контролируемых злоумышленником полезных нагрузок и соединении их в цепь с помощью виртуальных функций, которые уже присутствуют в целевом приложении или в загруженных библиотеках. Каждая виртуальная функция, содержащаяся в поддельном объекте, называется vfgadget и отвечает за выполнение небольшой задачи. Подобно ROP, vfgadgets может выполнять такие задачи, как занесение значения в регистр. Однако, объединившись вместе, несколько vfgadgets могут выполнять более сложные операции. Поскольку сегодня не существует специальных инструментов для обнаружения vfgadgets, их можно найти с помощью пользовательских скриптов, таких как IDAPython, используя процесс, аналогичный обнаружению гаджетов ROP. Поскольку vfgadgets выбираются из пула CFG-валидных функций, мы можем пометить их как легитимные, и их выполнение не будет блокироваться CFG, если мы перехватим косвенный вызов одной из них. Кроме того, интересным следствием является то, что Intel CET не будет срабатывать, поскольку мы не испортим ни одного адреса возврата функции в процессе последовательного вызова vfgadgets. Как показано в статье Шустера, типичная полезная нагрузка COOP начинается с основополагающего vfgadget, который действует как главная функция COOP. В этой статье мы будем называть его Looper. После того, как злоумышленник собрал поддельный объект в памяти, vfgadget Looper выполняет итерации по массиву других vfgadget, тщательно подготовленных злоумышленником, которые будут вызываться один за другим. Выровняв таким образом vfgadgets в поддельном объекте, мы сможем вызывать допустимые виртуальные функции контролируемым образом. Как только Looper будет запущен, он сможет вызывать другие vfgadgets, отвечающие за выполнение определенных операций, например, Argument LoadersInvokers и Collectors. Эти vfgadgets будут храниться через регулярные интервалы в массиве, к которому обращается Looper. Argument Loader vfgadget заполняет данный регистр, загружая в него значение. Загружаемое значение будет храниться внутри поддельного объекта со смещением от начала поддельного объекта. После заполнения регистров одним или несколькими Argument Loaders можно вызвать Invoker vfgadget для простого выполнения указателя функции целевого API.
Collectors - это инструменты, которые извлекают значение, уже имеющееся в регистре, и сохраняют его обратно в поддельный объект атакующего (т.е. как возвращаемое значение из вызванного API).
Следующий график обобщает рассмотренную до сих пор стратегию атаки COOP.
Мы можем располагать и смешивать различные vfgadgets в зависимости от их доступности и желаемых API, которые мы хотим выполнить.
Для того чтобы лучше понять COOP-атаку, давайте начнем с анализа основного vfgadget, Looper. Следующий ассемблерный код представляет упрощенную версию vfgadget Looper COOP:
В первой строке RCX указатель this, а в RBX мы загружаем начало поддельного объекта, который был размещен по смещению 0x40 от RCX. Поскольку все элементы в наших поддельных объектах будут ссылаться по смещению от этого указателя, нам нужно убедиться, что мы сохранили его значение перед тем, как перехватить поток программы (т.е. испортить vtable).
Затем базовый адрес полезной нагрузки COOP разыменовывается в RAX, который указывает на первый вызванный vfgadget. После возврата вызова загружается новый vfgadget по смещению 0x20 от предыдущего гаджета, и если содержимое RBX не равно нулю, происходит новая итерация цикла. В процессе записи нашего поддельного объекта в память нам необходимо выровнять каждый vfgadget по смещениям Looper, аналогично следующей схеме:
Здесь 00000227`26cd8900 - это базовый адрес нашей полезной нагрузки COOP, которая хранится по смещению 0x40 от этого указателя (RCX). Из предыдущего листинга кода мы видим, что в первой строке процедуры _loopstart указатель разыменовывается в RAX, который, в свою очередь, указывает на первый vfgadget. На следующей итерации цикла Looper повторяет ту же задачу, загружая указатель по смещению 0x20 от предыдущего, и в итоге вызывает второй vfgadget. При эксплуатации реальных целей, таких как браузеры, рекомендуется полагаться на vfgadget Looper, поскольку он обеспечивает больший контроль и стабильность по сравнению с другими vfgadget. Однако для краткости мы написали наше уязвимое приложение только с одним vfgadget Invoker, который принимает один аргумент, как мы увидим в следующем разделе. Рассмотрев ознакомительную теорию COOP, давайте перейдем к эксплуатации скомпилированного в CET концептуального приложения, которое мы разработали для демонстрации COOP-атак.
Обход Shadow Stack CET с помощью COOP
Уязвимое приложение, которое мы написали, скомпилировано с помощью CET и CFG в дополнение к DEP, который включен по умолчанию. Во-первых, чтобы убедиться, что CET действительно применяется, мы ставим точку останова на printf, просматриваем стек вызовов, перезаписываем адрес возврата и возобновляем выполнение.
Мы получаем подтверждение того, что CET включен, поскольку нам сразу же выдается исключение Shadow Stack, ссылающееся на недопустимый адрес возврата.
Поскольку CET является аппаратным средством защиты, для того чтобы вызвать вышеуказанную ошибку, нам потребуется процессор Core 11-го поколения "Tiger Lake".
Чтобы имитировать уязвимость браузера, мы можем получить контроль RIP, воспользовавшись уязвимостью типа confusion в приложении, которая срабатывает автоматически при выполнении приложения. Когда мы нажимаем кнопку запуска уязвимости, указатель vtable повреждается нашим вводом, что приводит к косвенному вызову, который мы контролируем. Затем мы перехватываем vtable, чтобы заставить ее указывать на наш буфер COOP, где находится наш первый (и единственный) vfgadget. Как упоминалось ранее, вместо использования Looper со вложенными vfgadget, для краткости мы предпочли использовать один гаджет, который включает в себя компоненты Invoker и Argument Loader. В рамках автоматизированной части эксплойта, чтобы получить указатель this vfgadget, мы слили указатель стека и извлекли указатель this как статическое смещение из стека. Получив адрес указателя this, мы подготовили адрес Windows API, который мы хотим вызвать, вместе с его аргументами. Это делается путем записи адреса Windows API и аргументов по нужным смещениям внутри поддельного объекта. Перед более подробным изучением полезной нагрузки COOP давайте сначала поймем синтаксис PoC, запустив его без каких-либо аргументов.
Приложение принимает четыре параметра: указатель на наш буфер поддельных объектов (COOP), адрес vfgadget, адрес Windows API и аргументы. Помощник показывает два простых случая использования, но, конечно, его можно расширить, чтобы вызвать любой разрешенный CFG API (если приложение скомпилировано с ним). Поскольку библиотеки Windows DLL загружаются по случайному базовому адресу, необходимо предварительно вычислить адрес нужного API. Давайте сначала посмотрим C++ код объекта, связанного с нашим vfgadget, а затем изучим соответствующую сборку из скомпилированного бинарного файла:
Класс OffSec, присутствующий в проекте, содержит метод trigger, который действует как указатель функции в стиле C, которым мы можем воспользоваться для вызова любого API, как мы увидим в ближайшее время. Затем класс 'OffSec' инстанцируется в основной процедуре программы, поэтому он загружается в память вместе со своими методами.
При более детальном рассмотрении Invoker в дизассемблере обнаруживается несколько интересных аспектов.
Начиная со второй по четвертую строку, указатель this, на который ссылается RCX, сначала сохраняется на стеке, а затем перемещается в RAX. Затем значение по смещению 0x10 из RAX разыменовывается и перемещается в RAX. Это значение будет указателем функции API, находящимся в нашем поддельном объекте. Затем в строках 7 и 8 первый аргумент функции разыменовывается по смещению 0x8 от этого указателя и перемещается в RCX.
Как мы вскоре узнаем, уязвимое приложение позаботится об этих смещениях после того, как мы передадим параметры из командной строки.
Рассмотрев основные составные части нашей цепочки атак, давайте попробуем запустить PoC, передав четыре аргумента, чтобы получить выполнение кода:
В приведенной выше команде мы задали следующие параметры:
00001e000000 в качестве буфера хранения для нашего поддельного объекта,
5086014001000000 в качестве vfgadget Invoker в дополнение к
40610fecfb7f0000, который является адресом памяти WinExec.
В качестве последнего аргумента мы передаем строковый аргумент WinExec. Обратите внимание, что все адреса памяти передаются в формате little-endian.
После запуска приложение немедленно останавливается, что позволяет нам подключить к нему отладчик. Перед этим мы запускаем Process Explorer, чтобы убедиться, что двоичный файл действительно запущен с включенным Intel CET.
В колонке "Защита стека" Process Explorer подтверждает, что CET применяется только для CET-совместимых модулей, что означает, что смягчение будет применяться для любого модуля, скомпилированного с CET. Это включает и наше приложение.
После подключения отладчика мы устанавливаем точку останова на единственный косвенный вызов, присутствующий в главной функции, и продолжаем выполнение.
Мы поместили точку останова по адресу main+0x3d2 и проверили, что у нас действительно есть косвенный вызов по этому адресу. Далее мы выгружаем содержимое нашего поддельного объекта, расположенного по статическому адресу 0x1e0000, который хранит указатель на наш vfgadget по адресу 00000001400186a0. По адресу main+0x3d2 происходит возгорание ошибки путаницы типов, которая позволяет нам взять контроль над RIP. Как только мы нажимаем точку останова, мы проверяем значение, находящееся в нашем буфере COOP, которое должно быть первым vfgadget Invoker. Мы позволяем приложению продолжить работу и проверяем, что мы действительно достигли точки останова.
После трассировки в CFG LdrpDispatchUserCallTarge мы переходим к Invoker vfgadget 'OffSec:
rigger', доказывая, что у нас есть контроль над потоком выполнения программы. Затем мы продолжаем трассировку внутри vfgadget:
В приведенном выше листинге Invoker сначала сохраняет указатель this из RCX в стек, и мы также проверяем, что он указывает на основание нашего буфера COOP. На последней инструкции указатель 'this' загружается в RAX, который будет использоваться в качестве ссылки для вызова API и его аргумента:
Сначала по смещению 0x10 мы видим, что адрес WinExec загружается в RAX, а затем, три инструкции спустя, параметр команды извлекается по смещению 0x8. Как только мы разрешаем выполнение, мы снова вызываем LdrpDispatchUserCallTarget, который в свою очередь отправляет выполнение на WinExec и приветствует нас с нашим вычислителем
На этом мы закончили нашу краткую демонстрацию того, как можно обойти Shadow Stack Intel CET и получить выполнение произвольного кода с помощью атаки COOP, вызывая разрешенные CFG функции и одновременно избегая повреждения любого адреса возврата.
Проект Visual Studio для этого PoC-приложения можно найти по следующему URL.
Выводы
Intel CET предоставляет еще один сильный защитный механизм, который, несомненно, повышает эффективность разработки эксплойтов. Тем не менее, новые пути атак, такие как COOP, могут быть использованы для обхода этой защиты. Как мы уже выяснили, COOP vfgadgets по своей сути разрешены CFG, поэтому в реальном сценарии они могут быть объединены в цепочку, чтобы обойти Intel CET и, возможно, другие средства защиты CFI.
ПЕРЕВЕДЕНО СПЕЦИАЛЬНО ДЛЯ xss.pro
$600 на SSD для Jolah Milovski ---> 0x5B1f2Ac9cF5616D9d7F1819d1519912e85eb5C09 для поднятия ноды ETHEREUM и тестов
С момента своего появления в 2005 году возвратно-ориентированное программирование (ROP) было основным способом предотвращения W ^ X во время использования повреждения памяти.
Хотя технология предотвращения выполнения данных (DEP) была разработана для блокирования атак путем внедрения простого кода из определенных областей памяти, злоумышленники быстро адаптировались и вместо того, чтобы внедрять всю полезную нагрузку кода, прибегли к повторному использованию нескольких фрагментов кода из разрешенных DEP страниц памяти, называемых ROP - модулями. Эти фрагменты кода берутся из уже существующего кода в целевом приложении и объединяются в цепочку, чтобы напоминать желаемую полезную нагрузку злоумышленника или просто отключать DEP для каждой страницы, чтобы разрешить выполнение существующих полезных нагрузок кода.
Чтобы навсегда заблокировать ROP-атаки, Intel разработала новую аппаратную защиту целостности потока управления под названием Control Enforcement Technology (CET), которая впервые появилась в системах Windows примерно два года назад. Сначала появление CET рисовало мрачную картину будущего для разработчиков эксплойтов и их зависимости от методов, основанных на ROP. была сформулирована новая методика повторного использования кода под названием «Поддельное объектно-ориентированное программирование» (COOP), Однако в 2015 году в статье, которая показалась весьма многообещающей для преодоления защиты целостности потока управления (CFI).
Ниже кратко расскажем, как работает защита от CFI, включая CET, и как мы можем использовать COOP для эффективного обхода Intel CET в последних выпусках Windows.
CFI "спереди и сзади"
Механизмы обеспечения целостности потока управления можно разделить на две основные категории: forward-edge и backward-edge.
Forward-edge CFI, как Microsoft CFG, защищает косвенные вызовы функций путем использования проверенных адресов функций. Например, если мы перезапишем указатель, разыменованный в инструкции CALL [rax], адресом ROP, CFG заблокирует наш эксплойт, выбросив исключение. Напротив, CFI с backward-edge, например, CET от Intel, защищает адрес возврата функции, сравнивая его с ранее сохраненной версией адреса, которая хранится в Shadow Stack. Если исходный адрес возврата будет перезаписан во время атаки на повреждение памяти, сравнение адресов неизбежно завершится неудачей, и приложение будет завершено. Учитывая, что атаки на основе ROP выполняют инструкцию "RET" без предварительной инструкции "CALL", значения стека выполняющегося потока и shadow stack не совпадают, поэтому CFI с обратным доступом, такие как CET, эффективно блокируют эту технику атаки. Intel CET был разработан для борьбы с атаками ROP как через теневой стек, так и через COP/JOP посредством косвенного отслеживания ветвлений (IBT). Однако, поскольку последняя технология еще не реализована в Windows, в этой статье мы будем называть "Intel CET" реализацией, в которой включен только Shadow Stack. Внутреннее устройство CET было подробно описано в предыдущих исследованиях, и мы уже продемонстрировали, как работает смягчение последствий и на каких основных программах CET была развернута. На момент написания этой статьи картина не сильно изменилась, и все основные браузеры продолжают запускать CET на всех процессах, кроме процесса рендеринга. Это означает, что нынешняя реализация CET неэффективна, поскольку процесс рендеринга - это тот процесс, который обычно эксплуатируется. Несмотря на то, что CET все еще не широко применяется в браузерах, мы должны ожидать, что в ближайшие годы он будет применяться к каждому процессу. Чтобы не оказаться застигнутыми врасплох, нам, как исследователям, следует совершенствовать свое ремесло и постоянно узнавать о новых разработанных углах атаки. Кроме того, независимо от того, насколько препятствующим может быть смягчение, нам нужно расширять горизонт, если мы хотим найти слабые места.
Подделка объектов
Как уже упоминалось в начале этой заметки, в 2015 году Феликс Шустер разработал новую технику повторного использования кода под названием Counterfeit Object-Oriented Programming или COOP. Следует отметить, что эта работа носит теоретический характер. Эта техника не была реализована в дикой природе или в раскрытых эксплойтах. Наша цель в этом блоге - попытаться использовать этот теоретический подход и реализовать его в доказательстве концепции, чтобы обойти Intel CET. Основная идея этой техники заключается в подделке, то есть создании новых объектов в памяти из контролируемых злоумышленником полезных нагрузок и соединении их в цепь с помощью виртуальных функций, которые уже присутствуют в целевом приложении или в загруженных библиотеках. Каждая виртуальная функция, содержащаяся в поддельном объекте, называется vfgadget и отвечает за выполнение небольшой задачи. Подобно ROP, vfgadgets может выполнять такие задачи, как занесение значения в регистр. Однако, объединившись вместе, несколько vfgadgets могут выполнять более сложные операции. Поскольку сегодня не существует специальных инструментов для обнаружения vfgadgets, их можно найти с помощью пользовательских скриптов, таких как IDAPython, используя процесс, аналогичный обнаружению гаджетов ROP. Поскольку vfgadgets выбираются из пула CFG-валидных функций, мы можем пометить их как легитимные, и их выполнение не будет блокироваться CFG, если мы перехватим косвенный вызов одной из них. Кроме того, интересным следствием является то, что Intel CET не будет срабатывать, поскольку мы не испортим ни одного адреса возврата функции в процессе последовательного вызова vfgadgets. Как показано в статье Шустера, типичная полезная нагрузка COOP начинается с основополагающего vfgadget, который действует как главная функция COOP. В этой статье мы будем называть его Looper. После того, как злоумышленник собрал поддельный объект в памяти, vfgadget Looper выполняет итерации по массиву других vfgadget, тщательно подготовленных злоумышленником, которые будут вызываться один за другим. Выровняв таким образом vfgadgets в поддельном объекте, мы сможем вызывать допустимые виртуальные функции контролируемым образом. Как только Looper будет запущен, он сможет вызывать другие vfgadgets, отвечающие за выполнение определенных операций, например, Argument LoadersInvokers и Collectors. Эти vfgadgets будут храниться через регулярные интервалы в массиве, к которому обращается Looper. Argument Loader vfgadget заполняет данный регистр, загружая в него значение. Загружаемое значение будет храниться внутри поддельного объекта со смещением от начала поддельного объекта. После заполнения регистров одним или несколькими Argument Loaders можно вызвать Invoker vfgadget для простого выполнения указателя функции целевого API.
Collectors - это инструменты, которые извлекают значение, уже имеющееся в регистре, и сохраняют его обратно в поддельный объект атакующего (т.е. как возвращаемое значение из вызванного API).
Следующий график обобщает рассмотренную до сих пор стратегию атаки COOP.
Мы можем располагать и смешивать различные vfgadgets в зависимости от их доступности и желаемых API, которые мы хотим выполнить.
Для того чтобы лучше понять COOP-атаку, давайте начнем с анализа основного vfgadget, Looper. Следующий ассемблерный код представляет упрощенную версию vfgadget Looper COOP:
Код с оформлением (BB-коды):
mov rbx, [rcx+0x40]
loop_start:
mov rax, [rbx]
call cs:__guard_dispatch_icall_fptr
mov rbx, [rbx+20h]
test rbx, rbx
jnz short loop_start
...
loop_exit:
ret
В первой строке RCX указатель this, а в RBX мы загружаем начало поддельного объекта, который был размещен по смещению 0x40 от RCX. Поскольку все элементы в наших поддельных объектах будут ссылаться по смещению от этого указателя, нам нужно убедиться, что мы сохранили его значение перед тем, как перехватить поток программы (т.е. испортить vtable).
Затем базовый адрес полезной нагрузки COOP разыменовывается в RAX, который указывает на первый вызванный vfgadget. После возврата вызова загружается новый vfgadget по смещению 0x20 от предыдущего гаджета, и если содержимое RBX не равно нулю, происходит новая итерация цикла. В процессе записи нашего поддельного объекта в память нам необходимо выровнять каждый vfgadget по смещениям Looper, аналогично следующей схеме:
Здесь 00000227`26cd8900 - это базовый адрес нашей полезной нагрузки COOP, которая хранится по смещению 0x40 от этого указателя (RCX). Из предыдущего листинга кода мы видим, что в первой строке процедуры _loopstart указатель разыменовывается в RAX, который, в свою очередь, указывает на первый vfgadget. На следующей итерации цикла Looper повторяет ту же задачу, загружая указатель по смещению 0x20 от предыдущего, и в итоге вызывает второй vfgadget. При эксплуатации реальных целей, таких как браузеры, рекомендуется полагаться на vfgadget Looper, поскольку он обеспечивает больший контроль и стабильность по сравнению с другими vfgadget. Однако для краткости мы написали наше уязвимое приложение только с одним vfgadget Invoker, который принимает один аргумент, как мы увидим в следующем разделе. Рассмотрев ознакомительную теорию COOP, давайте перейдем к эксплуатации скомпилированного в CET концептуального приложения, которое мы разработали для демонстрации COOP-атак.
Обход Shadow Stack CET с помощью COOP
Уязвимое приложение, которое мы написали, скомпилировано с помощью CET и CFG в дополнение к DEP, который включен по умолчанию. Во-первых, чтобы убедиться, что CET действительно применяется, мы ставим точку останова на printf, просматриваем стек вызовов, перезаписываем адрес возврата и возобновляем выполнение.
Код:
0:000> bp printf
0:000> g
0:000> k
# Child-SP RetAddr Call Site
00 00000000`0014fde8 00000001`400180e8 coop!printf [C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\ucrt\stdio.h @ 956]
01 00000000`0014fdf0 00000001`40018d54 coop!main+0x28 [C:\Users\uf0\source\repos\COOP\COOP\coop.cpp @ 54]
02 (Inline Function) --------`-------- coop!invoke_main+0x22 [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78]
03 00000000`0014fef0 00007ffb`ec0a7034 coop!__scrt_common_main_seh+0x10c [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288]
04 00000000`0014ff30 00007ffb`ecba2651 KERNEL32!BaseThreadInitThunk+0x14
05 00000000`0014ff60 00000000`00000000 ntdll!RtlUserThreadStart+0x21
0:000> dq 00000000`0014fde8 L1
00000000`0014fde8 00000001`400180e8
0:000> eq 00000000`0014fde8 414141414141
0:000> dq 00000000`0014fde8 L1
00000000`0014fde8 00004141`414141411
0:000> g
(16c0.f68): Security check failure or stack buffer overrun - code c0000409 (!!! second chance !!!)
Subcode: 0x39 FAST_FAIL_CONTROL_INVALID_RETURN_ADDRESS Shadow stack violation
coop!printf+0x56:
00000001`400187e6 c3 ret
Мы получаем подтверждение того, что CET включен, поскольку нам сразу же выдается исключение Shadow Stack, ссылающееся на недопустимый адрес возврата.
Поскольку CET является аппаратным средством защиты, для того чтобы вызвать вышеуказанную ошибку, нам потребуется процессор Core 11-го поколения "Tiger Lake".
Чтобы имитировать уязвимость браузера, мы можем получить контроль RIP, воспользовавшись уязвимостью типа confusion в приложении, которая срабатывает автоматически при выполнении приложения. Когда мы нажимаем кнопку запуска уязвимости, указатель vtable повреждается нашим вводом, что приводит к косвенному вызову, который мы контролируем. Затем мы перехватываем vtable, чтобы заставить ее указывать на наш буфер COOP, где находится наш первый (и единственный) vfgadget. Как упоминалось ранее, вместо использования Looper со вложенными vfgadget, для краткости мы предпочли использовать один гаджет, который включает в себя компоненты Invoker и Argument Loader. В рамках автоматизированной части эксплойта, чтобы получить указатель this vfgadget, мы слили указатель стека и извлекли указатель this как статическое смещение из стека. Получив адрес указателя this, мы подготовили адрес Windows API, который мы хотим вызвать, вместе с его аргументами. Это делается путем записи адреса Windows API и аргументов по нужным смещениям внутри поддельного объекта. Перед более подробным изучением полезной нагрузки COOP давайте сначала поймем синтаксис PoC, запустив его без каких-либо аргументов.
Код:
C:\Users\offsec\source\repos\COOP\x64\Debug>coop.exe
[-] SYNTAX:
coop.exe <COOP obj ptr> <1st vfgadget> <WinAPI> <API argument>
[-] EXAMPLE - WinExec:
coop.exe 00001e000000 5086014001000000 40610fecfb7f0000 "cmd.exe /C calc"
[-] EXAMPLE - LoadLibraryA:
coop.exe 00001e000000 5086014001000000 f0040becfb7f0000 "edgehtml.dll"
Приложение принимает четыре параметра: указатель на наш буфер поддельных объектов (COOP), адрес vfgadget, адрес Windows API и аргументы. Помощник показывает два простых случая использования, но, конечно, его можно расширить, чтобы вызвать любой разрешенный CFG API (если приложение скомпилировано с ним). Поскольку библиотеки Windows DLL загружаются по случайному базовому адресу, необходимо предварительно вычислить адрес нужного API. Давайте сначала посмотрим C++ код объекта, связанного с нашим vfgadget, а затем изучим соответствующую сборку из скомпилированного бинарного файла:
Код:
class OffSec {
public:
char* a;
int (*callback)(char* a);
public:
virtual void trigger(char* a1) {
callback(a);
}
};
Класс OffSec, присутствующий в проекте, содержит метод trigger, который действует как указатель функции в стиле C, которым мы можем воспользоваться для вызова любого API, как мы увидим в ближайшее время. Затем класс 'OffSec' инстанцируется в основной процедуре программы, поэтому он загружается в память вместе со своими методами.
При более детальном рассмотрении Invoker в дизассемблере обнаруживается несколько интересных аспектов.
Начиная со второй по четвертую строку, указатель this, на который ссылается RCX, сначала сохраняется на стеке, а затем перемещается в RAX. Затем значение по смещению 0x10 из RAX разыменовывается и перемещается в RAX. Это значение будет указателем функции API, находящимся в нашем поддельном объекте. Затем в строках 7 и 8 первый аргумент функции разыменовывается по смещению 0x8 от этого указателя и перемещается в RCX.
Как мы вскоре узнаем, уязвимое приложение позаботится об этих смещениях после того, как мы передадим параметры из командной строки.
Рассмотрев основные составные части нашей цепочки атак, давайте попробуем запустить PoC, передав четыре аргумента, чтобы получить выполнение кода:
Код:
C:\> coop.exe 00001e000000 5086014001000000 40610fecfb7f0000 "cmd.exe /C calc"
В приведенной выше команде мы задали следующие параметры:
00001e000000 в качестве буфера хранения для нашего поддельного объекта,
5086014001000000 в качестве vfgadget Invoker в дополнение к
40610fecfb7f0000, который является адресом памяти WinExec.
В качестве последнего аргумента мы передаем строковый аргумент WinExec. Обратите внимание, что все адреса памяти передаются в формате little-endian.
После запуска приложение немедленно останавливается, что позволяет нам подключить к нему отладчик. Перед этим мы запускаем Process Explorer, чтобы убедиться, что двоичный файл действительно запущен с включенным Intel CET.
В колонке "Защита стека" Process Explorer подтверждает, что CET применяется только для CET-совместимых модулей, что означает, что смягчение будет применяться для любого модуля, скомпилированного с CET. Это включает и наше приложение.
После подключения отладчика мы устанавливаем точку останова на единственный косвенный вызов, присутствующий в главной функции, и продолжаем выполнение.
Код:
0:001> bp 00000001`4001847e
0:001> bl
0 e Disable Clear 00000001`4001847e 0001 (0001) 0:**** coop!main+0x3d2
0:001> u 00000001`4001847e L1
coop!main+0x3d2:
00000001`4001847e ff159cbb0a00 call qword ptr [coop!__guard_dispatch_icall_fptr (00000001`400c4020)]
0:000> dq 0x1e0000 L1
00000000`001e0000 00000001`400186a0
0:001> g
Мы поместили точку останова по адресу main+0x3d2 и проверили, что у нас действительно есть косвенный вызов по этому адресу. Далее мы выгружаем содержимое нашего поддельного объекта, расположенного по статическому адресу 0x1e0000, который хранит указатель на наш vfgadget по адресу 00000001400186a0. По адресу main+0x3d2 происходит возгорание ошибки путаницы типов, которая позволяет нам взять контроль над RIP. Как только мы нажимаем точку останова, мы проверяем значение, находящееся в нашем буфере COOP, которое должно быть первым vfgadget Invoker. Мы позволяем приложению продолжить работу и проверяем, что мы действительно достигли точки останова.
Код:
Breakpoint 0 hit
coop!main+0x3d2:
00000001`4001847e ff159cbb0a00 call qword ptr [coop!__guard_dispatch_icall_fptr (00000001`400c4020)] ds:00000001`400c4020={ntdll!LdrpDispatchUserCallTarget (00007ffb`ecbdc620)}
0:000>t
ntdll!LdrpDispatchUserCallTarget:
00007ffb`ecbdc620 4c8b1d814d0f00 mov r11,qword ptr [ntdll!LdrSystemDllInitBlock+0xb8 (00007ffb`eccd13a8)] ds:00007ffb`eccd13a8=00007df600000000
0:000>
ntdll!LdrpDispatchUserCallTarget+0x23:
00007ff8`7c5fc643 48ffe0 jmp rax {coop!OffSec::trigger (00000001`400186a0)}
...
0:000>
coop!OffSec::trigger:
00000001`400186a0 4889542410 mov qword ptr [rsp+10h],rdx ss:00000000`0014fdf8=0000000000612b77
После трассировки в CFG LdrpDispatchUserCallTarge мы переходим к Invoker vfgadget 'OffSec:
Код:
0:000> t
...
coop!OffSec::trigger+0x5:
00000001`40018655 48894c2408 mov qword ptr [rsp+8],rcx ss:00000000`0014fdf0=00000001400a1108
0:000> dq rcx L1
00000000`00590d80 00000000`001e0000
0:000> t
coop!OffSec::trigger+0xa:
00000001`4001865a 4883ec38 sub rsp,38h
0:000>
coop!OffSec::trigger+0xe:
00000001`4001865e 488b442440 mov rax,qword ptr [rsp+40h] ss:00000000`0014fdf0=000000590d80
В приведенном выше листинге Invoker сначала сохраняет указатель this из RCX в стек, и мы также проверяем, что он указывает на основание нашего буфера COOP. На последней инструкции указатель 'this' загружается в RAX, который будет использоваться в качестве ссылки для вызова API и его аргумента:
Код:
0:000>
coop!OffSec::trigger+0x13:
00000001`40018663 488b4010 mov rax,qword ptr [rax+10h] ds:00000000`00590d90={KERNEL32!WinExec (00007ffb`ec0f6140)}
0:000>
coop!OffSec::trigger+0x17:
00000001`40018667 4889442420 mov qword ptr [rsp+20h],rax ss:00000000`0014fdd0=000000000000001f
0:000>
coop!OffSec::trigger+0x1c:
00000001`4001866c 488b442440 mov rax,qword ptr [rsp+40h] ss:00000000`0014fdf0=0000000000590d80
0:000>
coop!OffSec::trigger+0x21:
00000001`40018671 488b4808 mov rcx,qword ptr [rax+8] ds:00000000`00590d88=00000000001e0080
0:000> dc 00000000001e0080
00000000`001e0080 2e646d63 20657865 6320432f 00636c61 cmd.exe /C calc.
Сначала по смещению 0x10 мы видим, что адрес WinExec загружается в RAX, а затем, три инструкции спустя, параметр команды извлекается по смещению 0x8. Как только мы разрешаем выполнение, мы снова вызываем LdrpDispatchUserCallTarget, который в свою очередь отправляет выполнение на WinExec и приветствует нас с нашим вычислителем
На этом мы закончили нашу краткую демонстрацию того, как можно обойти Shadow Stack Intel CET и получить выполнение произвольного кода с помощью атаки COOP, вызывая разрешенные CFG функции и одновременно избегая повреждения любого адреса возврата.
Проект Visual Studio для этого PoC-приложения можно найти по следующему URL.
Выводы
Intel CET предоставляет еще один сильный защитный механизм, который, несомненно, повышает эффективность разработки эксплойтов. Тем не менее, новые пути атак, такие как COOP, могут быть использованы для обхода этой защиты. Как мы уже выяснили, COOP vfgadgets по своей сути разрешены CFG, поэтому в реальном сценарии они могут быть объединены в цепочку, чтобы обойти Intel CET и, возможно, другие средства защиты CFI.