В рамках августовского вторника исправлений Microsoft пропатчила одну уязвимость нулевого дня, нацеленную на Internet Explorer 11, а именно CVE-2020-1380. Это ошибка UAF в движке JavaScript Internet Explorer, jscript9.dll. За последние несколько лет мы заметили, что атаки нулевого дня на Internet Explorer обычно эксплуатируют vbscript.dll и jscript.dll для запуска шелл-кода. На этот раз цель сменилась на jscript9.dll и использовала движок Just-In-Time (JIT) современного движка JavaScript, чтобы вызвать ошибку, поэтому я решил погрузиться в JIT-движок jscrtip9.dll, чтобы попытаться выяснить основную причину ошибки CVE-2020-1380.
Обзор конвейера выполнения Jscirpt9.dll
Jscript9.dll - это движок JavaScript по умолчанию, который используется для замены jscript.dll, начиная с Internet Explorer 9. На рисунке 1 показан конвейер выполнения jscript9.dll.
Как правило, выполнение исходного кода JavaScript в jscript9.dll состоит из пяти основных шагов:
Расположение машинного кода
Прежде чем говорить об уязвимости, нам сначала нужно найти машинный код, сгенерированный JIT-движком. Цикл for обычно вызывает механизм JIT в интерпретаторе. На рисунке 2 показан простой код JavaScript, который может запускать JIT:
Когда количество циклов превышает некоторые пороговые значения (0x32 во фрагменте кода на рисунке 3), тело цикла и функция внутреннего вызова opt будут отправлены в очередь бэкэнда движка JIT для генерации оптимизированного машинного кода:
Поток внутреннего механизма JIT получает задание из очереди и, наконец, вызывает jscript9! Func :: Codegen для генерации оптимизированного машинного кода:
Бэкэнд-движок JIT выполняет несколько шагов для создания оптимизированного машинного кода, таких как: построение промежуточного представления (IR), встраивание, построение графа потока управления (CFG), анализ потока данных, уменьшение, выделение регистров, компоновка, кодирование и так далее:
Когда сгенерирован оптимизированный машинный код, он будет использоваться для замены тела цикла ByteCode. Когда цикл for вызывается следующим, машинный код будет вызываться вместо этого в функции Js::InterpreterStackFrame:: CallLoopBody:
Наконец, машинный код тела цикла вызовет машинный код функции внутреннего вызова opt, который мы можем видеть здесь:
Анализ первопричин CVE-2020-1380
PoC CVE-2020-1380 показан на рисунке 8:
Следующие шаги могут вызвать ошибку:
JavaScript - это динамический язык, в котором тип или свойство могут быть неявно преобразованы. Сгенерированный машинный код, который выполняет неявные вызовы JavaScript напрямую без какой-либо проверки, не заслуживает доверия. Jscript9.dll использует функцию ExecuteImplicitCall, чтобы сделать неявный вызов JavaScript безопасным.
Во-первых, мы меняем три строки "arguments operation" на "arguments [0] = value2", что имеет тот же эффект. На рисунке 9 показан сгенерированный фрагмент кода JIT.
Перед вызовом функции преобразования типов jscript9!Js::JavascriptConversion:
oFloat_Helper некоторые значения устанавливаются на флаги, хранящиеся в адресах 0x140F3F68 и 0x140F3E86 отдельно. Флаг, хранящийся по адресу 0x140F3F68 - это ImplicitCallFlags, а другой флаг, хранящийся в 0x140F3E86 - это DisableImplicitFlags. DisableImplicitFlags имеет значение 3 (DisableImplicitCallFlag | DisableImplicitExceptionFlag), что означает, что преобразование типа в float не допускается при неявном вызове из кода JavaScript, который должен быть вызван, например valueOf.
Функция Js::JavascriptConversion:
oFloat_Helper проверяет тип ввода, чтобы решить, какой путь преобразования типа выбрать. Поскольку value2 является объектом, вызывается Js:
ynamicObject:
oPrimitive, а затем, наконец, вызывается ExecuteImplicitCall:
ExecuteImplicitCall проверяет DisableImplicitFlags; если значение не равно 0, неявный вызов JavaScript не будет вызываться и вернет undefined напрямую. Наконец, машинный код перейдет к интерпретатору и безопасно вызовет неявный вызов в интерпретаторе:
Однако, когда три строки "операции аргументов" используются для замены "arguments[0] = value2", мы видим, что сгенерированный машинный код вызывает Js::JavascriptConversion:
oFloat_Helper напрямую, без установки DisableImplicitFlags:
Наконец, неявный вызов valueOf будет вызываться непосредственно из машинного кода. Злоумышленник может использовать эту возможность обратного вызова без проверки, чтобы вызвать уязвимость UAF, например стерилизовать память ArrayBuffer TypedArray рабочим потоком.
Меня заинтересовало, почему три строки "операции с аргументами" могут устранить машинный код установки DisableImplicitFlags. Я думаю, что основной причиной является ошибка вывода типа arguments[0] в бэкэнде на этапе JIT GlobOpt. Механизм JIT не знает побочного эффекта Array.prototype.push, который можно использовать для изменения типа аргументов [0]. Тип arguments[0] должен быть уничтожен после операции Array.prototype.push, чтобы избежать проблемы с ошибкой вывода этого типа.
Эту ошибку могут вызвать другие методы, например, использование Array.prototype.splice или использование Float64Array для вызова пути преобразования Js::JavascriptConversion:
oNumber_Helper. Эта ошибка также была исправлена в августовском патче.
Заключение
В последние несколько лет атаки нулевого дня, нацеленные на Internet Explorer, обычно используют уязвимости vbscrpt.dll и jscript.dll. CVE-2020-1380 является особенным, поскольку он нацелен на JIT-движок jscript9.dll. Уязвимости JIT - распространенная проблема в современных движках JavaScript, таких как V8, JavascriptCore, Spidermonkey и Chakra.
Возможно, злоумышленники теперь выбирают целью JIT-движок Internet Explorer.
Источник https://www.trendmicro.com/en_us/re...0-analysis-of-recently-fixed-ie-zero-day.html
Автор перевода: yashechka
Переведено специально для https://xss.pro
Обзор конвейера выполнения Jscirpt9.dll
Jscript9.dll - это движок JavaScript по умолчанию, который используется для замены jscript.dll, начиная с Internet Explorer 9. На рисунке 1 показан конвейер выполнения jscript9.dll.
Как правило, выполнение исходного кода JavaScript в jscript9.dll состоит из пяти основных шагов:
- Парсер анализирует исходный код JavaScript, чтобы получить Абстрактное Синтаксическое Дерево (AST).
- ByteCodeGenerator проходит через AST и генерирует ByteCode.
- Интерпретатор - это виртуальная машина, выполняющая ByteCode. Данные профиля, такие как информация о типе, собираются при выполнении ByteCode.
- Когда некоторые фрагменты кода вызываются несколько раз, например, в цикле for, интерпретатор отправляет данные ByteCode и профиля на движок бэкэнда Just-In-Time (JIT) для генерации машинного кода, а затем заменяет точку входа ByteCode на сгенерированный машинный код.
- При выполнении машинного кода, если какой-либо статус нарушает предположение о профиле, машинный код отправляет интерпретатору запрос о помощи для повторного выполнения ByteCode, чтобы избежать каких-либо проблем с безопасностью.
Расположение машинного кода
Прежде чем говорить об уязвимости, нам сначала нужно найти машинный код, сгенерированный JIT-движком. Цикл for обычно вызывает механизм JIT в интерпретаторе. На рисунке 2 показан простой код JavaScript, который может запускать JIT:
Когда количество циклов превышает некоторые пороговые значения (0x32 во фрагменте кода на рисунке 3), тело цикла и функция внутреннего вызова opt будут отправлены в очередь бэкэнда движка JIT для генерации оптимизированного машинного кода:
Поток внутреннего механизма JIT получает задание из очереди и, наконец, вызывает jscript9! Func :: Codegen для генерации оптимизированного машинного кода:
Бэкэнд-движок JIT выполняет несколько шагов для создания оптимизированного машинного кода, таких как: построение промежуточного представления (IR), встраивание, построение графа потока управления (CFG), анализ потока данных, уменьшение, выделение регистров, компоновка, кодирование и так далее:
Когда сгенерирован оптимизированный машинный код, он будет использоваться для замены тела цикла ByteCode. Когда цикл for вызывается следующим, машинный код будет вызываться вместо этого в функции Js::InterpreterStackFrame:: CallLoopBody:
Наконец, машинный код тела цикла вызовет машинный код функции внутреннего вызова opt, который мы можем видеть здесь:
Анализ первопричин CVE-2020-1380
PoC CVE-2020-1380 показан на рисунке 8:
Следующие шаги могут вызвать ошибку:
- Цикл for отправляет функцию opt механизму JIT.
- В функции opt три строки "операции аргументов" могут установить value2 в value1, тогда Float32Array первый элемент arr[0] Float32Array устанавливается value1.
- После отправки функции opt механизму JIT она изменяет аргумент value2 с целого числа 0x1337 на объект, который имеет функцию обратного вызова valueOf.
- В последнем вызове функции opt, поскольку аргумент "flag" установлен в 0, базовый блок "if (flag == 1)" не выполняется, что устанавливает value2 в arr[0]. Поскольку объект заменяет value2, происходит неявное преобразование типа, тогда функция обратного вызова valueOf может быть вызвана в машинном коде.
JavaScript - это динамический язык, в котором тип или свойство могут быть неявно преобразованы. Сгенерированный машинный код, который выполняет неявные вызовы JavaScript напрямую без какой-либо проверки, не заслуживает доверия. Jscript9.dll использует функцию ExecuteImplicitCall, чтобы сделать неявный вызов JavaScript безопасным.
Во-первых, мы меняем три строки "arguments operation" на "arguments [0] = value2", что имеет тот же эффект. На рисунке 9 показан сгенерированный фрагмент кода JIT.
Перед вызовом функции преобразования типов jscript9!Js::JavascriptConversion:
Функция Js::JavascriptConversion:
ExecuteImplicitCall проверяет DisableImplicitFlags; если значение не равно 0, неявный вызов JavaScript не будет вызываться и вернет undefined напрямую. Наконец, машинный код перейдет к интерпретатору и безопасно вызовет неявный вызов в интерпретаторе:
Однако, когда три строки "операции аргументов" используются для замены "arguments[0] = value2", мы видим, что сгенерированный машинный код вызывает Js::JavascriptConversion:
Наконец, неявный вызов valueOf будет вызываться непосредственно из машинного кода. Злоумышленник может использовать эту возможность обратного вызова без проверки, чтобы вызвать уязвимость UAF, например стерилизовать память ArrayBuffer TypedArray рабочим потоком.
Меня заинтересовало, почему три строки "операции с аргументами" могут устранить машинный код установки DisableImplicitFlags. Я думаю, что основной причиной является ошибка вывода типа arguments[0] в бэкэнде на этапе JIT GlobOpt. Механизм JIT не знает побочного эффекта Array.prototype.push, который можно использовать для изменения типа аргументов [0]. Тип arguments[0] должен быть уничтожен после операции Array.prototype.push, чтобы избежать проблемы с ошибкой вывода этого типа.
Эту ошибку могут вызвать другие методы, например, использование Array.prototype.splice или использование Float64Array для вызова пути преобразования Js::JavascriptConversion:
Заключение
В последние несколько лет атаки нулевого дня, нацеленные на Internet Explorer, обычно используют уязвимости vbscrpt.dll и jscript.dll. CVE-2020-1380 является особенным, поскольку он нацелен на JIT-движок jscript9.dll. Уязвимости JIT - распространенная проблема в современных движках JavaScript, таких как V8, JavascriptCore, Spidermonkey и Chakra.
Возможно, злоумышленники теперь выбирают целью JIT-движок Internet Explorer.
Источник https://www.trendmicro.com/en_us/re...0-analysis-of-recently-fixed-ie-zero-day.html
Автор перевода: yashechka
Переведено специально для https://xss.pro