Microsoft за февраль 2020 года выпустила исправления безопасности для 99 ошеломляющих CVE, и это довольно большое количество, которое нужно исправить за один месяц. Хотя активно эксплуатируемой уязвимости Scripting Engine было уделено много внимания, особо выделялась другая уязвимость, оцененная как критическая. CVE-2020-0729 была признана уязвимостью удаленного выполнения кода с использованием файлов Windows LNK, также известных как файлы ярлыков. Отчасти эта уязвимость настолько убедительна, что исторически эксплойты для уязвимостей в файлах LNK использовались для распространения вредоносных программ, таких как знаменитый Stuxnet, и, в подавляющем большинстве случаев, они просто просматривали папку, содержащую вредоносный файл LNK, будь то локальную или сетевую папку, достаточную, чтобы вызвать уязвимость. Тогда возникает вопрос, имеет ли эта уязвимость такой же потенциал для эксплуатации, как и некоторые из прошлых уязвимостей LNK? В связи с тем, что файлы LNK представляют собой двоичный формат файлов, в котором имеется документация только для нескольких структур верхнего уровня, для ответа на этот вопрос требуется много покопаться в коде.
Начало анализа
Для Microsoft Patch Tuesday это стандартная процедура для нашей исследовательской группы: начать анализ уязвимости с распаковки пакета исправлений "только для безопасности" для данной платформы Windows и, на основе информации из рекомендаций Microsoft, попытаться найти файлы в патче, которые скорее всего связаны с уязвимостью. Февральский пакет исправлений не содержал обновлений ни для одной из обычных библиотек DLL, обычно связанных с обработкой LNK-файлов, таких как shell32.dll и windows.storage.dll, что заставляло нас ломать голову над тем, в чем может заключаться проблема. Однако при внимательном рассмотрении списка файлов нам удалось выделить одну конкретную DLL: StructuredQuery.dll. Одна из причин, по которой это выделялось, заключается в том, что в прошлом мы видели уязвимости, явно названные как связанные со StructuredQuery, например CVE-2018-0825, но в этом конкретном патче во вторник таких рекомендаций не существует. Итак, какова связь между файлами LNK и StructuredQuery? Быстрый поиск StructuredQuery в Microsoft’s Windows Dev Center приводит нас к документации по заголовку structuredquery.h, который сообщает нам, что он используется Windows Search и именно здесь соединяются файлы LNK и StructuredQuery.
Многочисленные способности файлов LNK
Файлы LNK в основном известны тем, что содержат двоичные структуры, которые создают ярлык для файла или папки, но менее известной особенностью является то, что они могут напрямую содержать сохраненный поиск. Обычно, когда пользователь ищет файл в Windows 10, на панеле проводника появляется вкладка "Инструменты поиска", позволяющая пользователю уточнить свой поиск и выбрать дополнительные параметры для поискового запроса. Вкладка также позволяет пользователям сохранять существующий поиск для повторного использования в более позднее время, в результате чего сохраняется XML-файл с расширением ".search-ms", формат файла, который задокументирован лишь частично.
Однако это не единственный способ сохранить поиск. Если щелкнуть и перетащить значок результатов поиска из адресной строки, выделенной на изображении ниже, в другую папку, будет создан файл LNK, содержащий сериализованную версию данных, которые будут содержаться в XML-файле search-ms.
Имея это в виду, давайте взглянем на патч diff для StructuredQuery с использованием BinDiff.
Как мы видим, изменилась только одна функция, StructuredQuery1::ReadPROPVARIANT (), и, судя по всему, она изменилась довольно сильно, основываясь на сходстве всего 81%. Беглое сравнение потоковых диаграмм может подтвердить, что изменения довольно обширны:
Что именно делает эта функция в контексте файла LNK? Ответ требует подробного изучения недокументированных структур, содержащихся в сохраненном файле LNK для поиска, так что давайте углубимся и посмотрим.
Файлы ссылок оболочки Windows содержат несколько обязательных и дополнительных компонентов, как определено в спецификации формата двоичного файла ссылки оболочки (.LNK). Каждый файл ссылки оболочки должен содержать, как минимум, заголовок ссылки оболочки, который имеет следующий формат:
Все многобайтовые поля представлены в порядке little-endian байта, если не указано иное.
Поле LinkFlags указывает отсутствие дополнительных структур, а также различные параметры, например, кодируются ли строки в ссылке оболочки в Unicode или нет. Ниже приведен иллюстративный макет поля LinkFlags:
Один флаг, который устанавливается в большинстве случаев, HasLinkTargetIDList, представлен позицией "A", младшим битом первого байта поля LinkFlags. Если установлено, структура LinkTargetIDList должна следовать за заголовком ссылки оболочки. Структура LinkTargetIDList определяет цель ссылки и имеет следующий макет:
Структура IDList, содержащаяся внутри, определяет формат постоянного списка идентификаторов элементов:
ItemIDList служит той же цели, что и путь к файлу, где каждая структура ItemID соответствует одному компоненту пути в иерархии, подобной пути. Идентификаторы элементов могут относиться к реальным папкам файловой системы, виртуальным папкам, таким как панель управления или сохраненные результаты поиска, или другим формам встроенных данных, которые служат "ярлыком" для выполнения определенной функции.
Для получения более общей информации о ItemIDs и ItemIDLists смотри общие концепции Microsoft Explorer. Особое значение для уязвимости имеют структуры ItemIDList и ItemID, присутствующие в файле LNK, который содержит сохраненный поисковый запрос.
Когда пользователь создает ярлык, содержащий информацию о поиске, результирующий файл содержит структуру IDList, которая начинается с идентификатора элемента папки делегата, за которым следует идентификатор элемента представления свойств пользователя, специфичный для поисковых запросов. Как правило, ItemID начинается со следующей структуры:
Значение двух байтов, начинающихся со смещения 0x0004, используется в сочетании с ItemSize и ItemType, чтобы помочь определить тип ItemID. Например, если ItemSize равен 0x14, а ItemType равен 0x1F, проверяются 2 байта в 0x0004, чтобы увидеть, больше ли их значение, чем ItemSize. Если это так, предполагается, что оставшиеся данные ItemID будут состоять из 16-байтового глобального уникального идентификатора (GUID). Обычно это структура первого ItemID, найденного в файле LNK, указывающего на файл или папку. Если ItemSize больше размера, необходимого для содержания GUID, но меньше байтов в 0x0004, оставшиеся данные после GUID считаются ExtraDataBlock, начиная с поля размером 2 байта, за которым следует это количество байтов данных.
Для идентификатора элемента папки делегата те же 2 байта соответствуют полю размера для оставшейся структуры, что приводит к следующей общей структуре:
Все идентификаторы GUID в файлах LNK хранятся с использованием представления RPC IDL для идентификаторов GUID. Представление RPC IDL означает, что первые три сегмента GUID хранятся как представления с прямым порядком байтов всего сегмента (т.е. DWORD, за которым следуют 2 WORD), тогда как каждый байт в последних 2 сегментах считается индивидуальным. Например, GUID {01234567-1234-ABCD-9876-0123456789AB} имеет следующее двоичное представление:
Точная функция идентификаторов элементов папки делегата не документирована. Однако вполне вероятно, что такая запись предназначена для того, чтобы последующие идентификаторы элементов обрабатывались классом, указанным в поле GUID элемента, тем самым устанавливая этот класс в качестве корневого пространства имен для иерархии. В случае файла LNK, содержащего встроенные данные поиска, GUID элемента будет {04731B67-D933-450A-90E6-4ACD2E9408FE}, что соответствует CLSID_SearchFolder, ссылке на Windows.Storage.Search.dll.
За идентификатором элемента папки делегата следует идентификатор элемента представления свойств пользователя, который имеет структуру, аналогичную структуре идентификатора элемента папки делегата:
Особое значение для этого отчета имеет поле PropertyStoreList, которое, если оно присутствует, содержит один или несколько сериализованных элементов PropertyStore, каждый из которых имеет следующую структуру:
Поле данных хранилища свойств представляет собой последовательность свойств. Все свойства в данном магазине PropertyStore принадлежат классу, определяемому GUID формата свойства. Каждое конкретное свойство идентифицируется числовым идентификатором, известным как идентификатор свойства или PID, который в сочетании с GUID формата свойства известен как ключ свойства или PKEY. PKEY определяется несколько иначе, если GUID формата свойств равен {D5CDD505-2E9C-101B-9397-08002B2CF9AE}. В этом случае каждое свойство считается частью "Property Bag" и имеет следующую структуру:
Пакеты свойств обычно содержат элементы с именами "Ключ:FMTID" и "Ключ:PID", определяющие конкретный PKEY, который определяет интерпретацию других элементов. Для определенных реализаций Property Bag также потребуется наличие других элементов, чтобы быть действительными.
Если GUID формата свойств не равен ранее упомянутому GUID для пакетов свойств, каждое свойство идентифицируется целочисленным значением PID и имеет следующую структуру:
Поле TypedPropertyValue соответствует типизированному значению свойства в наборе свойств, как определено в разделе 2.15 спецификации структур данных набора свойств Microsoft Object Linking and Embedding (OLE).
Различные ключи PKEY определены в заголовках, поставляемых с Windows SDK. Однако многие из них недокументированы и могут быть идентифицированы только путем изучения ссылок в отладочных символах для связанных библиотек. Для файлов LNK, содержащих встроенные данные поиска, первое хранилище свойств в идентификаторе элемента представления свойств пользователя имеет GUID формата свойства {1E3EE840-BC2B-476C-8237-2ACD1A839B22}, содержащий свойство с идентификатором 2, что соответствует PKEY_FilterInfo.
Поле TypedPropertyValue PKEY_FilterInfo состоит из свойства VT_STREAM. Обычно свойство VT_STREAM состоит из типа 0x0042, 2 байта заполнения и IndirectPropertyName, который указывает имя альтернативного потока, содержащего либо пакет PropertySetStream для хранения простого набора свойств, либо элемент потока "CONTENTS" для непростого набора свойств. хранилище согласно документации Microsoft. Это имя указывается с помощью строки широких символов "prop", за которой следует десятичная строка, соответствующая идентификатору свойства в пакете PropertySet. Однако, поскольку файлы LNK используют сериализованные хранилища свойств, встроенные в свойства VT_STREAM, IndirectPropertyName проверяется только для того, чтобы узнать, начинается ли оно с "prop". Само значение игнорируется. В результате получается следующая структура TypedPropertyValue:
Содержимое поля Stream Data зависит от конкретного PKEY, которому принадлежит свойство потока. Для PKEY_FilterInfo Stream Data по существу содержит встроенный PropertyStoreList с более сериализованными структурами PropertyStore и имеет следующую структуру:
Вложенный PropertyStoreList в потоке PKEY_FilterInfo представляет собой сериализованную версию тега "условия" в файле .search-ms. Ниже приведен пример структуры тега условий:
Точная функциональность элемента атрибута публично не документирована. Однако элемент атрибута содержит GUID, соответствующий CONDITION_HISTORY, и CLSID, который соответствует классу CConditionHistory в StructuredQuery, что означает, что вложенные структуры условий и атрибутов представляют историю поискового запроса до его сохранения. В общем, похоже, что атрибут chs элемента attribute определяет, присутствует ли какая-либо дополнительная история. Когда эта структура сериализуется в хранилище свойств, она помещается в PKEY_FilterInfo PropertyStoreList, который принимает форму пакета свойств с вышеупомянутым GUID формата свойства. Более конкретно, сериализованная структура условий содержится в свойстве VT_STREAM, которое идентифицируется именем "Условие". В результате элемент PropertyStore имеет следующую структуру:
Объект Condition обычно является объектом "Leaf Condition" или "Compound Condition", который содержит ряд вложенных объектов, обычно включая один или несколько объектов Leaf Condition и, возможно, дополнительные объекты Compound Condition. Оба объекта условия начинаются со следующей структуры:
Condition GUID будет {52F15C89-5A17-48E1-BBCD-46A3F89C7CC2} для Leaf Condition и {116F8D13-101E-4FA5-84D4-FF8279381935} для Compound Condition. Поле Attributes состоит из структур атрибутов, где количество структур атрибутов определяется полем "Number of Attributes". Каждая структура атрибута соответствует элементу атрибута из файла .search-ms и начинается со следующей структуры:
Остальная структура атрибута зависит от AttributeID и CLSID. Для вышеупомянутого атрибута CONDITION_HISTORY идентификатор attributeID установлен на {9554087B-CEB6-45AB-99FF-50E8428E860D} и имеет CLSID {C64B9B66-E53D-4C56-B9AE-FEDE4EE95DB1}. Оставшаяся структура будет объектом ConditionHistory, имеющим следующую структуру. Обратите внимание, что поля названы так же, как совпадающие атрибуты элемента XML атрибута:
Если значение has_nested_condition больше нуля, атрибут CONDITION_HISTORY будет иметь вложенный объект условия, который сам может иметь вложенные атрибуты с вложенными условиями и так далее.
Как только атрибут верхнего уровня полностью прочитан, включая все вложенные структуры, структуры Compound Condition и Leaf Condition начинают различаться. Остающаяся структура Compound Condition выглядит следующим образом со смещениями относительно конца поля атрибутов:
Поле numFixedObjects определяет, сколько дополнительных условий (обычно Leaf Condition) будет выполнено немедленно.
Остающаяся структура Leaf Condition следующая со смещениями относительно конца поля Attributes:
Наличие структур TokenInformationComplete зависит от того, установлен ли предыдущий флаг. Если он не установлен, структура отсутствует, и сразу следует следующий флаг. Если он установлен, присутствует следующая структура:
Таким образом, следующее дерево показывает простейшую возможную структуру файла LNK с сохраненным поиском, с удаленными для простоты нерелевантными структурами:
Помните, что поиск с одним Leaf Condition дает простейшую структуру. Чаще всего сохраненный файл LNK поиска начинается с Compound Conditio и множества вложенных структур, включая множество Leaf Conditions.
Уязвимость
Теперь, когда мы объяснили основную структуру сохраненного файла LNK поиска, мы можем взглянуть на саму уязвимость, которая заключается в том, как обрабатывается поле PropertyVariant в Leaf Condition.
Поле PropertyVariant в Leaf Condition примерно соответствует структуре PROPVARIANT. Структуры PropertyVariant состоят из 2-байтового типа, за которым следуют данные, относящиеся к этому типу. Важно отметить, что StructuredQuery, похоже, имеет слегка настраиваемую реализацию структуры PROPVARIANT, поскольку байты заполнения, указанные в спецификации Microsoft, обычно отсутствуют.
Также важно отметить, что значение 0x1000 или VT_VECTOR в сочетании с другим типом означает, что будет несколько значений указанного типа.
Анализ поля PropertyVariant обрабатывается нашей ранее упомянутой уязвимой функцией StructuredQuery1 :: ReadPROPVARIANT (). Функция сначала считывает 2-байтовый тип и проверяет, установлен ли VT_ARRAY (0x2000) из-за того, что он не поддерживается в StructuredQuery:
Затем функция проверяет, является ли тип VT_UI4 (0x0013), и если нет, вводит оператор switch для обработки всех других типов.
Сама уязвимость заключается в том, как обрабатывается PropertyVariant с типом VT_VARIANT (0x000C). Тип VT_VARIANT обычно используется в сочетании с VT_VECTOR, что эффективно приводит к серии структур PropertyVariant. Другими словами, это похоже на массив, члены которого могут иметь любой тип данных.
Когда тип PropertyVariant установлен в VT_VARIANT (0x000C), проверяется поле полного типа, чтобы увидеть, установлен ли VT_VECTOR.
Если VT_VECTOR не установлен, 24-байтовый буфер выделяется с помощью вызова CoTaskMemAlloc(), и буфер передается рекурсивному вызову ReadPROPVARIANT() с намерением, чтобы буфер был заполнен свойством, которое сразу же следует за полем VT_VARIANT. Однако буфер не инициализируется (например,заполнены байтами NULL) перед передачей в ReadPROPVARIANT().
Если вложенное свойство имеет тип VT_CF (0x0047), свойство, предназначенное для содержания указателя на данные буфера обмена, ReadPROPVARIANT() выполняет ту же проверку для VT_VECTOR и, если он не установлен, пытается записать следующие 4 байта потока в место, указанное 8-байтовым значением в ранее выделенном 24-байтовом буфере.
Поскольку буфер не был инициализирован, данные будут записаны в неопределенную ячейку памяти, что может привести к выполнению произвольного кода. Попытку записи данных можно увидеть в следующем исключении и частичной трассировке стека из WinDBG с включенной Page Heap в explorer.exe:
По сути, если злоумышленник может правильно манипулировать компоновкой памяти, чтобы неинициализированный буфер содержал значение, которое он контролирует, он может записывать любые данные по 4 байта за раз в адрес памяти по своему выбору.
Вывод
Анализ исправленной уязвимости был бы неполным без упоминания способа устранения уязвимости. В этом конкретном случае решение было простым; заполните вновь выделенный 24-байтовый буфер байтами NULL, гарантируя, что злоумышленник не сможет использовать данные в буфере, оставшиеся от предыдущих использований этой области памяти. Microsoft выпустила свой патч в феврале. Следует отметить, что Microsoft устранила еще одну уязвимость LNK в марте, но мартовский патч не имеет отношения к этой конкретной ошибке.
Особая благодарность Джону Симпсону и Пенгсу Ченгу из исследовательской группы Trend Micro за столь тщательный анализ этой уязвимости.
Источник: https://www.thezdi.com/blog/2020/3/25/cve-2020-0729-remote-code-execution-through-lnk-files
Автор перевода: yashechka
Переведено специально для https://xss.pro
Начало анализа
Для Microsoft Patch Tuesday это стандартная процедура для нашей исследовательской группы: начать анализ уязвимости с распаковки пакета исправлений "только для безопасности" для данной платформы Windows и, на основе информации из рекомендаций Microsoft, попытаться найти файлы в патче, которые скорее всего связаны с уязвимостью. Февральский пакет исправлений не содержал обновлений ни для одной из обычных библиотек DLL, обычно связанных с обработкой LNK-файлов, таких как shell32.dll и windows.storage.dll, что заставляло нас ломать голову над тем, в чем может заключаться проблема. Однако при внимательном рассмотрении списка файлов нам удалось выделить одну конкретную DLL: StructuredQuery.dll. Одна из причин, по которой это выделялось, заключается в том, что в прошлом мы видели уязвимости, явно названные как связанные со StructuredQuery, например CVE-2018-0825, но в этом конкретном патче во вторник таких рекомендаций не существует. Итак, какова связь между файлами LNK и StructuredQuery? Быстрый поиск StructuredQuery в Microsoft’s Windows Dev Center приводит нас к документации по заголовку structuredquery.h, который сообщает нам, что он используется Windows Search и именно здесь соединяются файлы LNK и StructuredQuery.
Многочисленные способности файлов LNK
Файлы LNK в основном известны тем, что содержат двоичные структуры, которые создают ярлык для файла или папки, но менее известной особенностью является то, что они могут напрямую содержать сохраненный поиск. Обычно, когда пользователь ищет файл в Windows 10, на панеле проводника появляется вкладка "Инструменты поиска", позволяющая пользователю уточнить свой поиск и выбрать дополнительные параметры для поискового запроса. Вкладка также позволяет пользователям сохранять существующий поиск для повторного использования в более позднее время, в результате чего сохраняется XML-файл с расширением ".search-ms", формат файла, который задокументирован лишь частично.
Однако это не единственный способ сохранить поиск. Если щелкнуть и перетащить значок результатов поиска из адресной строки, выделенной на изображении ниже, в другую папку, будет создан файл LNK, содержащий сериализованную версию данных, которые будут содержаться в XML-файле search-ms.
Имея это в виду, давайте взглянем на патч diff для StructuredQuery с использованием BinDiff.
Как мы видим, изменилась только одна функция, StructuredQuery1::ReadPROPVARIANT (), и, судя по всему, она изменилась довольно сильно, основываясь на сходстве всего 81%. Беглое сравнение потоковых диаграмм может подтвердить, что изменения довольно обширны:
Что именно делает эта функция в контексте файла LNK? Ответ требует подробного изучения недокументированных структур, содержащихся в сохраненном файле LNK для поиска, так что давайте углубимся и посмотрим.
Файлы ссылок оболочки Windows содержат несколько обязательных и дополнительных компонентов, как определено в спецификации формата двоичного файла ссылки оболочки (.LNK). Каждый файл ссылки оболочки должен содержать, как минимум, заголовок ссылки оболочки, который имеет следующий формат:
Все многобайтовые поля представлены в порядке little-endian байта, если не указано иное.
Поле LinkFlags указывает отсутствие дополнительных структур, а также различные параметры, например, кодируются ли строки в ссылке оболочки в Unicode или нет. Ниже приведен иллюстративный макет поля LinkFlags:
Один флаг, который устанавливается в большинстве случаев, HasLinkTargetIDList, представлен позицией "A", младшим битом первого байта поля LinkFlags. Если установлено, структура LinkTargetIDList должна следовать за заголовком ссылки оболочки. Структура LinkTargetIDList определяет цель ссылки и имеет следующий макет:
Структура IDList, содержащаяся внутри, определяет формат постоянного списка идентификаторов элементов:
ItemIDList служит той же цели, что и путь к файлу, где каждая структура ItemID соответствует одному компоненту пути в иерархии, подобной пути. Идентификаторы элементов могут относиться к реальным папкам файловой системы, виртуальным папкам, таким как панель управления или сохраненные результаты поиска, или другим формам встроенных данных, которые служат "ярлыком" для выполнения определенной функции.
Для получения более общей информации о ItemIDs и ItemIDLists смотри общие концепции Microsoft Explorer. Особое значение для уязвимости имеют структуры ItemIDList и ItemID, присутствующие в файле LNK, который содержит сохраненный поисковый запрос.
Когда пользователь создает ярлык, содержащий информацию о поиске, результирующий файл содержит структуру IDList, которая начинается с идентификатора элемента папки делегата, за которым следует идентификатор элемента представления свойств пользователя, специфичный для поисковых запросов. Как правило, ItemID начинается со следующей структуры:
Значение двух байтов, начинающихся со смещения 0x0004, используется в сочетании с ItemSize и ItemType, чтобы помочь определить тип ItemID. Например, если ItemSize равен 0x14, а ItemType равен 0x1F, проверяются 2 байта в 0x0004, чтобы увидеть, больше ли их значение, чем ItemSize. Если это так, предполагается, что оставшиеся данные ItemID будут состоять из 16-байтового глобального уникального идентификатора (GUID). Обычно это структура первого ItemID, найденного в файле LNK, указывающего на файл или папку. Если ItemSize больше размера, необходимого для содержания GUID, но меньше байтов в 0x0004, оставшиеся данные после GUID считаются ExtraDataBlock, начиная с поля размером 2 байта, за которым следует это количество байтов данных.
Для идентификатора элемента папки делегата те же 2 байта соответствуют полю размера для оставшейся структуры, что приводит к следующей общей структуре:
Все идентификаторы GUID в файлах LNK хранятся с использованием представления RPC IDL для идентификаторов GUID. Представление RPC IDL означает, что первые три сегмента GUID хранятся как представления с прямым порядком байтов всего сегмента (т.е. DWORD, за которым следуют 2 WORD), тогда как каждый байт в последних 2 сегментах считается индивидуальным. Например, GUID {01234567-1234-ABCD-9876-0123456789AB} имеет следующее двоичное представление:
\x67\x45\x23\x01\x34\x12\xCD\xAB\x98\x76\x01\x23\x45\x67\x89\xAB
Точная функция идентификаторов элементов папки делегата не документирована. Однако вполне вероятно, что такая запись предназначена для того, чтобы последующие идентификаторы элементов обрабатывались классом, указанным в поле GUID элемента, тем самым устанавливая этот класс в качестве корневого пространства имен для иерархии. В случае файла LNK, содержащего встроенные данные поиска, GUID элемента будет {04731B67-D933-450A-90E6-4ACD2E9408FE}, что соответствует CLSID_SearchFolder, ссылке на Windows.Storage.Search.dll.
За идентификатором элемента папки делегата следует идентификатор элемента представления свойств пользователя, который имеет структуру, аналогичную структуре идентификатора элемента папки делегата:
Особое значение для этого отчета имеет поле PropertyStoreList, которое, если оно присутствует, содержит один или несколько сериализованных элементов PropertyStore, каждый из которых имеет следующую структуру:
Поле данных хранилища свойств представляет собой последовательность свойств. Все свойства в данном магазине PropertyStore принадлежат классу, определяемому GUID формата свойства. Каждое конкретное свойство идентифицируется числовым идентификатором, известным как идентификатор свойства или PID, который в сочетании с GUID формата свойства известен как ключ свойства или PKEY. PKEY определяется несколько иначе, если GUID формата свойств равен {D5CDD505-2E9C-101B-9397-08002B2CF9AE}. В этом случае каждое свойство считается частью "Property Bag" и имеет следующую структуру:
Пакеты свойств обычно содержат элементы с именами "Ключ:FMTID" и "Ключ:PID", определяющие конкретный PKEY, который определяет интерпретацию других элементов. Для определенных реализаций Property Bag также потребуется наличие других элементов, чтобы быть действительными.
Если GUID формата свойств не равен ранее упомянутому GUID для пакетов свойств, каждое свойство идентифицируется целочисленным значением PID и имеет следующую структуру:
Поле TypedPropertyValue соответствует типизированному значению свойства в наборе свойств, как определено в разделе 2.15 спецификации структур данных набора свойств Microsoft Object Linking and Embedding (OLE).
Различные ключи PKEY определены в заголовках, поставляемых с Windows SDK. Однако многие из них недокументированы и могут быть идентифицированы только путем изучения ссылок в отладочных символах для связанных библиотек. Для файлов LNK, содержащих встроенные данные поиска, первое хранилище свойств в идентификаторе элемента представления свойств пользователя имеет GUID формата свойства {1E3EE840-BC2B-476C-8237-2ACD1A839B22}, содержащий свойство с идентификатором 2, что соответствует PKEY_FilterInfo.
Поле TypedPropertyValue PKEY_FilterInfo состоит из свойства VT_STREAM. Обычно свойство VT_STREAM состоит из типа 0x0042, 2 байта заполнения и IndirectPropertyName, который указывает имя альтернативного потока, содержащего либо пакет PropertySetStream для хранения простого набора свойств, либо элемент потока "CONTENTS" для непростого набора свойств. хранилище согласно документации Microsoft. Это имя указывается с помощью строки широких символов "prop", за которой следует десятичная строка, соответствующая идентификатору свойства в пакете PropertySet. Однако, поскольку файлы LNK используют сериализованные хранилища свойств, встроенные в свойства VT_STREAM, IndirectPropertyName проверяется только для того, чтобы узнать, начинается ли оно с "prop". Само значение игнорируется. В результате получается следующая структура TypedPropertyValue:
Содержимое поля Stream Data зависит от конкретного PKEY, которому принадлежит свойство потока. Для PKEY_FilterInfo Stream Data по существу содержит встроенный PropertyStoreList с более сериализованными структурами PropertyStore и имеет следующую структуру:
Вложенный PropertyStoreList в потоке PKEY_FilterInfo представляет собой сериализованную версию тега "условия" в файле .search-ms. Ниже приведен пример структуры тега условий:
Точная функциональность элемента атрибута публично не документирована. Однако элемент атрибута содержит GUID, соответствующий CONDITION_HISTORY, и CLSID, который соответствует классу CConditionHistory в StructuredQuery, что означает, что вложенные структуры условий и атрибутов представляют историю поискового запроса до его сохранения. В общем, похоже, что атрибут chs элемента attribute определяет, присутствует ли какая-либо дополнительная история. Когда эта структура сериализуется в хранилище свойств, она помещается в PKEY_FilterInfo PropertyStoreList, который принимает форму пакета свойств с вышеупомянутым GUID формата свойства. Более конкретно, сериализованная структура условий содержится в свойстве VT_STREAM, которое идентифицируется именем "Условие". В результате элемент PropertyStore имеет следующую структуру:
Объект Condition обычно является объектом "Leaf Condition" или "Compound Condition", который содержит ряд вложенных объектов, обычно включая один или несколько объектов Leaf Condition и, возможно, дополнительные объекты Compound Condition. Оба объекта условия начинаются со следующей структуры:
Condition GUID будет {52F15C89-5A17-48E1-BBCD-46A3F89C7CC2} для Leaf Condition и {116F8D13-101E-4FA5-84D4-FF8279381935} для Compound Condition. Поле Attributes состоит из структур атрибутов, где количество структур атрибутов определяется полем "Number of Attributes". Каждая структура атрибута соответствует элементу атрибута из файла .search-ms и начинается со следующей структуры:
Остальная структура атрибута зависит от AttributeID и CLSID. Для вышеупомянутого атрибута CONDITION_HISTORY идентификатор attributeID установлен на {9554087B-CEB6-45AB-99FF-50E8428E860D} и имеет CLSID {C64B9B66-E53D-4C56-B9AE-FEDE4EE95DB1}. Оставшаяся структура будет объектом ConditionHistory, имеющим следующую структуру. Обратите внимание, что поля названы так же, как совпадающие атрибуты элемента XML атрибута:
Если значение has_nested_condition больше нуля, атрибут CONDITION_HISTORY будет иметь вложенный объект условия, который сам может иметь вложенные атрибуты с вложенными условиями и так далее.
Как только атрибут верхнего уровня полностью прочитан, включая все вложенные структуры, структуры Compound Condition и Leaf Condition начинают различаться. Остающаяся структура Compound Condition выглядит следующим образом со смещениями относительно конца поля атрибутов:
Поле numFixedObjects определяет, сколько дополнительных условий (обычно Leaf Condition) будет выполнено немедленно.
Остающаяся структура Leaf Condition следующая со смещениями относительно конца поля Attributes:
Наличие структур TokenInformationComplete зависит от того, установлен ли предыдущий флаг. Если он не установлен, структура отсутствует, и сразу следует следующий флаг. Если он установлен, присутствует следующая структура:
Таким образом, следующее дерево показывает простейшую возможную структуру файла LNK с сохраненным поиском, с удаленными для простоты нерелевантными структурами:
Помните, что поиск с одним Leaf Condition дает простейшую структуру. Чаще всего сохраненный файл LNK поиска начинается с Compound Conditio и множества вложенных структур, включая множество Leaf Conditions.
Уязвимость
Теперь, когда мы объяснили основную структуру сохраненного файла LNK поиска, мы можем взглянуть на саму уязвимость, которая заключается в том, как обрабатывается поле PropertyVariant в Leaf Condition.
Поле PropertyVariant в Leaf Condition примерно соответствует структуре PROPVARIANT. Структуры PropertyVariant состоят из 2-байтового типа, за которым следуют данные, относящиеся к этому типу. Важно отметить, что StructuredQuery, похоже, имеет слегка настраиваемую реализацию структуры PROPVARIANT, поскольку байты заполнения, указанные в спецификации Microsoft, обычно отсутствуют.
Также важно отметить, что значение 0x1000 или VT_VECTOR в сочетании с другим типом означает, что будет несколько значений указанного типа.
Анализ поля PropertyVariant обрабатывается нашей ранее упомянутой уязвимой функцией StructuredQuery1 :: ReadPROPVARIANT (). Функция сначала считывает 2-байтовый тип и проверяет, установлен ли VT_ARRAY (0x2000) из-за того, что он не поддерживается в StructuredQuery:
Затем функция проверяет, является ли тип VT_UI4 (0x0013), и если нет, вводит оператор switch для обработки всех других типов.
Сама уязвимость заключается в том, как обрабатывается PropertyVariant с типом VT_VARIANT (0x000C). Тип VT_VARIANT обычно используется в сочетании с VT_VECTOR, что эффективно приводит к серии структур PropertyVariant. Другими словами, это похоже на массив, члены которого могут иметь любой тип данных.
Когда тип PropertyVariant установлен в VT_VARIANT (0x000C), проверяется поле полного типа, чтобы увидеть, установлен ли VT_VECTOR.
Если VT_VECTOR не установлен, 24-байтовый буфер выделяется с помощью вызова CoTaskMemAlloc(), и буфер передается рекурсивному вызову ReadPROPVARIANT() с намерением, чтобы буфер был заполнен свойством, которое сразу же следует за полем VT_VARIANT. Однако буфер не инициализируется (например,заполнены байтами NULL) перед передачей в ReadPROPVARIANT().
Если вложенное свойство имеет тип VT_CF (0x0047), свойство, предназначенное для содержания указателя на данные буфера обмена, ReadPROPVARIANT() выполняет ту же проверку для VT_VECTOR и, если он не установлен, пытается записать следующие 4 байта потока в место, указанное 8-байтовым значением в ранее выделенном 24-байтовом буфере.
Поскольку буфер не был инициализирован, данные будут записаны в неопределенную ячейку памяти, что может привести к выполнению произвольного кода. Попытку записи данных можно увидеть в следующем исключении и частичной трассировке стека из WinDBG с включенной Page Heap в explorer.exe:
По сути, если злоумышленник может правильно манипулировать компоновкой памяти, чтобы неинициализированный буфер содержал значение, которое он контролирует, он может записывать любые данные по 4 байта за раз в адрес памяти по своему выбору.
Вывод
Анализ исправленной уязвимости был бы неполным без упоминания способа устранения уязвимости. В этом конкретном случае решение было простым; заполните вновь выделенный 24-байтовый буфер байтами NULL, гарантируя, что злоумышленник не сможет использовать данные в буфере, оставшиеся от предыдущих использований этой области памяти. Microsoft выпустила свой патч в феврале. Следует отметить, что Microsoft устранила еще одну уязвимость LNK в марте, но мартовский патч не имеет отношения к этой конкретной ошибке.
Особая благодарность Джону Симпсону и Пенгсу Ченгу из исследовательской группы Trend Micro за столь тщательный анализ этой уязвимости.
Источник: https://www.thezdi.com/blog/2020/3/25/cve-2020-0729-remote-code-execution-through-lnk-files
Автор перевода: yashechka
Переведено специально для https://xss.pro