В рамках данной статьи мы достаточно подробно рассмотрим процесс написания эксплоита под уязвимость в Microsoft Edge, с последующим выходом из песочницы. Если вам интересно узнать, как выглядит этот процесс, то welcome под кат! На последнем Pwn2Own 2019 в Монреале в категории браузеры был продемонстрирован эксплоит для взлома Microsoft Edge. Для этого было использовано две уязвимости: double free в ренедере и логическая уязвимость для выхода из песочницы. Эти две уязвимости были недавно закрыты и присвоены соотвествующие CVE: CVE-2019-0940 и CVE-2019-0938. Подробнее об уязвимостях можно прочитать в блоге: Pwn2Own 2019: Microsoft Edge Renderer Exploitation (CVE-2019-0940). Part 1 и Pwn2Own 2019: Microsoft Eedge Sandbox Escape (CVE-2019-0938). Part 2. В рамках нашей статьи мы хотим показать процесс написания подобного эксплоита и то, сколько нужно времени и ресурсов на это на примере того же Microsoft Edge на Windows 10 с помощью CVE-2017-0240 и CVE-2016-3309. Одним из отличий будет то, что, если в эксплоите, продемонстрированном на Pwn2Own, для выхода из песочницы использовалась логическая уязвимость, то в нашем сценарии для выхода из песочницы будет использована уязвимость в ядре Windows 10. Как показывают патчи от Microsoft, уязвимостей в ядре находится куда больше, чем уязвимостей в реализации песочницы. В итоге, такую цепочку уязвимостей встретить намного вероятнее, и её будет полезно знать сотрудникам ИБ в компаниях. В данной статье будет рассмотрен процесс написания 1-day эксплойта для браузера Microsoft Edge. Будет эксплуатироваться CVE-2017-0240. Первый этап эксплуатации будет производиться на основе материалов из источника [1], мы получим arbitrary address read/write примитив, а также познакомимся с различными техниками, которые могут быть полезны при эксплуатации подобных уязвимостей. Далее будет знакомство с инструментом pwn.js, который поможет получить вызов произвольных функций на основе произвольного чтения и записи, также будут рассмотрены различные mitigations и способы их обхода. На последнем этапе будет произведена эксплуатация уязвимости ядра Windows CVE-2016-3309 для повышения привилегий, обхода ограничений AppContainer и получения полного контроля над атакуемой машиной. Эксплуатация будет проводиться на стенде c ОС Microsoft Windows 10 Pro 1703 (10.0.15063) и браузером Microsoft Edge (40.15063.0.0). Уязвимость типа use-after-free присутствует в методе copyFromChannel объекта Audio​Buffer. В презентации The Advanced Exploitation of 64-bit Edge Browser Use-After-Free Vulnerability on Windows 10 приведен подробный анализ уязвимости и патча. При вызове метода copyFromChannel происходит копирование содержимого канала аудио-буфера в буфер destination, указанный первым аргументом. Метод также принимает номер канала (channelNumber) и смещение в аудио-буфере (startInChannel), начиная с которого необходимо производить копирование. Перед непосредственным копированием данных в destination в функции CDOMAudioBuffer::Var_copyFromChannel происходит кеширование буфера destination (адрес и размер буфера сохраняются в локальные переменные функции на стеке) и преобразование значений объектов channelNumber и startInChannel к типу Int, для чего происходит вызов метода valueOf преобразуемых объектов. Уязвимость заключается в том, что закешированный буфер может быть освобожден в момент преобразования типов в переопределенном методе valueOf объекта. Для проверки воспользуемся следующим кодом: Для освобождения буфера в данном коде используется технология Web Workers. Создав пустой Worker, мы можем отправить ему сообщение с помощью метода postMessage. Второй необязательный аргумент transfer данного метода принимает массив Transferable-объектов (ArrayBuffer, MessagePost или ImageBitmap), права на объект будут переданы в Worker и объект более не будет доступен в текущем контексте, благодаря чему может быть удален. После этого происходит вызов sleep — функция, обеспечивающая временную остановку выполнения программы (реализуется самостоятельно). Это необходимо для того, чтобы система сборки мусора (GC, Garbage Collector) успела освободить буфер, права на который были переданы. Запустив данный код в Edge под отладчиком, можно получить следующее падение.  В результате вызов copyFromChannel пытается копировать содержимое аудио-буфера в неалоцированную область памяти. Для эксплуатации уязвимости необходимо добиться выделения в этой области памяти каких-либо объектов. В данном случае отлично подойдет сегмент массива. Массивы в Chakra (JS-движок, используемый в браузере Edge) устроены следующим образом: объект массива имеет фиксированный размер, сами указатели на объекты массива (или значения, в случае IntArray) хранятся в отдельной области памяти — сегменте, указатель на который содержится в объекте массива. Заголовок сегмента содержит различную информацию, в том числе размер сегмента, который соответствует размеру массива. Размер массива также присутствует и в самом объекте массива. Схематично это выглядит следующим образом:  Таким образом, если нам удастся выделить сегмент массива в ранее освобожденном пространстве, то мы сможем перезаписать заголовок сегмента массива содержимым аудио-буфера. Для этого модифицируем код выше, добавив в следующие строки после sleep(1000);: Размер массивов подобран таким образом, чтобы размер сегмента массива занимал целый сегмент кучи (минимальный неделимый участок памяти кучи, размер которого — 0x10000 байт). Запустим данный код, указав в качестве точки останова функцию memcpy (вызовов memcpy будет много, поэтому имеет смысл остановиться сначала на edgehtml!WebCore::AudioBufferData::copyBufferData), в которой происходило падение. Получим следующий результат:  Отлично! Теперь мы можем перезаписать заголовок сегмента массива собственными значениями. Наиболее интересные значения в данном случае — размер массива, смещение которого мы можем увидеть на скриншоте выше. Изменим содержимое аудио-буфера следующим образом: Обратите внимание на значения ta2[14] и ta2[15] — они уже относятся не к заголовку сегмента, а к самим значениям массива. С помощью этого мы сможем определить нужный нам массив в глобальном массиве arr следующим образом: Если в результате был обнаружен массив, первые два элемента которого были изменены определенным образом, то всё отлично. Теперь мы имеем массив, размер сегмента которого больше, чем он есть на самом деле. Остальные массивы при этом можно освободить. Тут необходимо вспомнить о том, что размер массива существует в двух сущностях: в объекте массива, где он остался неизменённым, и в сегменте массива, где мы его увеличили. Оказывается, что размер в объекте массива игнорируется, если код исполняется в JIT-режиме и был оптимизирован. Этого легко добиться, например, следующим образом: После этого, с помощью функций arr_get и arr_set можно обращаться за границы массива (OOB, out-of-bound). В данном разделе рассмотрим технику, позволяющую добиться чтения и записи по произвольному адресу с помощью OOB. Метод, с помощью которого мы это получим, будет похож на тот, что используется в источнике [1], но будут также и значительные изменения. В используемой версии Edge блоки памяти для кучи выделяются последовательно, благодаря чему при выделении большого количества каких-либо объектов рано или поздно они окажутся после сегмента массива, за границы которого мы можем обращаться. Во-первых, это дает нам возможность считать указатель на виртуальную таблицу методов объектов (vftable), благодаря чему можно обойти рандомизацию адресного пространства процесса (ASLR). Но доступ к каким объектам поможет нам добиться произвольного чтения и записи? Для этого отлично подходит пара объектов DataView. Внутренняя структура DataView содержит указатель на буфер. Для чтения и записи по произвольному адресу мы, например, можем построить цепочку из двух DataView (dv1 и dv2) следующим образом: в качестве буфера dv1 указываем адрес dv2 с помощью обращения за пределы массива. Теперь с помощью dv1 мы можем изменять адрес буфера dv2, за счет чего и достигается произвольные чтение и запись. Схематично это можно изобразить следующим образом:  Чтобы воспользоваться данным методом, необходимо научиться определять адреса объектов в памяти. Для этого существует следующая техника: необходимо создать новый Array, с помощью OOB сохранить его vftable и typeId (первые два 64-битных поля структуры) и присвоить первому элементу массива объект, адрес которого нас интересует. Затем, необходимо восстановить ранее сохраненные значения vftable и typeId. Теперь младшее и старшее двойное слово адреса объекта можно получить, обратившись к первому и второму элементу массива. Дело в том, что по умолчанию новый массив является IntArray, и в его сегменте хранятся 4-байтовые значения массива как они есть. При присвоении массиву объекта происходит преобразование массива в ObjectArray, и его сегмент используется для хранения адресов объектов. При преобразовании изменяются vftable и typeId. Соответственно, если мы восстановим исходные значения vftable и typeId, через элементы этого массива мы сможем обращаться напрямую к сегменту. Схематично описанный процесс можно изобразить следующим образом:  Функция для получения адреса будет выглядеть следующим образом: Открытым вопросом остается создание необходимых объектов и поиск их с помощью OOB. Как было сказано ранее, при выделении большого количества объектов, рано или поздно они начнут выделяться после сегмента массива, за границы которого мы можем обращаться. Чтобы найти нужные объекты, необходимо просто пройтись по индексам за пределами массива в поиске нужных объектов. Т.к. все объекты одного типа располагаются в одном сегменте кучи, можно оптимизировать поиск и идти по сегментам кучи с шагом 0x10000, а проверять только несколько первых значений с начала каждого сегмента кучи. Чтобы идентифицировать объекты, можно установить им уникальные значения каких-либо параметров (например, для DataView это может быть byteOffset) или, используя уже известные константы в структуре объекта (например, в используемой версии Edge в IntArray по смещению 0x18 всегда находится значение 0x10005). Объединив все вышеописанные приемы, можно получить чтение и запись по произвольному адресу. Ниже представлен скриншот с чтением памяти объкетов DataView.  На данном этапе мы получили возможность чтения и записи по произвольному адресу внутри процесса отображения контента Edge. Рассмотрим основные технологии, которые должны помешать дальнейшей эксплуатации приложения и средства их обхода. Мы уже писали небольшой цикл статей Браузеры и app specific security mitigation (часть 1, вводная, часть 2, Internet Explorer и Edge, часть 3, Google Chrome), но стоит учитывать, что разработчики не стоят на месте и добавляют в свои продукты новые средства защиты. Выше мы научились считывать адреса виртуальных таблиц классов, используя их, мы с легкостью можем вычислить базовый адрес модуля Chakra.dll, таким образом ASLR не представляет проблем для дальнейшей эксплуатации. Один из способов обхода данной защиты — вызов VirtualAlloc с помощью ROP-цепочек. Но в случае Edge данный метод не сработает из-за ACG (см. ниже). Данная технология контролирует только косвенные вызовы, например, вызовы методов из виртуальной таблицы функций объектов. Адреса возврата на стеке не контролируются, и этим можно воспользоваться для построения ROP-цепочек. В будущем использованию ROP/JOP/COP-цепочек может помешать новая технология Intel: Control-flow Enforcement Technology (CET). Данная технология состоит из двух частей: Данная защита, как и CFG, не предотвращает использование ROP-цепочек. Данная защита значительно усложняет процесс эксплуатации. Из-за неё мы не можем вызвать сторонние исполняемые файлы или получить доступ к чувствительной информации пользователя в памяти или на дисках. Однако данную защиту можно преодолеть, используя уязвимости в реализации песочницы AppContainer или с помощью повышения привилегий через эксплуатацию уязвимостей в ядре ОС. Стоит отметить, что у Microsoft существует отдельная программа вознаграждений за техники обхода security mitigation-технологий. В программе указано, что повторное использование исполняемого кода (построение ROP-цепочек является разновидностью данной техники) не попадает под программу, т.к. является архитектурной проблемой. Из анализа всех технологий защиты следует, что для получения возможности исполнения произвольного кода необходимо обойти песочницу AppContainer. В данной статье мы опишем способ с использованием уязвимости ядра Windows. При этом мы можем использовать только JS-код и ROP-цепочки. Писать эксплойт для ядра, используя только ROP-цепочки, может быть очень сложно. Для упрощения этой задачи можно найти набор гаджетов, с помощью которого мы бы смогли вызывать необходимые методы WinAPI. К счастью, это уже реализовано в библиотеке pwn.js. С помощью неё, описав лишь функции read и write для произвольного чтения и записи, можно получить удобное API для поиска необходимых функций WinAPI и их вызова. Также pwn.js предоставляет удобный инструмент для работы с 64-битными значениями и указателями и инструменты для работы со структурами. Рассмотрим простой пример. На предыдущем этапе мы получили цепочку из двух связанных DataView. Для подготовки эксплойта необходимо создать следующий класс: Использовать полученный класс очень легко, вот простейший пример для вызова MessageBoxA: В результате исполнения получим следующее сообщение:  На данном этапе мы научились выполнять произвольные вызовы WinAPI. Этого может быть достаточно для реализации эксплуатации уязвимостей ядра. Отличный пример подходящей уязвимости является CVE-2016-3309. Подробную информацию о ней можно найти в источниках [7] и [8], а в презентации по pwn.js [2] есть частичная реализация эксплойта, который основан на относительно новой технологии эксплуатации GDI-объектов. Познакомиться подробнее с данной технологией можно в источниках [9], [10] и [11]. В результате эксплуатации уязвимости можно получить примитив произвольного чтения и записи в пространстве ядра. Изучение самого процесса эксплуатации мы оставим на читателя, в этом помогут перечисленные выше источники. В данной статье рассмотрим лишь общие техники, позволяющие с помощью произвольного чтения и записи в пространстве ядра повысить привилегии процесса и обойти AppContainer, а также рассмотрим примеры реализации этих техник с помощью pwn.js. Конечной целью эксплуатации в данной статье будет открытие командного интерпретатора cmd.exe от имени пользователя SYSTEM. Итак, у нас получилось реализовать чтение и запись по произвольному адресу в пространстве ядра. JS-функции для чтения и записи 64-битных значений в ядре назовем kernel_read_64 и kernel_write_64, соответственно. Первым делом необходимо получить базовый адрес ядра Windows. В контексте эксплуатации уязвимости это можно сделать через объект типа BITMAP, адрес которого мы знаем. pwn.js предоставляет удобный интерфейс описания структур. Структуру BITMAP можно описать, например, так: Поле Tid данной структуры хранит указатель на объект KTHREAD с контекстом текущего потока исполнения, который, в свою очередь, содержит адрес EmpCheckErrataList, с помощью которого можно определить базовый адрес ядра. Таким образом, для определения адреса ядра необходимо выполнить следующий код: Зная базовый адрес ядра, мы можем отключить ограничения AppContainer и повысить привилегии процесса. Для отключения ограничений AppContainer необходимо отчистить бит IsPackagedProcess из блока окружения процесса (Process Environment Block, PEB), что можно сделать не прибегая к эксплуатации уязвимости ядра. Также необходимо получить Access Token, в котором не будет признаков запуска приложения внутри AppContainer. В качестве такого токена можно взять Access Token системного процесса, там самым мы еще и повысим привилегии текущего процесса. Access Token идентифицирует пользователя и его привилегии, группу и другие данные доступа. Структура процесса в ядре EPROCESS содержит двусвязный список ActiveProcessLinks, с помощью которого можно перечислить все процессы. Указатель на PEB также можно получить из EPROCESS. Указатель на системный процесс можно получить из глобальной переменной PsInitialSystemProcess ядра, начиная с него, можно пройтись по всем процессам с помощью списка ActiveProcessLinks. В случае браузера Edge существует еще одна проблема: если процесс отображения контента не отвечает, то основной процесс Edge его завершит вместе со всеми дочерними процессами. По этой причине необходимо создавать новый процесс от имени другого процесса с привилегиями SYSTEM. Это может быть, например, winlogon.exe. Описанные выше техники можно реализовать с помощью pwn.js следующим образом: Результат выполнения полной цепочки эксплойтов:  На нашем YouTube вы можете посмотреть видео о том, как выйти из песочницы Microsoft Edge. Немного цифровых фактов: Теперь мы думаем, что у вас может сложиться некоторая картина о том, как злоумышленники на основании той же информации из патчей могут быстро создать эксплойт под интресующее их ПО. А учитывая, что человек может специализироваться на конкретном ПО, это займет у него еще меньше времени и сил. Поэтому не забывайте о своевременном обновлении своего ПО. Security Researcher