Аннотация
С конца 20-го века стало ясно, что веб-браузеры будут играть решающую роль в доступе к интернет-ресурсам, таким как WWW. Они превратились в сложные программные пакеты, которые способны обрабатывать множество форматов данных. Компиляция Just-In-Time (JIT) была включена для ускорения выполнения кода скрипта, но также используется помимо веб-браузеров для повышения производительности. Злоумышленники с радостью приветствовали JIT по-своему, и до сегодняшнего дня JIT-компиляторы являются важной целью различных атак. Это включает в себя, например, JIT-Spraying атаки с повторным использованием кода на основе JIT и специфичные для JIT недостатки, позволяющие обойти методы смягчения, чтобы упростить использование уязвимостей, приводящих к повреждению памяти. Кроме того, JIT-компиляторы являются сложными и обеспечивают большую поверхность атаки, что видно в постоянном потоке критических ошибок, появляющихся в них. В этой статье мы рассмотрим и систематизируем джунгли компиляторов JIT основных (клиентских) программ и дадим категоризацию методов злоупотребления компиляцией JIT. Таким образом, мы представляем методы, используемые в академических, а также в неакадемических работах, которые пытаются сломать различные средства защиты от уязвимости повреждения памяти. Кроме того, мы обсудим какие меры могут привести к ужесточению JIT-компиляторов для предотвращения их использования злоумышленниками, желающими использовать компиляторы Just-In-Time.
1. Введение
Поскольку стало ясно, что ошибки памяти, особенно переполнения буфера в стеке, могут использоваться для выполнения произвольного кода, управляемого злоумышленником, в течение многих лет было предложено множество атак и защит. По сравнению с гонкой вооружений, появились новые защитные сооружения, которые вскоре были разрушены из-за новых атак, которые снова привели к созданию новых оборонительных сооружений. Особенно веб-браузеры стали привлекательной мишенью для атак, учитывая их практическую важность и широкое использование, в дополнение к их сложности. Атаки на клиентские программы, такие как браузеры, сначала были обработаны неисполнимым стеком, чтобы предотвратить выполнение данных в стеке, а также неисполнимой кучей, чтобы остановить разбрызгивание кучи данных, которые впоследствии выполняются как код. Эта защита стала широко известной как W ⊕X (Writable xor eXecutable) или Data Execution Prevention (DEP), чтобы сделать любую область данных неисполнимой в 2003 год. Чтобы противостоять DEP, злоумышленники начали выполнять повторное использование кода, например, Return-Oriented Programming (ROP) и многие другие варианты. В общем, если злоумышленник знает местоположение статического кода в адресном пространстве уязвимой цели, он может подготовить поддельный стек с адресами этих гаджетов. Как только управление указателем инструкции получено, эти гаджеты выполняются в цепочке и выполняют нужные действия. Чтобы предотвратить повторное использование кода и подобные типы атак, в 2003 году была предложена рандомизация расположения адресного пространства (ASLR). Он рандомизирует структуру адресов, затрудняя поиск фрагментов кода для повторного использования. JIT-Spraying пригодился и здесь: если выражения с постоянными значениями языка высокого уровня - это Just-In-Time (JIT), скомпилированный в нативный код, который может использоваться для встраивания байтов вредоносного кода во время выполнения. Это обходит DEP, потому что данные (косвенно) внедряются как код. Кроме того, если злоумышленнику удастся создать много областей этого кода, их местоположение станет предсказуемым. Следовательно, распыляя много областей кода, можно предсказать адрес одной области, чтобы обойти ASLR. Наконец, для перенаправления потока управления на введенный код требуется только управление указателем инструкций. Таким образом, уязвимость, связанная с использованием после освобождения, путаница типов или переполнение буфера
Мы представляем обзор JIT-компиляторов в Разделе 2 и более детальный взгляд на JIT-Spray в Разделе 3. JIT-Spray позволил создать полные автономные полезные нагрузки, такие как произвольный Shell -код, который выполняется непрерывно. Кроме того, существуют методы распыления небольших фрагментов (так называемых JIT-gadgets), которые должны быть объединены в одну цепочку. Если их адреса предсказуемы, мы все равно считаем их JIT-Spray.
Однако, если для их обнаружения необходима уязвимость раскрытия памяти, мы считаем ее не JIT-Spray, а атакой, использующей повторное использование кода на основе JIT. Основная причина этого различия заключается в том, что раскрытие памяти обычно труднее достичь, чем контроль только над указателем инструкций. Следовательно, злоумышленник нуждается в большем контроле и должен вкладывать больше ресурсов, чем при использовании только JIT-Spray. Обзор атак на повторное использование кода на основе JIT представлен в Разделе 4. Обратите внимание, что повторное использование кода на основе JIT-Spray и JIT имеет общее свойство: внедренный код является непреднамеренным и обычно находится в середине потока команд, намеренно испускаемых компилятором JIT , Защитой, которая не только предотвращает выполнение непреднамеренного кода JIT, но также и непреднамеренного статического кода, такого как гаджеты ROP, является ControlFlow Integrity (CFI). Это гарантирует, что только предопределенные записи кода являются допустимыми целями для ветвей как в статическом, так и в JIT-коде. Важной реализацией CFI является Microsoft Control-Flow Guard (MS-CFG), которая также защищает области JIT-кода в Microsoft Edge. Совсем недавно LLVM-CFI появился в Google Chrome как эффективное решение CFI. Еще одним общим средством защиты от атак с использованием кода является защита от произвольного кода (ACG). Если этот параметр включен, кодовые страницы становятся недоступными для записи, а записываемые страницы исполняемыми. Это создает проблему для JIT-компиляторов, так как им нужно сначала написать код, а потом изменить разрешения на исполняемый файл. Следовательно, Microsoft Edge использует JIT-сервер вне процесса, который отображает динамический код в область памяти, совместно используемую процессом браузера. В разделе 5 мы опишем дополнительные проблемы безопасности, которые могут возникнуть в JIT-компиляторах. Существуют также специальные меры по устранению недостатков, связанных с JIT-Spray и JIT. Мы кратко представим некоторые из них, необходимые для обеспечения общего понимания для остальной части статьи. В разделе 6 мы предоставляем более глубокую и полную картину мер по снижению атак против злоупотреблений JIT-компиляторами. Одним из смягчающих факторов является удобная оптимизация компилятора, называемая постоянным сворачиванием: если компилятор (JIT) способен вычислять операции во время компиляции перед генерацией собственного кода, то только оставшиеся операции / результаты окажутся в собственном коде. Следовательно, не появляются константы, в которых злоумышленник мог бы встроить байты вредоносного кода. Более конкретной защитой от встраивания кода в константы является постоянное ослепление. Для этого непосредственное значение передается случайным ключом перед тем, как JIT-компилятор отправит его в собственную инструкцию. Как только он используется, он «не записывается», но непосредственное значение не появляется в нативном коде. Следовательно, это напрямую препятствует внедрению кода через JIT-Spray. Мы объясним защиту, связанную с JIT, более подробно в разделе 6. В целом, мы вносим следующие вклады:
• Мы предоставляем обзор JIT-Spray по архитектуре x86 и ARM, включая академическую работу, а также неакадемические атаки, и подробно описываем методы нападения. Самая последняя техника JIT-Spray появилась в ASM.JS в Mozilla Firefox, что мы проиллюстрируем более подробно.
• Мы отличаем JIT-Spray от атак с повторным использованием кода на основе JIT и объясняем ландшафт атак с повторным использованием кода на основе JIT-компилятора.
• Мы суммируем обходные пути смягчения, которые были возможны с помощью компиляторов JIT, и демонстрируем средства защиты, появившиеся в течение многих лет для защиты от различных недостатков, связанных с JIT.
Остальные разделы разбиты на категории в таблице 1. Он обеспечивает атаки повторного использования кода на основе JIT-Spray и JIT на основе достигнутой цели эксплойта в затронутых целях. (Я не знаю, что делать с этой тавтологией)
2 Just-In-Time Compilation
Как отмечалось ранее, самые популярные клиентские программы для обычных пользователей, несомненно, есть веб-браузеры. Такие браузеры, как Mozilla Firefox, Internet Explorer, Microsoft Edge, Google Chrome и Apple Safari, содержат среду выполнения JavaScript. JS является динамическим языком сценариев и позволяет удобно манипулировать веб-контентом, что является ключевым аспектом современного Интернета. Хотя движки JavaScript работают как интерпретаторы, они встраивают компиляторы Just-In-Time (JIT). Преимущества JIT компиляции по сравнению с интерпретацией JavaScript (байт-код) - это огромный выигрыш в производительности: вместо выполнения кода JavaScript в стиле виртуальной машины, нативный код испускается компиляторами JIT для процессора. Если функция перегревается, т.е. выполняется часто, запускается компиляция Just-In-Time и преобразует функцию в машинный код для архитектуры, на которой работает браузер.
Существует несколько компиляторов Just-In-Time и уровней оптимизации. Вообще говоря, JavaScript браузеры имеют одинаковый дизайн . Существует интерпретатор и один или несколько компиляторов Just-In-Time с разными уровнями оптимизации. Например, WebKit, который является основой Apple Safari, использует четырехуровневую стратегию оптимизации JavaScript: три уровня оптимизации JIT (LLint, Baseline, DFG, FTL). В то время как первый уровень интерпретирует JavaScript байт-код, второй-четвертый этапы JIT запускают увеличение количества выполнений функций и операторов. Таким образом, оптимизация становится больше. Точно так же ChakraCore, JavaScript движок Microsoft Edge, имеет несколько уровней.
Таблица 1: Защиты, обходимые JIT-Spray и атаками с повторным использованием кода на основе JIT, и предлагаемые меры по их снижению.
JIT-компиляция может быть разбита на параллельные фоновые потоки. В настоящее время Mozilla Firefox использует IonMonkey в качестве компилятора Just-In-Time, который разработан Cross-Architectur (Кросс-Архитектурным) способом для упрощения компиляции во время выполнения различных процессоров, таких как x86 и ARM. TurboFan является JIT-компилятором V8, движка JavaScript Google Chrome, и реализует собственный набор агрессивных оптимизаций, одновременно снижая сложность предыдущих JIT-компиляторов. Помимо браузеров, также виртуальная машина Java (JVM) использует интерпретатор и JIT-компилятор (Oracle HotSpot). Таким образом, скомпилированные двоичные файлы Java остаются переносимыми с использованием универсального байт-кода, который интерпретируется и JIT-компилируется в базовую архитектуру, на которой должна выполняться программа. Для полноты картины отметим, что в каркасе Microsoft dotNet имеется также JIT-компилятор (RyuJIT), и даже ядро Linux использует JIT-компиляцию для расширенных фильтров пакетов Berkeley (eBPF). Для популярного серверного языка PHP существует JIT-компилятор с именем HHVM, и язык Lua использует LuaJIT для динамической генерации собственного кода. Интересно, что, как мы объясним в следующем разделе, наиболее заметной атакой против JIT-компилятора была JIT Spray, нацеленная на виртуальную машину ActionScript (AVM) Adobe Flash
3 JIT-Spray
JIT-Spray - это элегантный метод эксплуатации, позволяющий обойти как DEP, так и ASLR. Хотя он не такой общий, как ROP, поскольку необходим JIT-компилятор, он значительно упрощает использование ошибок памяти.
3.1 JIT-Spray на x86
Длина инструкции архитектуры x86 является переменной. Следовательно, в пределах потока байтов кода каждое смещение байта является потенциальным началом инструкции, учитывая, что байты с каждым смещением декодируются с помощью x86. С точки зрения эксплуататора, это может быть использовано для внедрения кода: если злоумышленник имеет достаточный контроль над JIT-компилятором, он может заставить его выдавать инструкции, содержащие непосредственные значения, в то время как они сами содержат действительные байты инструкций. В 2010 году Блазакис обнаружил, что ActionScript Adobe Flash напрямую генерирует константы уровня скрипта в машинном коде. Рассмотрим длинное выражение XOR в ActionScript, как показано в коде 1.
Код 2: Предполагаемый нативный код, испускаемыйJIT-компилятором ActionScript
Затем противник вынуждает JIT-компилятор генерировать достаточно копий собственного машинного кода, чтобы их адреса в памяти стали предсказуемыми (в 32-разрядных системах). Затем она может перенаправить поток управления на предсказанный адрес и выполнить введенный код. Вероятность 80%, что удар по NOP-Sled не будет. В одном из пяти случаев (20%) поток инструкций будет поражен, и эксплойт не потерпит неудачу.
Код 3: Внедренный код, не предназначенный для ActionScript
JIT-компилятор.
Это было рождение JIT-Spray, и дальнейшие атаки не заставили себя долго ждать. В 2010 году Синцов показал, как автоматизировать и писать Shell-код для атак JIT-Spray. Проблемы с инструкциями, размер которых превышает три байта, но большинство из них можно преобразовать в семантически эквивалентные инструкции, которые не превышают трех байтов. Например, «MOV EAX, 0x41424344» приводит к пятибайтовой инструкции. Однако его можно разделить на три инструкции, выполняющие одну и ту же операцию: «MOV EAX, 0x41424yyzz» генерируется путем управления тремя байтами и предоставления компилятору JIT возможности манипулировать двумя байтами (yyzz). Они устанавливаются отдельно с помощью двух инструкций размером два байта: «MOV AH, 0x43» и «MOV AL, 0x44». Другой хитрый трюк заключался в использовании 0x6A вместо 0x3C в качестве маскирующего байта. Таким образом, вместо создания семантического NOP, который изменяет флаги ЦП (3C35 cmp al, 0x35), появилась команда push (6A35 push 0x35). Это позволило противникам использовать условные переходы (т.е. JNZ) впоследствии. JIT-Spray также был возможен в версии JavaScript Apple Safari для Windows в 2010 году. Поскольку JIT-компилятор выдавал много кода между контролируемыми константами JavaScript, автор использовал два байта из константы в качестве байтов полезной нагрузки, а два других байта в качестве безусловного перехода к следующей константе. Рассмотрим константу 0x14EB9090 в операции JavaScript. Компилятор Apple Safari JavaScriptCore baseline-JIT генерировал код, который можно использовать следующим образом (см. Код 4):
Код 4: Использование констант JavaScript для соединения двух байт полезной нагрузки с помощью коротких переходов. Хотя только два байта эффективно используются для злонамеренных целей, Синцов показал, что произвольные операции все еще возможны. Это включало запись вредоносной полезной нагрузки на записываемую и исполняемую страницу JIT, а затем переход к ней. В 2010 и 2011 годах было проведено глубокое исследование, особенно на JIT-компиляторах LLVM и Mozilla Firefox . Авторы продемонстрировали, что бывшие JIT-движки Mozilla Firefox, такие как JaegerMonkey и TraceMonkey, были склонны к JIT-Spray. Кроме того, они показали, что значения с плавающей запятой, такие как -6.828527034422786e-229, могут использоваться для JIT-Spray, поскольку шестнадцатеричное представление значения из восьми байтов 0x90 было напрямую передано в области исполняемого кода. Для TraceMonkey и LLVM они смогли заставить JIT-компилятор выдавать небольшие фрагменты кода, пригодные для атак с повторным использованием кода, которые они назвали gaJIT. Кроме того, авторы исследовали меры по смягчению в различных движках JIT и обнаружили, что большинство из них не применяют достаточную защиту от JIT-Spray (см. Также Раздел 6). С помощью повторного использования кода в 2013 году Серна показал, что все еще можно позволить JIT-компилятору Flash ActionScript генерировать небольшие фрагменты кода по предсказуемым адресам. Несмотря на то, что полная полезная нагрузка была невозможна из-за мер по уменьшению, которые Adobe включала в то время (т. е. Случайные NOPs, см. Раздел 6), эти небольшие фрагменты JIT использовались для утечки адресов возврата из стека. JIT-Spray также повлиял на JVM: в 2013 году было показано, что константы в операциях XOR в Java передавались в исполняемый код. Как и в Коде 3, три байта были пригодны для внедрения кода. Генерация нескольких классов и функций, содержащих операции, запускает генерацию кода по предсказуемым адресам. Следовательно, DEP и ASLR были обойдены, управляя указателем команд с уязвимостью повреждения памяти.
Одна из последних атак JIT-Spray, затрагивающая JIT-компиляторы, была опубликована в 2016 году: шейдеры WebGL можно было использовать в JavaScript Internet Explorer и Microsoft Edge [76]. JIT-компилятор WARP создал собственный код, не защищенный MS-CFG. Таким образом, авторам удалось внедрить код по предсказуемым адресам с помощью JIT-компилятора шейдера Windows Advanced Rasterization Platform (WARP). JIT-Spray также возможен в ядре Linux, если доступны расширенные фильтры пакетов Berkeley (eBPF) (которые по умолчанию отключены). Однако этот метод обходит защиту, которая запрещает ядру выполнять код, предоставляемый пользовательским пространством, таким как SMEP и KERNEXEC. Было показано, что сборка программы BPF и создание множества сокетов с прикрепленными (BPF) фильтрами приводит к созданию JIT-Spray внутри ядра. Таким образом, управляемая злоумышленником полезная нагрузка может быть выполнена, например, порождая корневую оболочку, когда поток управления перехвачен.
3.2 Тематическое исследование: ASM.JS
Мы опубликовали новую атаку JIT-Spray в 2017 году. Разница заключается в том, что она предназначена не для компилятора JIT, а для компилятора Ahead-Of-Time (AOT) в 32-битном Mozilla Firefox в Windows. Технически говоря, компиляторы AOT не генерируют и не оптимизируют код, если и после того, как определенный высокоуровневый код уже был выполнен несколько раз, но до того, как он был выполнен в первый раз. ASM.JS - это компилятор AOT, использующий подмножество JavaScript. Он соответствует определенному синтаксису и появился в 2013 году в Mozilla Firefox.
Код 5: Простой модуль ASM.JS с функцией, возвращающей 32-разрядное целое число. Простой модуль ASM.JS, который скомпилирован заранее без выполнения, показан в коде 5. Загрузка веб-страницы, содержащей код, достаточна для запуска AOT (Ahead-of-Time). Этот модуль запрашивался много раз, и мы обнаружили, что несколько копий собственного кода были отправлены на предсказуемые адреса (см. Код 6).
Код 6: Запрос модуля ASM.JS несколько раз для распыления множества копий кода на предсказуемые адреса. Поскольку константы не были слепыми, они появлялись как непосредственные значения в операциях с собственным кодом и могли использоваться как идеальная цель для скрытия контролируемых злоумышленниками байтов полезной нагрузки. В коде 7 показан собственный код, сгенерированный компилятором AOT, и показано, что константа 0xc1c2c3c4 появляется непосредственно в областях исполняемого кода. Кроме того, эти регионы расположены несколько раз по предсказуемым адресам, что делает ASLR неэффективным.
Код 7: четырехбайтовая константа в собственном коде простой копии кода ASM.JS, многие из которых отправляются по предсказуемым адресам. Нам удалось использовать несколько операций для распыления вредоносного кода по предсказуемым адресам. Обзор представлен на рисунке 1. Помимо прочего, мы определили, что арифметические операции, установка элементов массива и передача параметров вызовам сторонних функций хорошо подходят для встраивания байтов скрытого кода. Однако наиболее интересный метод связан с первым методом JIT-Spray в ARM (см. Раздел 3.3): когда значения с плавающей запятой используются в качестве параметров при вызове функции в модуле ASM.JS, они не отображаются непосредственно в нативный код Вместо этого выдаются инструкции, косвенно ссылающиеся на эти параметры (см. Код 8).
Код 8: вызов функции в ASM.JS с двойными параметрами с плавающей запятой и дизассемблированием сгенерированных констант, ссылающихся на собственный код в той же области кода. Однако константы находятся в одной и той же исполняемой области и являются непрерывными в памяти (Я попытался это перевести нормально). Это очень удобно для злоумышленника, потому что он может использовать все восемь байтов значения с двойным плавающим значением в качестве полезной нагрузки и вводить непрерывный Shell-код без прерывания другими кодами операций. С помощью различных эксплоитов было продемонстрировано, что этот метод выполним и упрощает эксплуатацию.
Таблица 1: Операция ASM.JS и соответствующий испущенный собственный код встраивают контролируемые злоумышленником байты кода в непосредственные значения уязвимостей повреждения памяти. Поскольку злоумышленники могут воздерживаться от раскрытия памяти и повторного использования кода, необходим только контроль указателя инструкций. Мы также разработали инструмент для преобразования полезных нагрузок в его форму ASM.JS, которая затем во время выполнения передается компилятору AOT в собственный код. Хотя в качестве байтов полезной нагрузки используются только два или три байта из константы высокого уровня, все еще возможно выполнить произвольный код. Например, код stage0 разрешается и вызывает функцию API Windows VirtualAlloc (), затем копирует в нее большую полезную нагрузку (stage1) и выполняет ее.
3.3 JIT-Spray на ARM
Фундаментальное свойство, делающее возможным использование JIT-Spray на платформе x86, недоступно в архитектуре ARM: собственные инструкции имеют фиксированный размер либо 32-разрядный, либо выровнены по четырехбайтовым адресам (режим ARM), либо имеют размер 16-разрядный и выровнены по двухбайтовым границам (режим Thumb). Кроме того, режим Thumb-2 добавляет новые 32-битные инструкции в Thumb и позволяет смешивать 16-битные и 32-битные инструкции. Следовательно, вставлять произвольный код намного сложнее, чем в x86. Однако первый тип JIT-Spray на ARM, о котором мы знаем, использовал константы с плавающей точкой в ActionScript. Протестированный движок ActionScript JIT сгенерировал код с относительными к ПК ссылками на константы. Кроме того, значения с плавающей точкой находились на той же странице, что и код, и были непрерывными в памяти. Это позволило сделать непрерывный Shell-код без других разрушающих кодов операций.
Код 9: функция JavaScript, используемая в JavaScriptCore на ARMv7-A.
Lian et al.. более подробно исследовал идею JIT-Spray на ARMv7-A [33]. JIT-компилятор JavaScriptCore (из WebKit) генерировал при определенных обстоятельствах гаджеты, подверженные влиянию злоумышленников по предсказуемым адресам. Фрагмент JavaScript, скомпилированный в код, содержащий гаджет, показан в коде 9. DFG JIT генерирует полезные инструкции (см.Код 10). Обратите внимание, что большинство инструкций гаджета были намеченными инструкциями, только первая инструкция была непреднамеренной: вместо выполнения предполагаемой 32-битной инструкции, выровненной по четырехбайтовой границе, выполнение началось с перехода на вторую половину. Затем выполнение ресинхронизируется с намеченным потоком команд. В коде 11 показаны инструкции для гаджета для функции readGadget. Выполнение начинается в середине инструкции по смещению 0x38 и повторной синхронизации по смещению 0x3a. Тем не менее гаджет предоставляет злоумышленнику возможность считывать память и возвращать содержимое в виде 32-разрядных целых чисел в JavaScript.
Код 10: собственный код, созданный для кода readGadgetJavaScript. Более подробную информацию можно найти в оригинальной статье. Этот и подобные гаджеты были вызваны высокоуровневым кодом JavaScript для выполнения связанных операций. Таким образом, авторы достигли возможности записи Shell-кода в записываемую кодовую страницу и выполнения его, имея контроль только за счетчиком программы.
Код 11: непреднамеренный поток инструкций для JavaScript-кода readGadget, способный считывать память, указанную регистром R2. В последующей работе Lian et al. предназначался для JIT-компилятора Mozilla Firefox IonMonkey из его движка SpiderMonkey JavaScript на ARM. Они смогли построить полную самодостаточную нагрузку JIT по предсказуемым адресам, не полагаясь на гаджеты. Они принудительно генерировали 32-битные инструкции ARM AND, имея по 20 битов контроля над каждой из контекста JavaScript. Им удалось интерпретировать эту инструкцию как две 16-битные инструкции Thumb-2. Вооружившись этой возможностью, первая инструкция используется для выполнения полезных операций для атакующего. Вторая инструкция используется в качестве безусловной относительной ветви ПК к следующей непреднамеренной команде, которая должна быть выполнена. Кроме того, он предотвращает переключение обратно в поток команд, предназначенный для компилятора. В целом, этот полный JIT-Spray на ARM был первым в своем роде, и авторы опровергли мнение о том, что JIT-Spray невозможен на архитектурах RISC с фиксированной длиной инструкций и фиксированными границами инструкций.
4.Повторное использование кода на основе JIT
Первая (академическая) работа, в которой использовались скомпилированные во время выполнения гаджеты из JIT-компилятора, возникла из-за необходимости обходить защиту от повторного использования кода в 2015 году. Если статический код программы не содержит гаджетов, то повторное использование кода обычно не вариант. Однако если гаджеты создаются компилятором JIT, повторное использование кода снова становится возможным. Athanasakis et al. предназначался для IonMonkey в 32-битном Linux и Chakra в 64-битном Internet Explorer 9 в Windows. В общем, они спровоцировали JIT-компилятор на генерацию гаджетов, содержащих всего несколько инструкций, и использовали двухбайтовые константы JavaScript. Это обошло постоянное ослепление в Internet Explorer и других защитах, связанных с JIT, которые были включены в то время. Однако обратите внимание, что авторам требовалось раскрытие памяти, чтобы найти гаджеты в памяти. Следовательно, мы не считаем его JIT-Spray, поскольку JIT-Spray не требует утечки информации, а только контролирует указатель инструкций, чтобы перенаправить поток управления на заранее определенный адрес, содержащий скомпилированный JIT код злоумышленника. В 2015 году были обнаружены другие недостатки в движке Chakra JavaScript Internet Explorer, связанные с повторным использованием JIT-кода. В то время как Chakra применяет постоянное ослепление, выражения деления в JavaScript с 32-разрядными целыми числами привели к появлению не слепых четырехбайтовых констант, содержащих внедренный код. Кроме того, авторы показали, что две 16-разрядные константы передавались непосредственно в одну инструкцию x86, когда первая использовалась в качестве индекса массива, а вторая - в качестве элемента массива. Чтобы иметь возможность перейти к этому внедренному коду, они использовали инструкцию «JMP ECX» самого движка JavaScript, который не был защищен MS-CFG. Тем не менее, как введенный код, так и инструкция перехода были доступны только для раскрытия памяти. Следовательно, мы не считаем его JIT-Spray, а используем его повторно. Мы также хотим отличить термин JIT-ROP от тех методов нападения, которые представлены в этой статье. Он не связан с JIT-компиляторами. Он просто описывает метод многократного определения местоположения, чтения и разборки статического кода с помощью e. г. раскрытие памяти в JavaScript. Затем полезная нагрузка повторного использования кода может быть построена точно в срок. Это необходимо, если к целевым двоичным файлам применяется мелкозернистая рандомизация кода, поскольку она скрывает не только базовые адреса модулей в адресном пространстве, но также записи функций, базовые блоки и адреса инструкций.
Таблица 2: Код JavaScript и соответствующий выданный код JIT отсутствуют для скрытия четырехбайтовых констант в Google Chrome (найден Dachshund). Тем не менее, защита от JIT-ROP была ослаблена с помощью JIT-скомпилированных гаджетов: одним из таких средств защиты является память «только для исполнения». Он запрещает чтение кода, но адреса JIT-гаджетов все еще могут быть найдены по средствам последующей утечки читаемого объекта данных, пока не будут обнаружены указатели на гаджеты. Maisuradze и др. Смогли заставить JIT-компиляторы Internet Explorer, Google Chrome и Mozilla Firefox скрывать код в ветвях. Их тщательно сконструированный код JavaScript приводил к инструкциям потока управления, таким как условные переходы и прямые вызовы. Их целевые адреса и смещения имели скрытые байты кода и, следовательно, могли использоваться в качестве гаджетов для атак с повторным использованием кода.
В качестве защиты, постоянное сворачивание или ослепление не является опцией, поскольку неявные константы находятся в пределах (относительных) вызовов / переходов. Следовательно, они предложили исключить все неявные константы, заменив их инструкции косвенного управления потоком. Как мы упоминали во введении и описывали в смягчениях(см. Раздел 6), длинные полезные нагрузки и гаджеты JIT-Spray предотвращаются постоянным ослеплением. Однако Maisuradze et al. также пытались найти ”не ослепленные” четырехбайтовые константы, несмотря на четырехбайтовое постоянное ослепление в современных веб-браузерах. В целом удалось найти несколько операций JavaScript с константами, которые JIT-компилятор кодирует в память в Google Chrome и Microsoft Edge. На таблице 2 представлены их результаты в Google Chrome.
5.Злоупотребление недостатками JIT-компилятора
Методы, которые обсуждались в предыдущих двух разделах, основывались на внутренней работе компиляторов JIT и шли в направлении обходных путей для предотвращения эксплойтов. Далее мы представляем атаки, которые могут рассматриваться как основанные на недостатках и ошибках компиляторов JIT, но, пожалуйста, обратите внимание, что различие может иногда быть нечетким.
5.1 Больше обходных путей смягчения
Очень популярным средством смягчения последствий является DEP. Несмотря на то, что JIT-Spray обходит DEP, была также возможность перезаписать испускаемый код, если права доступа к кодовым страницам доступны для записи. В настоящее время ни статический код, ни JIT-скомпилированный код не должны быть одновременно исполняемыми и записываемыми. В настоящее время это не относится к Google Chrome. Тем не менее, даже если W ⊕X включен для регионов JIT, они должны быть записаны в первую очередь и быть исполняемыми впоследствии. В 2015 году Song et al. использовал это маленькое временное окно для перезаписи кэшей кода в многопоточном коде с использованием веб-работников [72]. Следовательно, им удалось внедрить код несмотря на W ⊕X в динамических областях кода. В 2016 году Chakra Internet Explorer подверглась аналогичной атаке. Авторы сделали три шага: во-первых, они запустили JIT-компилятор для кодирования большой области кода. Это создало временное окно, в котором фоновый поток работал над временным буфером для записи кода. На втором этапе этот буфер находился с раскрытиями памяти и перезаписывался вредоносным кодом на третьем этапе. Благодаря этой атаке они смогли обойти DEP и MS-CFG. В 2017 году Frassetto et al. продемонстрировал атаку только на данные на промежуточное представление (IR) Chakres в Microsoft Edge. Вместо создания / изменения кода или указателей кода они создали вредоносный объект C ++, представляющий операторы IR, с обязательным условием примитива чтения / записи из JavaScript. Поскольку JIT-компилятор использует эти объекты для генерации собственного кода, авторы смогли создавать и выполнять свой код по своему выбору. Fratrik подробно исследовал JIT-компилятор Microsoft Edge в 2018 году. Среди других недостатков он смог внедрить свой код выбора, чтобы обойти ACG, злоупотребляя архитектурой сервера JIT.
Microsoft Edge использует отдельный сервер JIT для генерации динамического кода для процесса браузера (контента). Этот процесс разделяет область памяти с процессом браузера и отображает динамический код в эту общую область. JIT-сервер получает доступ к региону с правами на запись, в то время как процесс браузера имеет исполняемый вид региона. Таким образом, процесс браузера подчиняется ACG: он не может модифицировать или создавать кодовые страницы сам по себе, он может только выполнять их. Автор обнаружил, что JIT-сервер можно обмануть, чтобы сделать управляемую злоумышленником память в исполняемом процессе браузера: если процесс браузера скомпрометирован, исполняемое представление общей области может быть не отображено. Затем злоумышленник распределяет доступную для записи память по тому же адресу и записывает в нее полезную нагрузку. Процесс JIT с радостью использует следующий адрес и пометит его как исполняемый без изменения полезной нагрузки. Таким образом, ACG был обойден без непосредственного вмешательства в содержание кодовых страниц. Обновление безопасности исправило проблему.
5.2 Уязвимости JIT-компилятора
JIT-компиляторы представляют собой сложные программные системы, как и другие компиляторы. Следовательно, естественно, что они содержат критические ошибки безопасности. Несмотря на то, что постоянно обнаруживаются уязвимости, мы хотим кратко перечислить наиболее значимые на момент написания статьи образцовым образом. DFG JIT от Apple Safari стал жертвой ошибок оптимизации во время конкурсов Pwn2Own в 2017 и 2018 годах. В оба года это было отправной точкой для участников, чтобы повысить свой уровень привилегий с помощью дополнительных методов эксплуатации. Одна интересная ошибка JIT-оптимизации в Google Chrome V8 дала злоумышленнику очень мощные примитивы, такие как утечка произвольной памяти и создание произвольных объектов JavaScript. Выполнение кода было достигнуто путем записи кода на страницу JIT. Для более подробной информации читатель ссылается на оригинальное исследование. JIT-компилятор в Chakra также подвержен уязвимостям. Например, CVE-2018-0953 позволил создать путаницу типов, установив элемент массива с магией значение. Это стало возможным, потому что JIT-компилятор пропустил проверку чека динамического кода.
6.Смягчения
Прежде чем углубляться в более общие меры по смягчению воздействия JIT-Spray и других средств защиты, связанных с JIT, мы рассмотрим исправления для ASM.JS JIT-Spray из Раздела 3.2. Mozilla назначила CVE-2017-5375 и CVE-2017-5400 для этого метода обходного обхода. Было назначено два CVE, потому что первого исправления было недостаточно: рандомизация для выделения кода была увеличена, но при определенных обстоятельствах старый код, отвечающий за передачу областей ASM.JS по предсказуемым адресам, мог все еще срабатывать. Последующее исправление изменило схему распределения: при запуске, когда адресное пространство почти пусто, случайный диапазон адресов зарезервирован для выделения кода ASM.JS. Местоположение этого диапазона трудно предсказать атакующему. Позднее, во время выполнения, как только запрашиваются модули ASM.JS, регионы из этого набора страниц объединяются и высвобождаются при необходимости. Поскольку исходный код является общим для ASM.JS и WebAssembly, эта схема защиты (также известная как рандомизация выделения) также используется для выделения кода WebAssembly в Mozilla Firefox. Начальный диапазон адресов дополнительно ограничен определенным количеством страниц, чтобы предотвратить исчерпание адресного пространства во время распыления (также известное как ограничение выделения). Мы уже объясняли постоянное свертывание во введении. Adobe включила постоянное свертывание в JIT-компилятор Flash ActionScript, чтобы противостоять исходным атакам JIT-Spray. Это предотвратило непосредственные контролируемые злоумышленником константы в коде JIT. Тем не менее, его вскоре обошли с помощью оператора ActionScript IN. Достаточно было использовать одну операцию IN в операции, иначе содержащей OR, чтобы обмануть JIT-компилятор и снова получить немедленные значения, контролируемые атакующим. Код ActionScript «0x3c909090 IN 0x3c909090 | 0x3c909090 | ... »снова дали нативные салазки, когда выполнялись с не смещенным смещением. С постоянным ослеплением была разработана и внедрена защита, которая предотвращает контролируемые злоумышленником немедленные значения в коде JIT. Таким образом, компилятор во время компиляции копирует непосредственное значение со случайным ключом, неизвестным злоумышленнику. Прежде чем значение используется в операции в собственном коде, оно записывается с этим ключом во время выполнения. Таким образом, все операции в нативном коде остаются действительными,
*- по умолчанию 32-битный, переход на 64-битный требует изменения в реестре (не по умолчанию)
◦- применяется LLVM-CFI
†- применяется MS-CFG
‡- частичное внедрение MS-CFG
#- не обязательно для исполняемых областей JIT
но не легко предсказуемы противниками, и, следовательно, скрыть байты кода невозможно. Поскольку на производительность влияют ослепляющие константы, обычно только четырехбайтовые или большие константы являются слепыми. Воздействие слишком радикально для двухбайтовых или меньших констант. В результате это приводит к возможности скрыть код и позволить компилятору JIT создавать
гаджеты снова. Еще одна возможность сделать скрытый код сразу
Менее предсказуемы случайные числа. Различные инструкции типа NOP различных размеров, такие как «LEA ESP, [ESP]», «XCHG EDX, EDX» или «MOV ECX, ECX», могут смешивать запланированные операции, скомпилированные JIT. Если они неизвестны атакующему, Jumping to a NOP с помощью указателя инструкции может завершиться неудаче из за случайных выпадов. Adobe включила эту технику в 2011 году в JIT-компилятор ActionScript, но частота выброса случайных NOP была слишком мала, чтобы сломать маленькие гаджеты JIT. Однако это предотвратило полную самостоятельную загрузку JIT. Точно так же сдвиг предполагаемого кода JIT со случайным базовым смещением в областях кода добавляет непредсказуемость к местоположению введенных байтов кода. Не занимаясь непосредственной защитой JIT-Spray или JIT-атак с повторным использованием кода.
JIT-кодовые страницы без флага записи не могут быть перезаписаны, а защитные страницы не позволяют произойти переполнению буфера для достижения JIT-кодовых страниц (в случае, если они доступны для записи и смежны с объектами кучи). Например, регионы JIT-кода в Mozilla Firefox больше не доступны для записи с конца 2015 года. В 2011 году JitDefender уже использовал механизмы для преобразования W ⊕ X в JIT-код ActionScript и JavaScript. При таком подходе регионы JIT-кода не выполнялись и переключались на только исполняемые, если был сделан законный вызов регионов JIT. Это предотвратило незаконное выполнение полезных нагрузок, обработанных JIT, поскольку переходы к нему попадали в код, который не был исполняемым. Librando, JIT-инфраструктура, может применять некоторые из этих защит к компиляторам COTS JIT в «черном ящике». Таким образом, функции выделения операционной системы перехватываются, чтобы анализировать, диверсифицировать и перезаписывать скомпилированный код, который должен быть точно в срок. К JIT-коду применяются рандомизация распределения, постоянное ослепление, случайная вставка NOP и различные оптимизации. Авторам удалось укрепить Java и V8 с замедлением от 1,15 до 3,5 раз. RIM, JITSafe и INSeRT используют метод Obfuscation, Diversification и рандомизацию аналогичным образом, чтобы разбить скрытый код на непосредственные значения и препятствовать JIT-Spray. В то время как целостность потока управления стала очень популярной для статического кода, для динамического кода мало CFI.
RockJIT - это одна система, нацеленная на динамическое обеспечение целостности потока управления для собственного кода. Он применяет политики к коду JIT и JIT-компилятору сам. Среди прочих методов, после передачи анализа исходного кода, используются проверки, чтобы разрешить только цели действительного ввода кода быть целью косвенных ветвей. Это мешает JIT-Spray, потому что байты нерасширенного кода в середине предполагаемой инструкции являются недопустимыми целями кода. Авторы применили RockJIT к V8 с падением производительности на 14,6%. Native Client (NaCl), формально собственный движок кода в Google Chrome, похож на RockJIT. Он применяет программную изоляцию отказов (SFI) в контексте браузера. Код выровнен по 32-байтовым фрагментам, а косвенным ветвям разрешено только переходить к началу фрагментов. Это выполняется путем проверки масок адресов перед выдачей косвенной ветви. Последующая работа пыталась улучшить CFI для кода JIT. JITScope работает с инфраструктурой LLVM и может не только укрепить код JIT и JIT-компилятор, но и приложение в целом с помощью CFI. Авторы применили JITScope к TraceMonkey с влиянием на производительность менее 10%. В настоящее время CFI включен для кода JIT в Microsoft Edge с MS-CFG, а LLVM-CFI включен в Google Chrome. Grsecurity использует RAP, передовое и обратное решение CFI для Linux, предотвращающее выполнение непреднамеренных инструкций. Подобно разделению JIT вне процесса в Microsoft Edge, в 2014 году Lobotomy предложила двухпроцессную модель. Процесс браузера имеет исполняемое представление области совместно используемой памяти, в то время как процесс JIT имеет доступное для записи представление этой области. Таким образом, только JIT процесс может создавать код JIT и манипулировать им, а уязвимости в процессе браузера не могут изменять или перезаписывать регионы JIT. Было предложено несколько других способов защиты от недостатков, связанных с JIT, таких как JIT-Sec. JIT-Sec применяет монитор к JIT-коду для запрета системных вызовов. Это помогает нарушать самодостаточные полезные нагрузки JIT-Spray, которые выдают системные вызовы, когда авторы считают, что предполагаемый код JIT не выполняет системные вызовы. Еще одна сложная защита, называемая JIT-Guard, использует архитектуру Intel SGX и различные другие методы для защиты от внедрения кода, повторного использования кода и атак только на данные.
Критические части JIT-компилятора изолированы в анклавах SGX, для кода JIT добавлен уровень рандомизации, и раскрытия памяти решаются с помощью уровня косвенности. Основным изменением, которое можно рассматривать как неявное смягчение, является переход от 32-разрядных к 64-разрядным архитектурам. В настоящее время все основные веб-браузеры, кроме Internet Explorer, работают как собственные 64-битные x86-приложения. Аналогично, платформа ARM имеет 64-битную архитектуру под названием AArch64, которая поддерживает 64-битное адресное пространство. Поскольку 64-разрядное адресное пространство больше, чем 32-разрядное, JIT-Spray кажется невозможным, поскольку распыление многих областей кода для получения предсказуемого адреса невозможно. Тем не менее, повторное использование JIT-кода все еще может быть полезным, если раскрытие памяти может осуществляться в 64-разрядных целях. Таблица 2 суммирует влияние (безопасности) функций на повторное использование JIT-Spray и JIT-кода. Если выброс непреднамеренных байтов кода (внедрение кода), распыление в предсказуемые места и выполнение непреднамеренного кода полностью предотвращены, то JIT-Spray недопустим. Имея комбинацию постоянного ослепления, 64-битного адресного пространства, высокой степени ASLR и CFI, можно достичь этой цели, если все будет правильно реализовано.
7.Заключение
JIT-Spray - это метод, который удобно упростил использование уязвимостей, приводящих к повреждению памяти, поскольку он обходит DEP и ASLR. В то время, когда начали уменьшаться эксплойты, перехват потока управления был все еще достаточен для выполнения произвольного (удаленного) выполнения кода. С 2010 года, когда появился JIT-Spray, многие JIT-компиляторы были уязвимы для него. В этой статье мы подробно рассмотрели эту технику и показали, какие методы используются для сокрытия байтов кода в константах языков высокого уровня. Кроме того, мы опросили затронутые цели на архитектуре x86 и ARM и установили связь с атаками с повторным использованием кода, которые используют JIT-компиляторы. В то время как мы предоставили обзор дополнительных недостатков, которые могут возникнуть в JIT-движках в качестве примера, мы более подробно рассмотрели последний недостаток JIT-Spray, который затронул компилятор AOT ASM.JS в 2017 году в Firefox. Атаки и защита на основе JIT-компилятора все еще остаются живой областью исследований, как показывает множество (академических) мер по смягчению. Переход браузеров на 64-битные и рандомизация размещения адресного пространства с высокой энтропией, по-видимому, делает традиционный JIT-Spray неосуществимым. Однако в свете раскрытия памяти, несовершенной реализации CFI и неполного (то есть двухбайтового) постоянного ослепления повторное использование JIT-кода по-прежнему является ценным активом для злоумышленников. Кроме того, недостатки в JIT-компиляторах (например, уязвимости) остаются привлекательными целями. Несмотря на то, что ситуация с эксплойтами уменьшается, а использование уязвимостей повреждения памяти становится все труднее, мы не хотим исключать возможность повторного появления JIT-Spray в будущих целях. Конечно, защита становится все более изощренной, как и злоумышленники.
Благодарность
Мы хотели бы поблагодарить Мэтта Миллера за то, что он подготовил этот документ и за его плодотворные предложения и комфортное сотрудничество. Кроме того, мы благодарим наших анонимных рецензентов за конструктивную обратную связь, которая помогла нам улучшить статью. Эта работа была поддержана Европейской комиссией в рамках проекта H2020 DS-07-2017 «РЕАКТ» в рамках Грантового соглашения № 786669.
И того более 6 600 слов.
Переведено cпециально для xss.pro
neopaket
Благодарю модератора weaver за предложение данного текста для перевода и admin за поддержку треда tabac про перевод статей для ныне полюбившегося мне форума XSS.
Оригинальная статья: https://www.usenix.org/system/files/conference/woot18/woot18-paper-gawlik.pdf
С конца 20-го века стало ясно, что веб-браузеры будут играть решающую роль в доступе к интернет-ресурсам, таким как WWW. Они превратились в сложные программные пакеты, которые способны обрабатывать множество форматов данных. Компиляция Just-In-Time (JIT) была включена для ускорения выполнения кода скрипта, но также используется помимо веб-браузеров для повышения производительности. Злоумышленники с радостью приветствовали JIT по-своему, и до сегодняшнего дня JIT-компиляторы являются важной целью различных атак. Это включает в себя, например, JIT-Spraying атаки с повторным использованием кода на основе JIT и специфичные для JIT недостатки, позволяющие обойти методы смягчения, чтобы упростить использование уязвимостей, приводящих к повреждению памяти. Кроме того, JIT-компиляторы являются сложными и обеспечивают большую поверхность атаки, что видно в постоянном потоке критических ошибок, появляющихся в них. В этой статье мы рассмотрим и систематизируем джунгли компиляторов JIT основных (клиентских) программ и дадим категоризацию методов злоупотребления компиляцией JIT. Таким образом, мы представляем методы, используемые в академических, а также в неакадемических работах, которые пытаются сломать различные средства защиты от уязвимости повреждения памяти. Кроме того, мы обсудим какие меры могут привести к ужесточению JIT-компиляторов для предотвращения их использования злоумышленниками, желающими использовать компиляторы Just-In-Time.
1. Введение
Поскольку стало ясно, что ошибки памяти, особенно переполнения буфера в стеке, могут использоваться для выполнения произвольного кода, управляемого злоумышленником, в течение многих лет было предложено множество атак и защит. По сравнению с гонкой вооружений, появились новые защитные сооружения, которые вскоре были разрушены из-за новых атак, которые снова привели к созданию новых оборонительных сооружений. Особенно веб-браузеры стали привлекательной мишенью для атак, учитывая их практическую важность и широкое использование, в дополнение к их сложности. Атаки на клиентские программы, такие как браузеры, сначала были обработаны неисполнимым стеком, чтобы предотвратить выполнение данных в стеке, а также неисполнимой кучей, чтобы остановить разбрызгивание кучи данных, которые впоследствии выполняются как код. Эта защита стала широко известной как W ⊕X (Writable xor eXecutable) или Data Execution Prevention (DEP), чтобы сделать любую область данных неисполнимой в 2003 год. Чтобы противостоять DEP, злоумышленники начали выполнять повторное использование кода, например, Return-Oriented Programming (ROP) и многие другие варианты. В общем, если злоумышленник знает местоположение статического кода в адресном пространстве уязвимой цели, он может подготовить поддельный стек с адресами этих гаджетов. Как только управление указателем инструкции получено, эти гаджеты выполняются в цепочке и выполняют нужные действия. Чтобы предотвратить повторное использование кода и подобные типы атак, в 2003 году была предложена рандомизация расположения адресного пространства (ASLR). Он рандомизирует структуру адресов, затрудняя поиск фрагментов кода для повторного использования. JIT-Spraying пригодился и здесь: если выражения с постоянными значениями языка высокого уровня - это Just-In-Time (JIT), скомпилированный в нативный код, который может использоваться для встраивания байтов вредоносного кода во время выполнения. Это обходит DEP, потому что данные (косвенно) внедряются как код. Кроме того, если злоумышленнику удастся создать много областей этого кода, их местоположение станет предсказуемым. Следовательно, распыляя много областей кода, можно предсказать адрес одной области, чтобы обойти ASLR. Наконец, для перенаправления потока управления на введенный код требуется только управление указателем инструкций. Таким образом, уязвимость, связанная с использованием после освобождения, путаница типов или переполнение буфера
Мы представляем обзор JIT-компиляторов в Разделе 2 и более детальный взгляд на JIT-Spray в Разделе 3. JIT-Spray позволил создать полные автономные полезные нагрузки, такие как произвольный Shell -код, который выполняется непрерывно. Кроме того, существуют методы распыления небольших фрагментов (так называемых JIT-gadgets), которые должны быть объединены в одну цепочку. Если их адреса предсказуемы, мы все равно считаем их JIT-Spray.
Однако, если для их обнаружения необходима уязвимость раскрытия памяти, мы считаем ее не JIT-Spray, а атакой, использующей повторное использование кода на основе JIT. Основная причина этого различия заключается в том, что раскрытие памяти обычно труднее достичь, чем контроль только над указателем инструкций. Следовательно, злоумышленник нуждается в большем контроле и должен вкладывать больше ресурсов, чем при использовании только JIT-Spray. Обзор атак на повторное использование кода на основе JIT представлен в Разделе 4. Обратите внимание, что повторное использование кода на основе JIT-Spray и JIT имеет общее свойство: внедренный код является непреднамеренным и обычно находится в середине потока команд, намеренно испускаемых компилятором JIT , Защитой, которая не только предотвращает выполнение непреднамеренного кода JIT, но также и непреднамеренного статического кода, такого как гаджеты ROP, является ControlFlow Integrity (CFI). Это гарантирует, что только предопределенные записи кода являются допустимыми целями для ветвей как в статическом, так и в JIT-коде. Важной реализацией CFI является Microsoft Control-Flow Guard (MS-CFG), которая также защищает области JIT-кода в Microsoft Edge. Совсем недавно LLVM-CFI появился в Google Chrome как эффективное решение CFI. Еще одним общим средством защиты от атак с использованием кода является защита от произвольного кода (ACG). Если этот параметр включен, кодовые страницы становятся недоступными для записи, а записываемые страницы исполняемыми. Это создает проблему для JIT-компиляторов, так как им нужно сначала написать код, а потом изменить разрешения на исполняемый файл. Следовательно, Microsoft Edge использует JIT-сервер вне процесса, который отображает динамический код в область памяти, совместно используемую процессом браузера. В разделе 5 мы опишем дополнительные проблемы безопасности, которые могут возникнуть в JIT-компиляторах. Существуют также специальные меры по устранению недостатков, связанных с JIT-Spray и JIT. Мы кратко представим некоторые из них, необходимые для обеспечения общего понимания для остальной части статьи. В разделе 6 мы предоставляем более глубокую и полную картину мер по снижению атак против злоупотреблений JIT-компиляторами. Одним из смягчающих факторов является удобная оптимизация компилятора, называемая постоянным сворачиванием: если компилятор (JIT) способен вычислять операции во время компиляции перед генерацией собственного кода, то только оставшиеся операции / результаты окажутся в собственном коде. Следовательно, не появляются константы, в которых злоумышленник мог бы встроить байты вредоносного кода. Более конкретной защитой от встраивания кода в константы является постоянное ослепление. Для этого непосредственное значение передается случайным ключом перед тем, как JIT-компилятор отправит его в собственную инструкцию. Как только он используется, он «не записывается», но непосредственное значение не появляется в нативном коде. Следовательно, это напрямую препятствует внедрению кода через JIT-Spray. Мы объясним защиту, связанную с JIT, более подробно в разделе 6. В целом, мы вносим следующие вклады:
• Мы предоставляем обзор JIT-Spray по архитектуре x86 и ARM, включая академическую работу, а также неакадемические атаки, и подробно описываем методы нападения. Самая последняя техника JIT-Spray появилась в ASM.JS в Mozilla Firefox, что мы проиллюстрируем более подробно.
• Мы отличаем JIT-Spray от атак с повторным использованием кода на основе JIT и объясняем ландшафт атак с повторным использованием кода на основе JIT-компилятора.
• Мы суммируем обходные пути смягчения, которые были возможны с помощью компиляторов JIT, и демонстрируем средства защиты, появившиеся в течение многих лет для защиты от различных недостатков, связанных с JIT.
Остальные разделы разбиты на категории в таблице 1. Он обеспечивает атаки повторного использования кода на основе JIT-Spray и JIT на основе достигнутой цели эксплойта в затронутых целях. (Я не знаю, что делать с этой тавтологией)
2 Just-In-Time Compilation
Как отмечалось ранее, самые популярные клиентские программы для обычных пользователей, несомненно, есть веб-браузеры. Такие браузеры, как Mozilla Firefox, Internet Explorer, Microsoft Edge, Google Chrome и Apple Safari, содержат среду выполнения JavaScript. JS является динамическим языком сценариев и позволяет удобно манипулировать веб-контентом, что является ключевым аспектом современного Интернета. Хотя движки JavaScript работают как интерпретаторы, они встраивают компиляторы Just-In-Time (JIT). Преимущества JIT компиляции по сравнению с интерпретацией JavaScript (байт-код) - это огромный выигрыш в производительности: вместо выполнения кода JavaScript в стиле виртуальной машины, нативный код испускается компиляторами JIT для процессора. Если функция перегревается, т.е. выполняется часто, запускается компиляция Just-In-Time и преобразует функцию в машинный код для архитектуры, на которой работает браузер.
Существует несколько компиляторов Just-In-Time и уровней оптимизации. Вообще говоря, JavaScript браузеры имеют одинаковый дизайн . Существует интерпретатор и один или несколько компиляторов Just-In-Time с разными уровнями оптимизации. Например, WebKit, который является основой Apple Safari, использует четырехуровневую стратегию оптимизации JavaScript: три уровня оптимизации JIT (LLint, Baseline, DFG, FTL). В то время как первый уровень интерпретирует JavaScript байт-код, второй-четвертый этапы JIT запускают увеличение количества выполнений функций и операторов. Таким образом, оптимизация становится больше. Точно так же ChakraCore, JavaScript движок Microsoft Edge, имеет несколько уровней.
| Attack Flavor | Exploit Goal | Targets (see § 2) | Target Architecture | Bypassed Mitigations | Proposed Defenses (see § 6) |
| JIT-Spray (see § 3) | Code execution | ActionScript JIT Apple Safari (JSC) | x86 | W⊕X, ASLR | Con. folding/bliding Ran. Nop insert |
| JIT-Spray (see § 3) | of continuous | JVM {Jaeger|Trace}Monkey | x86 | SMEP, KERNEXEC | CFI (RAP) |
| JIT-Spray (see § 3) | payload | Mozilla Firefox (ASM.JS) | ARM | W⊕X, ASLR | Constant blinding |
| JIT-Spray (see § 3) | ---------------------------------------------- | ActionScript JIT JSC Spider Monkey | x86 | W⊕X, ASLR, Random nops | Constant blinding |
| JIT-Spray (see § 3) | Code execution | LLVM Jit {Jaeger|Trace}Monkey | x86 | W⊕X, ASLR | Constant blinding |
| JIT-Spray (see § 3) | of JIT gadgets | Internet Explorer Microsoft Edge | x86 x64 | W⊕X, ASLR, MS-CFG | Enforce MS-CFG for WARP JIT code |
| JIT-Spray (see § 3) | JSC V8 | ARM | W⊕X, ASLR | JIT allocation randomization | |
| JIT-based code reuse | Code execution | Spider Monkey, Int Explorer | x 86 x64 | Gadget-free static code | Constant blinding |
| JIT-based code reuse | of JIT gadgets | Google Chrome, Int Explorer, Spider Monkey | x64 x86 x86/x64 | Execute-only memory | Remove implicit constants from native code |
JIT-компиляция может быть разбита на параллельные фоновые потоки. В настоящее время Mozilla Firefox использует IonMonkey в качестве компилятора Just-In-Time, который разработан Cross-Architectur (Кросс-Архитектурным) способом для упрощения компиляции во время выполнения различных процессоров, таких как x86 и ARM. TurboFan является JIT-компилятором V8, движка JavaScript Google Chrome, и реализует собственный набор агрессивных оптимизаций, одновременно снижая сложность предыдущих JIT-компиляторов. Помимо браузеров, также виртуальная машина Java (JVM) использует интерпретатор и JIT-компилятор (Oracle HotSpot). Таким образом, скомпилированные двоичные файлы Java остаются переносимыми с использованием универсального байт-кода, который интерпретируется и JIT-компилируется в базовую архитектуру, на которой должна выполняться программа. Для полноты картины отметим, что в каркасе Microsoft dotNet имеется также JIT-компилятор (RyuJIT), и даже ядро Linux использует JIT-компиляцию для расширенных фильтров пакетов Berkeley (eBPF). Для популярного серверного языка PHP существует JIT-компилятор с именем HHVM, и язык Lua использует LuaJIT для динамической генерации собственного кода. Интересно, что, как мы объясним в следующем разделе, наиболее заметной атакой против JIT-компилятора была JIT Spray, нацеленная на виртуальную машину ActionScript (AVM) Adobe Flash
3 JIT-Spray
JIT-Spray - это элегантный метод эксплуатации, позволяющий обойти как DEP, так и ASLR. Хотя он не такой общий, как ROP, поскольку необходим JIT-компилятор, он значительно упрощает использование ошибок памяти.
3.1 JIT-Spray на x86
Длина инструкции архитектуры x86 является переменной. Следовательно, в пределах потока байтов кода каждое смещение байта является потенциальным началом инструкции, учитывая, что байты с каждым смещением декодируются с помощью x86. С точки зрения эксплуататора, это может быть использовано для внедрения кода: если злоумышленник имеет достаточный контроль над JIT-компилятором, он может заставить его выдавать инструкции, содержащие непосредственные значения, в то время как они сами содержат действительные байты инструкций. В 2010 году Блазакис обнаружил, что ActionScript Adobe Flash напрямую генерирует константы уровня скрипта в машинном коде. Рассмотрим длинное выражение XOR в ActionScript, как показано в коде 1.
Код:
1 var y = (
2 0 x3c909090 ˆ
3 0 x3c909090 ˆ
4 0 x3c909090 ˆ
5 ...
6 )
Код 1: Оператор ActionScript, содержащий длинную последовательность XOR.
JIT-компилятор ActionScript генерирует машинный код, содержащий инструкции, показанные в коде 2. Хотя эти инструкции представляют высокоуровневые вычисления, выполняются разные инструкции, если выполнение начинается с первого смещения (см. Код 3).
Поскольку константы ActionScript полностью контролируются злоумышленником, могут быть введены произвольные инструкции полезной нагрузки, размер которых не превышает трех байтов. Код 1: Оператор ActionScript, содержащий длинную последовательность XOR.
Четвертый байт (0x3C) служит для маскировки допустимой операции, представленной кодом операции 0x35 (⊕), и приводит к семантической Nop-Like инструкции (cmp al, 0x35). Это также предотвращает повторную синхронизацию команд.
[CODE]
0 x00: B8 9090903 C mov eax, 0 x3c909090
0 x05: 35 9090903 C xor eax, 0 x3c909090
0 x0a: 35 9090903 C xor eax, 0 x3c909090
...
Затем противник вынуждает JIT-компилятор генерировать достаточно копий собственного машинного кода, чтобы их адреса в памяти стали предсказуемыми (в 32-разрядных системах). Затем она может перенаправить поток управления на предсказанный адрес и выполнить введенный код. Вероятность 80%, что удар по NOP-Sled не будет. В одном из пяти случаев (20%) поток инструкций будет поражен, и эксплойт не потерпит неудачу.
Код:
0 x01 : 90 nop
0 x02 : 90 nop
0 x03 : 90 nop
0 x04 : 3 C35 cmp al , 0 x35
0 x06 : 90 nop
0 x07 : 90 nop
0 x08 : 90 nop
0 x09 : 3 C35 cmp al , 0 x35
0 x0b : 90 nop
0 x0c : 90 nop
0 x0d : 90 nop
JIT-компилятор.
Это было рождение JIT-Spray, и дальнейшие атаки не заставили себя долго ждать. В 2010 году Синцов показал, как автоматизировать и писать Shell-код для атак JIT-Spray. Проблемы с инструкциями, размер которых превышает три байта, но большинство из них можно преобразовать в семантически эквивалентные инструкции, которые не превышают трех байтов. Например, «MOV EAX, 0x41424344» приводит к пятибайтовой инструкции. Однако его можно разделить на три инструкции, выполняющие одну и ту же операцию: «MOV EAX, 0x41424yyzz» генерируется путем управления тремя байтами и предоставления компилятору JIT возможности манипулировать двумя байтами (yyzz). Они устанавливаются отдельно с помощью двух инструкций размером два байта: «MOV AH, 0x43» и «MOV AL, 0x44». Другой хитрый трюк заключался в использовании 0x6A вместо 0x3C в качестве маскирующего байта. Таким образом, вместо создания семантического NOP, который изменяет флаги ЦП (3C35 cmp al, 0x35), появилась команда push (6A35 push 0x35). Это позволило противникам использовать условные переходы (т.е. JNZ) впоследствии. JIT-Spray также был возможен в версии JavaScript Apple Safari для Windows в 2010 году. Поскольку JIT-компилятор выдавал много кода между контролируемыми константами JavaScript, автор использовал два байта из константы в качестве байтов полезной нагрузки, а два других байта в качестве безусловного перехода к следующей константе. Рассмотрим константу 0x14EB9090 в операции JavaScript. Компилятор Apple Safari JavaScriptCore baseline-JIT генерировал код, который можно использовать следующим образом (см. Код 4):
Код:
0 x01 : 90 nop
0 x02 : 90 nop
0 x03 : eb14 jmp 0 x19
...
0 x19 : 90 nop
0 x1a : 90 nop
0 x1b : eb14 jmp 0 x31
…
Одна из последних атак JIT-Spray, затрагивающая JIT-компиляторы, была опубликована в 2016 году: шейдеры WebGL можно было использовать в JavaScript Internet Explorer и Microsoft Edge [76]. JIT-компилятор WARP создал собственный код, не защищенный MS-CFG. Таким образом, авторам удалось внедрить код по предсказуемым адресам с помощью JIT-компилятора шейдера Windows Advanced Rasterization Platform (WARP). JIT-Spray также возможен в ядре Linux, если доступны расширенные фильтры пакетов Berkeley (eBPF) (которые по умолчанию отключены). Однако этот метод обходит защиту, которая запрещает ядру выполнять код, предоставляемый пользовательским пространством, таким как SMEP и KERNEXEC. Было показано, что сборка программы BPF и создание множества сокетов с прикрепленными (BPF) фильтрами приводит к созданию JIT-Spray внутри ядра. Таким образом, управляемая злоумышленником полезная нагрузка может быть выполнена, например, порождая корневую оболочку, когда поток управления перехвачен.
3.2 Тематическое исследование: ASM.JS
Мы опубликовали новую атаку JIT-Spray в 2017 году. Разница заключается в том, что она предназначена не для компилятора JIT, а для компилятора Ahead-Of-Time (AOT) в 32-битном Mozilla Firefox в Windows. Технически говоря, компиляторы AOT не генерируют и не оптимизируют код, если и после того, как определенный высокоуровневый код уже был выполнен несколько раз, но до того, как он был выполнен в первый раз. ASM.JS - это компилятор AOT, использующий подмножество JavaScript. Он соответствует определенному синтаксису и появился в 2013 году в Mozilla Firefox.
Код:
function asm_js_module () {
" use asm"
function asm_js_function () {
var val = 0 xc1c2c3c4 ;
return val |0;
}
return asm_js_function 8 }
Код:
modules = []
for (i =0; i <=0 x2000 ; i++) {
modules [i] = asm_js_module ()
}
Код:
****0023: b8c4c3c2c1 mov eax , 0 xc1c2c3c4
****0028: 6690 xchg ax ,ax
****002 a : 83 c404 add esp ,
4 ****002 d : c3 ret
Код:
val = + ffi_func (
2261634.5098039214 , // 0 x4141414141414141
156842099844.51764 , // 0 x4242424242424242
1.0843961455707782 e+16 , // 0 x4343434343434343
7.477080264543605 e +20 // 0 x4444444444444444
)
Код:
0 x00 : movsd xmm1 , mmword [****0530]
0 x08 : movsd xmm3 , mmword [****0538]
0 x10 : movsd xmm2 , mmword [****0540
] 0 x18 : movsd xmm0 , mmword [****0548]
...
****0530: 41414141 41414141
42424242 42424242
****0540: 43434343 43434343
44444444 44444444
...
Таблица 1: Операция ASM.JS и соответствующий испущенный собственный код встраивают контролируемые злоумышленником байты кода в непосредственные значения уязвимостей повреждения памяти. Поскольку злоумышленники могут воздерживаться от раскрытия памяти и повторного использования кода, необходим только контроль указателя инструкций. Мы также разработали инструмент для преобразования полезных нагрузок в его форму ASM.JS, которая затем во время выполнения передается компилятору AOT в собственный код. Хотя в качестве байтов полезной нагрузки используются только два или три байта из константы высокого уровня, все еще возможно выполнить произвольный код. Например, код stage0 разрешается и вызывает функцию API Windows VirtualAlloc (), затем копирует в нее большую полезную нагрузку (stage1) и выполняет ее.
3.3 JIT-Spray на ARM
Фундаментальное свойство, делающее возможным использование JIT-Spray на платформе x86, недоступно в архитектуре ARM: собственные инструкции имеют фиксированный размер либо 32-разрядный, либо выровнены по четырехбайтовым адресам (режим ARM), либо имеют размер 16-разрядный и выровнены по двухбайтовым границам (режим Thumb). Кроме того, режим Thumb-2 добавляет новые 32-битные инструкции в Thumb и позволяет смешивать 16-битные и 32-битные инструкции. Следовательно, вставлять произвольный код намного сложнее, чем в x86. Однако первый тип JIT-Spray на ARM, о котором мы знаем, использовал константы с плавающей точкой в ActionScript. Протестированный движок ActionScript JIT сгенерировал код с относительными к ПК ссылками на константы. Кроме того, значения с плавающей точкой находились на той же странице, что и код, и были непрерывными в памяти. Это позволило сделать непрерывный Shell-код без других разрушающих кодов операций.
Код:
function readGadget (x) {
return x ˆ 0 x11111610 ;
}
Lian et al.. более подробно исследовал идею JIT-Spray на ARMv7-A [33]. JIT-компилятор JavaScriptCore (из WebKit) генерировал при определенных обстоятельствах гаджеты, подверженные влиянию злоумышленников по предсказуемым адресам. Фрагмент JavaScript, скомпилированный в код, содержащий гаджет, показан в коде 9. DFG JIT генерирует полезные инструкции (см.Код 10). Обратите внимание, что большинство инструкций гаджета были намеченными инструкциями, только первая инструкция была непреднамеренной: вместо выполнения предполагаемой 32-битной инструкции, выровненной по четырехбайтовой границе, выполнение началось с перехода на вторую половину. Затем выполнение ресинхронизируется с намеченным потоком команд. В коде 11 показаны инструкции для гаджета для функции readGadget. Выполнение начинается в середине инструкции по смещению 0x38 и повторной синхронизации по смещению 0x3a. Тем не менее гаджет предоставляет злоумышленнику возможность считывать память и возвращать содержимое в виде 32-разрядных целых чисел в JavaScript.
Код:
0 x00 : mov r2 , lr
0 x02 : str .w r2 , [r5 , # -16] ; save return address
...
0 x32 : ldr .w r0 , [r5 , # -64] ; load argument
0 x36 : movw r12 , #5648 ;0 x1610
0 x3a : movt r12 , #4369 ;0 x1111
0 x3e : eor .w r0 , r0 , r12
0 x42 : mov .w r1 , #4294967295 ;0 xffffffff
0 x46 : ldr .w r2 , [r5 , # -16] ; load return address
0 x4a : ldr .w r5 , [r5 , # -40] ; restore frame ptr
0 x4e : mov lr , r2
0 x50 : bx lr ; return
...
Код:
0 x38 : ldr r0 , [r2 , #64] ; read memory from r2 +#64
0 x3a : movt r12 , #4369 ;0 x1111
0 x3e : eor .w r0 , r0 , r12
0 x42 : mov .w r1 , #4294967295 ;0 xffffffff
0 x46 : ldr .w r2 , [r5 , # -16] ; load return address
0 x4a : ldr .w r5 , [r5 , # -40] ; restore frame ptr
0 x4e : mov lr , r2
0 x50 : bx lr ; return
...
4.Повторное использование кода на основе JIT
Первая (академическая) работа, в которой использовались скомпилированные во время выполнения гаджеты из JIT-компилятора, возникла из-за необходимости обходить защиту от повторного использования кода в 2015 году. Если статический код программы не содержит гаджетов, то повторное использование кода обычно не вариант. Однако если гаджеты создаются компилятором JIT, повторное использование кода снова становится возможным. Athanasakis et al. предназначался для IonMonkey в 32-битном Linux и Chakra в 64-битном Internet Explorer 9 в Windows. В общем, они спровоцировали JIT-компилятор на генерацию гаджетов, содержащих всего несколько инструкций, и использовали двухбайтовые константы JavaScript. Это обошло постоянное ослепление в Internet Explorer и других защитах, связанных с JIT, которые были включены в то время. Однако обратите внимание, что авторам требовалось раскрытие памяти, чтобы найти гаджеты в памяти. Следовательно, мы не считаем его JIT-Spray, поскольку JIT-Spray не требует утечки информации, а только контролирует указатель инструкций, чтобы перенаправить поток управления на заранее определенный адрес, содержащий скомпилированный JIT код злоумышленника. В 2015 году были обнаружены другие недостатки в движке Chakra JavaScript Internet Explorer, связанные с повторным использованием JIT-кода. В то время как Chakra применяет постоянное ослепление, выражения деления в JavaScript с 32-разрядными целыми числами привели к появлению не слепых четырехбайтовых констант, содержащих внедренный код. Кроме того, авторы показали, что две 16-разрядные константы передавались непосредственно в одну инструкцию x86, когда первая использовалась в качестве индекса массива, а вторая - в качестве элемента массива. Чтобы иметь возможность перейти к этому внедренному коду, они использовали инструкцию «JMP ECX» самого движка JavaScript, который не был защищен MS-CFG. Тем не менее, как введенный код, так и инструкция перехода были доступны только для раскрытия памяти. Следовательно, мы не считаем его JIT-Spray, а используем его повторно. Мы также хотим отличить термин JIT-ROP от тех методов нападения, которые представлены в этой статье. Он не связан с JIT-компиляторами. Он просто описывает метод многократного определения местоположения, чтения и разборки статического кода с помощью e. г. раскрытие памяти в JavaScript. Затем полезная нагрузка повторного использования кода может быть построена точно в срок. Это необходимо, если к целевым двоичным файлам применяется мелкозернистая рандомизация кода, поскольку она скрывает не только базовые адреса модулей в адресном пространстве, но также записи функций, базовые блоки и адреса инструкций.
Таблица 2: Код JavaScript и соответствующий выданный код JIT отсутствуют для скрытия четырехбайтовых констант в Google Chrome (найден Dachshund). Тем не менее, защита от JIT-ROP была ослаблена с помощью JIT-скомпилированных гаджетов: одним из таких средств защиты является память «только для исполнения». Он запрещает чтение кода, но адреса JIT-гаджетов все еще могут быть найдены по средствам последующей утечки читаемого объекта данных, пока не будут обнаружены указатели на гаджеты. Maisuradze и др. Смогли заставить JIT-компиляторы Internet Explorer, Google Chrome и Mozilla Firefox скрывать код в ветвях. Их тщательно сконструированный код JavaScript приводил к инструкциям потока управления, таким как условные переходы и прямые вызовы. Их целевые адреса и смещения имели скрытые байты кода и, следовательно, могли использоваться в качестве гаджетов для атак с повторным использованием кода.
В качестве защиты, постоянное сворачивание или ослепление не является опцией, поскольку неявные константы находятся в пределах (относительных) вызовов / переходов. Следовательно, они предложили исключить все неявные константы, заменив их инструкции косвенного управления потоком. Как мы упоминали во введении и описывали в смягчениях(см. Раздел 6), длинные полезные нагрузки и гаджеты JIT-Spray предотвращаются постоянным ослеплением. Однако Maisuradze et al. также пытались найти ”не ослепленные” четырехбайтовые константы, несмотря на четырехбайтовое постоянное ослепление в современных веб-браузерах. В целом удалось найти несколько операций JavaScript с константами, которые JIT-компилятор кодирует в память в Google Chrome и Microsoft Edge. На таблице 2 представлены их результаты в Google Chrome.
5.Злоупотребление недостатками JIT-компилятора
Методы, которые обсуждались в предыдущих двух разделах, основывались на внутренней работе компиляторов JIT и шли в направлении обходных путей для предотвращения эксплойтов. Далее мы представляем атаки, которые могут рассматриваться как основанные на недостатках и ошибках компиляторов JIT, но, пожалуйста, обратите внимание, что различие может иногда быть нечетким.
5.1 Больше обходных путей смягчения
Очень популярным средством смягчения последствий является DEP. Несмотря на то, что JIT-Spray обходит DEP, была также возможность перезаписать испускаемый код, если права доступа к кодовым страницам доступны для записи. В настоящее время ни статический код, ни JIT-скомпилированный код не должны быть одновременно исполняемыми и записываемыми. В настоящее время это не относится к Google Chrome. Тем не менее, даже если W ⊕X включен для регионов JIT, они должны быть записаны в первую очередь и быть исполняемыми впоследствии. В 2015 году Song et al. использовал это маленькое временное окно для перезаписи кэшей кода в многопоточном коде с использованием веб-работников [72]. Следовательно, им удалось внедрить код несмотря на W ⊕X в динамических областях кода. В 2016 году Chakra Internet Explorer подверглась аналогичной атаке. Авторы сделали три шага: во-первых, они запустили JIT-компилятор для кодирования большой области кода. Это создало временное окно, в котором фоновый поток работал над временным буфером для записи кода. На втором этапе этот буфер находился с раскрытиями памяти и перезаписывался вредоносным кодом на третьем этапе. Благодаря этой атаке они смогли обойти DEP и MS-CFG. В 2017 году Frassetto et al. продемонстрировал атаку только на данные на промежуточное представление (IR) Chakres в Microsoft Edge. Вместо создания / изменения кода или указателей кода они создали вредоносный объект C ++, представляющий операторы IR, с обязательным условием примитива чтения / записи из JavaScript. Поскольку JIT-компилятор использует эти объекты для генерации собственного кода, авторы смогли создавать и выполнять свой код по своему выбору. Fratrik подробно исследовал JIT-компилятор Microsoft Edge в 2018 году. Среди других недостатков он смог внедрить свой код выбора, чтобы обойти ACG, злоупотребляя архитектурой сервера JIT.
Microsoft Edge использует отдельный сервер JIT для генерации динамического кода для процесса браузера (контента). Этот процесс разделяет область памяти с процессом браузера и отображает динамический код в эту общую область. JIT-сервер получает доступ к региону с правами на запись, в то время как процесс браузера имеет исполняемый вид региона. Таким образом, процесс браузера подчиняется ACG: он не может модифицировать или создавать кодовые страницы сам по себе, он может только выполнять их. Автор обнаружил, что JIT-сервер можно обмануть, чтобы сделать управляемую злоумышленником память в исполняемом процессе браузера: если процесс браузера скомпрометирован, исполняемое представление общей области может быть не отображено. Затем злоумышленник распределяет доступную для записи память по тому же адресу и записывает в нее полезную нагрузку. Процесс JIT с радостью использует следующий адрес и пометит его как исполняемый без изменения полезной нагрузки. Таким образом, ACG был обойден без непосредственного вмешательства в содержание кодовых страниц. Обновление безопасности исправило проблему.
5.2 Уязвимости JIT-компилятора
JIT-компиляторы представляют собой сложные программные системы, как и другие компиляторы. Следовательно, естественно, что они содержат критические ошибки безопасности. Несмотря на то, что постоянно обнаруживаются уязвимости, мы хотим кратко перечислить наиболее значимые на момент написания статьи образцовым образом. DFG JIT от Apple Safari стал жертвой ошибок оптимизации во время конкурсов Pwn2Own в 2017 и 2018 годах. В оба года это было отправной точкой для участников, чтобы повысить свой уровень привилегий с помощью дополнительных методов эксплуатации. Одна интересная ошибка JIT-оптимизации в Google Chrome V8 дала злоумышленнику очень мощные примитивы, такие как утечка произвольной памяти и создание произвольных объектов JavaScript. Выполнение кода было достигнуто путем записи кода на страницу JIT. Для более подробной информации читатель ссылается на оригинальное исследование. JIT-компилятор в Chakra также подвержен уязвимостям. Например, CVE-2018-0953 позволил создать путаницу типов, установив элемент массива с магией значение. Это стало возможным, потому что JIT-компилятор пропустил проверку чека динамического кода.
6.Смягчения
Прежде чем углубляться в более общие меры по смягчению воздействия JIT-Spray и других средств защиты, связанных с JIT, мы рассмотрим исправления для ASM.JS JIT-Spray из Раздела 3.2. Mozilla назначила CVE-2017-5375 и CVE-2017-5400 для этого метода обходного обхода. Было назначено два CVE, потому что первого исправления было недостаточно: рандомизация для выделения кода была увеличена, но при определенных обстоятельствах старый код, отвечающий за передачу областей ASM.JS по предсказуемым адресам, мог все еще срабатывать. Последующее исправление изменило схему распределения: при запуске, когда адресное пространство почти пусто, случайный диапазон адресов зарезервирован для выделения кода ASM.JS. Местоположение этого диапазона трудно предсказать атакующему. Позднее, во время выполнения, как только запрашиваются модули ASM.JS, регионы из этого набора страниц объединяются и высвобождаются при необходимости. Поскольку исходный код является общим для ASM.JS и WebAssembly, эта схема защиты (также известная как рандомизация выделения) также используется для выделения кода WebAssembly в Mozilla Firefox. Начальный диапазон адресов дополнительно ограничен определенным количеством страниц, чтобы предотвратить исчерпание адресного пространства во время распыления (также известное как ограничение выделения). Мы уже объясняли постоянное свертывание во введении. Adobe включила постоянное свертывание в JIT-компилятор Flash ActionScript, чтобы противостоять исходным атакам JIT-Spray. Это предотвратило непосредственные контролируемые злоумышленником константы в коде JIT. Тем не менее, его вскоре обошли с помощью оператора ActionScript IN. Достаточно было использовать одну операцию IN в операции, иначе содержащей OR, чтобы обмануть JIT-компилятор и снова получить немедленные значения, контролируемые атакующим. Код ActionScript «0x3c909090 IN 0x3c909090 | 0x3c909090 | ... »снова дали нативные салазки, когда выполнялись с не смещенным смещением. С постоянным ослеплением была разработана и внедрена защита, которая предотвращает контролируемые злоумышленником немедленные значения в коде JIT. Таким образом, компилятор во время компиляции копирует непосредственное значение со случайным ключом, неизвестным злоумышленнику. Прежде чем значение используется в операции в собственном коде, оно записывается с этим ключом во время выполнения. Таким образом, все операции в нативном коде остаются действительными,
| Feautre | Impact on | Mozila Firefox | Google Chrome | Microsoft Edge | Internet Explorer |
| 64-bit adress space | Predictable location of injected code | ✓ | ✓ | ✓ | ✗/✓∗ |
| ASLR | Predictable location of injected code | ✓ | ✓ | ✓ | ✓ |
| CFI | Execution of injected code | ✗ | ✓◦ | ✓† | ✓†‡ |
| Random nop insertion | Predictable location of injected code | ✗ | ✓ | ✓ | ✓ |
| Constant folding | Code injection | ✓ | ✓ | ✓ | ✓ |
| Constant blinding of Code injection | Code injection | ✗ | ✓ | ✓ | ✓ |
| W ⊕X JIT regions | JIT-region overwrite | ✓ | ✗ | ✓ | ✓ |
| JIT-base offset Predictable location and/or randomization | Predictable location of injected code | ✓ | ✓ | ✓ | ✓ |
| JIT allocation Predictable location restriction | Predictable location of injected code | ✓ | ✓ | ✓ | ✓ |
| Guard pages | JIT-region overwrite | ✓ # | ✓# | ✓# | ✓# |
◦- применяется LLVM-CFI
†- применяется MS-CFG
‡- частичное внедрение MS-CFG
#- не обязательно для исполняемых областей JIT
но не легко предсказуемы противниками, и, следовательно, скрыть байты кода невозможно. Поскольку на производительность влияют ослепляющие константы, обычно только четырехбайтовые или большие константы являются слепыми. Воздействие слишком радикально для двухбайтовых или меньших констант. В результате это приводит к возможности скрыть код и позволить компилятору JIT создавать
гаджеты снова. Еще одна возможность сделать скрытый код сразу
Менее предсказуемы случайные числа. Различные инструкции типа NOP различных размеров, такие как «LEA ESP, [ESP]», «XCHG EDX, EDX» или «MOV ECX, ECX», могут смешивать запланированные операции, скомпилированные JIT. Если они неизвестны атакующему, Jumping to a NOP с помощью указателя инструкции может завершиться неудаче из за случайных выпадов. Adobe включила эту технику в 2011 году в JIT-компилятор ActionScript, но частота выброса случайных NOP была слишком мала, чтобы сломать маленькие гаджеты JIT. Однако это предотвратило полную самостоятельную загрузку JIT. Точно так же сдвиг предполагаемого кода JIT со случайным базовым смещением в областях кода добавляет непредсказуемость к местоположению введенных байтов кода. Не занимаясь непосредственной защитой JIT-Spray или JIT-атак с повторным использованием кода.
JIT-кодовые страницы без флага записи не могут быть перезаписаны, а защитные страницы не позволяют произойти переполнению буфера для достижения JIT-кодовых страниц (в случае, если они доступны для записи и смежны с объектами кучи). Например, регионы JIT-кода в Mozilla Firefox больше не доступны для записи с конца 2015 года. В 2011 году JitDefender уже использовал механизмы для преобразования W ⊕ X в JIT-код ActionScript и JavaScript. При таком подходе регионы JIT-кода не выполнялись и переключались на только исполняемые, если был сделан законный вызов регионов JIT. Это предотвратило незаконное выполнение полезных нагрузок, обработанных JIT, поскольку переходы к нему попадали в код, который не был исполняемым. Librando, JIT-инфраструктура, может применять некоторые из этих защит к компиляторам COTS JIT в «черном ящике». Таким образом, функции выделения операционной системы перехватываются, чтобы анализировать, диверсифицировать и перезаписывать скомпилированный код, который должен быть точно в срок. К JIT-коду применяются рандомизация распределения, постоянное ослепление, случайная вставка NOP и различные оптимизации. Авторам удалось укрепить Java и V8 с замедлением от 1,15 до 3,5 раз. RIM, JITSafe и INSeRT используют метод Obfuscation, Diversification и рандомизацию аналогичным образом, чтобы разбить скрытый код на непосредственные значения и препятствовать JIT-Spray. В то время как целостность потока управления стала очень популярной для статического кода, для динамического кода мало CFI.
RockJIT - это одна система, нацеленная на динамическое обеспечение целостности потока управления для собственного кода. Он применяет политики к коду JIT и JIT-компилятору сам. Среди прочих методов, после передачи анализа исходного кода, используются проверки, чтобы разрешить только цели действительного ввода кода быть целью косвенных ветвей. Это мешает JIT-Spray, потому что байты нерасширенного кода в середине предполагаемой инструкции являются недопустимыми целями кода. Авторы применили RockJIT к V8 с падением производительности на 14,6%. Native Client (NaCl), формально собственный движок кода в Google Chrome, похож на RockJIT. Он применяет программную изоляцию отказов (SFI) в контексте браузера. Код выровнен по 32-байтовым фрагментам, а косвенным ветвям разрешено только переходить к началу фрагментов. Это выполняется путем проверки масок адресов перед выдачей косвенной ветви. Последующая работа пыталась улучшить CFI для кода JIT. JITScope работает с инфраструктурой LLVM и может не только укрепить код JIT и JIT-компилятор, но и приложение в целом с помощью CFI. Авторы применили JITScope к TraceMonkey с влиянием на производительность менее 10%. В настоящее время CFI включен для кода JIT в Microsoft Edge с MS-CFG, а LLVM-CFI включен в Google Chrome. Grsecurity использует RAP, передовое и обратное решение CFI для Linux, предотвращающее выполнение непреднамеренных инструкций. Подобно разделению JIT вне процесса в Microsoft Edge, в 2014 году Lobotomy предложила двухпроцессную модель. Процесс браузера имеет исполняемое представление области совместно используемой памяти, в то время как процесс JIT имеет доступное для записи представление этой области. Таким образом, только JIT процесс может создавать код JIT и манипулировать им, а уязвимости в процессе браузера не могут изменять или перезаписывать регионы JIT. Было предложено несколько других способов защиты от недостатков, связанных с JIT, таких как JIT-Sec. JIT-Sec применяет монитор к JIT-коду для запрета системных вызовов. Это помогает нарушать самодостаточные полезные нагрузки JIT-Spray, которые выдают системные вызовы, когда авторы считают, что предполагаемый код JIT не выполняет системные вызовы. Еще одна сложная защита, называемая JIT-Guard, использует архитектуру Intel SGX и различные другие методы для защиты от внедрения кода, повторного использования кода и атак только на данные.
Критические части JIT-компилятора изолированы в анклавах SGX, для кода JIT добавлен уровень рандомизации, и раскрытия памяти решаются с помощью уровня косвенности. Основным изменением, которое можно рассматривать как неявное смягчение, является переход от 32-разрядных к 64-разрядным архитектурам. В настоящее время все основные веб-браузеры, кроме Internet Explorer, работают как собственные 64-битные x86-приложения. Аналогично, платформа ARM имеет 64-битную архитектуру под названием AArch64, которая поддерживает 64-битное адресное пространство. Поскольку 64-разрядное адресное пространство больше, чем 32-разрядное, JIT-Spray кажется невозможным, поскольку распыление многих областей кода для получения предсказуемого адреса невозможно. Тем не менее, повторное использование JIT-кода все еще может быть полезным, если раскрытие памяти может осуществляться в 64-разрядных целях. Таблица 2 суммирует влияние (безопасности) функций на повторное использование JIT-Spray и JIT-кода. Если выброс непреднамеренных байтов кода (внедрение кода), распыление в предсказуемые места и выполнение непреднамеренного кода полностью предотвращены, то JIT-Spray недопустим. Имея комбинацию постоянного ослепления, 64-битного адресного пространства, высокой степени ASLR и CFI, можно достичь этой цели, если все будет правильно реализовано.
7.Заключение
JIT-Spray - это метод, который удобно упростил использование уязвимостей, приводящих к повреждению памяти, поскольку он обходит DEP и ASLR. В то время, когда начали уменьшаться эксплойты, перехват потока управления был все еще достаточен для выполнения произвольного (удаленного) выполнения кода. С 2010 года, когда появился JIT-Spray, многие JIT-компиляторы были уязвимы для него. В этой статье мы подробно рассмотрели эту технику и показали, какие методы используются для сокрытия байтов кода в константах языков высокого уровня. Кроме того, мы опросили затронутые цели на архитектуре x86 и ARM и установили связь с атаками с повторным использованием кода, которые используют JIT-компиляторы. В то время как мы предоставили обзор дополнительных недостатков, которые могут возникнуть в JIT-движках в качестве примера, мы более подробно рассмотрели последний недостаток JIT-Spray, который затронул компилятор AOT ASM.JS в 2017 году в Firefox. Атаки и защита на основе JIT-компилятора все еще остаются живой областью исследований, как показывает множество (академических) мер по смягчению. Переход браузеров на 64-битные и рандомизация размещения адресного пространства с высокой энтропией, по-видимому, делает традиционный JIT-Spray неосуществимым. Однако в свете раскрытия памяти, несовершенной реализации CFI и неполного (то есть двухбайтового) постоянного ослепления повторное использование JIT-кода по-прежнему является ценным активом для злоумышленников. Кроме того, недостатки в JIT-компиляторах (например, уязвимости) остаются привлекательными целями. Несмотря на то, что ситуация с эксплойтами уменьшается, а использование уязвимостей повреждения памяти становится все труднее, мы не хотим исключать возможность повторного появления JIT-Spray в будущих целях. Конечно, защита становится все более изощренной, как и злоумышленники.
Благодарность
Мы хотели бы поблагодарить Мэтта Миллера за то, что он подготовил этот документ и за его плодотворные предложения и комфортное сотрудничество. Кроме того, мы благодарим наших анонимных рецензентов за конструктивную обратную связь, которая помогла нам улучшить статью. Эта работа была поддержана Европейской комиссией в рамках проекта H2020 DS-07-2017 «РЕАКТ» в рамках Грантового соглашения № 786669.
И того более 6 600 слов.
Переведено cпециально для xss.pro
neopaket
Благодарю модератора weaver за предложение данного текста для перевода и admin за поддержку треда tabac про перевод статей для ныне полюбившегося мне форума XSS.
Оригинальная статья: https://www.usenix.org/system/files/conference/woot18/woot18-paper-gawlik.pdf
Последнее редактирование: