Взлом ярлыков. Эксплуатируем опасную уязвимость в ярлыках Microsoft Windows

Взлом ярлыков. Эксплуатируем опасную уязвимость в ярлыках Microsoft Windows

Эксплойт

Каждый, кто пользовался Windows, хорошо знает, что такое ярлык. Однако этот значок со стрелкой далеко не так прост, как может показаться, и при определенных условиях он открывает широкие возможности для атакующего. Я расскажу, как сделать «ядовитый» ярлык и как с помощью флешки выполнять произвольный код на целевой системе.

 


Исторический экскурс

Все началось еще в 2010 году, когда в паблике всплыл эксплоит для Windows версий 7 и ниже. Эксплоит позволял атакующему выполнить произвольный код в системе, причем пользователю достаточно было всего лишь перейти по ссылке.

Этот эксплоит использовался как один из вариантов распространения небезызвестного червя Stuxnet. Когда кто-то вставлял флешку в компьютер, червь заражал ее, копируя вредоносный DLL, и создавал специальный ярлык. Если на машине, в которую потом вставляли носитель, была включена функция автозапуска, вредоносный код из библиотеки выполнялся.


Импакт при этом не ограничивался одной только флешкой: код также срабатывал, если пользователь посещал вредоносный URL, который указывал на сетевую или локальную папку с эксплоитом.

Взглянув на весь этот ужас, в Microsoft выпустили апдейт MS10-046, который исправляет эту уязвимость. Вот только получилась заплатка не то чтобы удачной.

В начале января 2015 года немецкий исследователь Михель Герклоц (Michael Heerklotz) детально изучил этот патч и нашел способ его обойти. Результатом работы Герклоца стал отчет на конференции Zero Day Initiative (ZDI), проводимой компанией HP. После исправления патча в марте того же 2015 года исследование было опубликовано. Имеется даже видеодемонстрация работы эксплоита.

После этого в Microsoft выкатили следующий патч — с порядковым номером MS15-018, однако и он не смог полностью закрыть уязвимость. Совсем недавно, в июне 2017 года, был обнаружен очередной способ его обхода. Импакт при этом остался тем же — выполнение произвольного кода на целевой системе.

Уязвимость откликается на CVE-2017-8464, а PoC-эксплоит создали Йорик Костер (Yorick Koster) и nixawk. Йорик также написал модуль для Metasploit. MSF я не использую по религиозным соображениям и поэтому в обзоре буду опираться на первоначальный сплоит. Найти его ты сможешь, заглянув в репозиторий к nixawk.

Если же хочешь просто потриггерить уязвимость, то здесь есть готовые LNK-файлы и DLL, которая запускает калькулятор. Тебе остается лишь скинуть их на флешку.

Материал адpесован специалистам по безопасности и тем, кто собираeтся ими стать. Вся информация предоставлена исключительно в ознакомительных целях. Ни канал, ни автор не несут ответственности за любой возможный вред, причиненный материалами данной статьи.

 


Стенд

В качестве подопытного кролика для теста уязвимости я буду использовать виртуалку с Windows 10 х64.

Уязвимая версия Windows 10 x64

При работе с бинарщиной нам, конечно же, пригодится дизассемблер. Не будем оригинальничать и возьмем IDA.

Также нам понадобится какой-нибудь дебаггер. Я решил использовать WinDbg в режиме отладчика ядра. Вот один из способов настроить удаленный дебаггинг виртуалки.

  1. Запустить WinDbg. Затем File → Kernel Debug. Там во вкладке NET нужно указать порт. Его будет слушать программа в ожидании подключения от отлаживаемой системы. Еще можно указать ключ для шифрования передаваемых данных во время соединения.
Настройка отладки ядра по сети в WinDbg
  1. Включить режим отладки на гостевой машине. В этом нам поможет консольная утилита bcdedit.

bcdedit /debug on
bcdedit /dbgsettings net hostip:127.0.0.1 port:50000 key:time.to.debug.1337

где hostip — IP хостовой ОС, port и key — порт и ключ, которые ты указал в настройках WinDbg. После перезагрузки виртуалки она подключится к отладчику.

Гостевая ОС готова к отладке

 


Особенности линкования

Чтобы понимать уязвимость и успешно ее проэксплуатировать, нужно сначала разобраться непосредственно с самим форматом LNK-файлов. К счастью, нам не придется продираться через дебри бинарщины вслепую, потому что ребята из Microsoft позаботились и написали подробную техническую спецификацию под названием Shell Link (.LNK) Binary File Format. Будем заглядывать туда периодически.

Еще нам понадобится утилита для просмотра детальной информации по файлам LNK. Так как моя основная ОС — это Windows 10, то я воспользуюсь приложением LNKParser. Если ты ищешь что-то платформонезависимое, то попробуй модуль pylnk (pip install pylnk) для Python.

Shell Link Binary File Format состоит из последовательности структур, которые регулируются правилами ABNF (Augmented Backus-Naur Form). Правила же описаны в спецификации RFC5234.

SHELL_LINK = SHELL_LINK_HEADER [LINKTARGET_IDLIST] [LINKINFO] [STRING_DATA] *EXTRA_DATA

Совокупность этих структур и называется shell link, а в простонародье — «ярлык».

В нем содержится не только ссылка на местоположение объекта, как можно было ожидать, но и множество других параметров.

Рассмотрим все это на живом примере. Для этого создадим ярлык на любой файл (я создал для explorer.exe) и натравим на него LNKParser.

lnk_parser_cmd "explorer.exe - Shortcut.lnk"
Просмотр содержимого ярлыка с помощью LNKParser

Здесь я рассмотрю только нужные для создания эксплоита структуры, ты же можешь покопаться в тонкостях строения .LNK в свое удовольствие.

За загрузку, анализ и парсинг данных из файла ярлыка отвечает функция CShellLink::_LoadFromStream из библиотеки windows.storage.dll.

Функция парсинга содержимого LNK-файла

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

Структура блока SHELL_LINK_HEADER

Пройдемся по остальным структурам.

  • HeaderSize (4 байта) — размер блока Header, он фиксированный — 0x0000004C.
  • LinkCLSID (16 байт) — уникальный идентификатор класса (CLSID). Структура CLSID — это оболочка для идентификатора COM-класса. Также имеет фиксированное значение — 00021401-0000-0000C000-000000000046.
  • LinkFlags (4 байта) — флаги, которые отвечают за опции ярлыка. Некоторые флаги означают наличие дополнительных структур в файле.
  • FileAttributes (4 байта) — атрибуты файла, на который ссылается ярлык.
  • CreationTime (8 байт), AccessTime (8 байт), WriteTime (8 байт) — время создания файла, последнего доступа к нему и его изменения.
  • FileSize (4 байта) — размер файла. Формат — 32-битное беззнаковое целое.
  • IconIndex (4 байта) — индекс иконки файла в указанном хранилище. Формат — 32-битное беззнаковое целое.
  • ShowCommand (4 байта) — вид окна приложения, запущенного через ярлык. Формат — 32-битное беззнаковое целое. Он может быть трех видов: нормальный (SW_SHOWNORMAL0x00000001), развернутый на весь экран (SW_SHOWMAXIMIZED0x00000003), свернутый (SW_SHOWMINNOACTIVE0x00000007).
  • HotKey (2 байта) — структура HotKeyFlags отвечает за назначенные ярлыку горячие клавиши. В настройках линка ты можешь указать требуемое сочетание клавиш, и если сам LNK лежит на рабочем столе или в меню, то при их нажатии он будет срабатывать.
  • Reserved1 (2 байта), Reserved2 (4 байта), Reserved3 (4 байта) — зарезервированные значения. Должны быть нулевыми.

Из всего этого многообразия нас интересует только LinkFlags. Секция состоит из 32 бит, каждый из которых отвечает за разные опции ярлыка.

Битовая маска секции LinkFlags

Нужные нам биты — это 0 (HasLinkTargetIDList) и 7 (IsUnicode).

Важные для эксплуатации флаги

С IsUnicode, думаю, все ясно из названия, а вот нулевой бит рассмотрим подробнее.

Флаг HasLinkTargetIDList указывает, что следующим блоком, идущим за хидером, будет структура IDList.

Структура IDList в файле легитимного ярлыка

Функция CShellLink::_LoadIDList сначала читает первые два байта данных из блока (это размер структуры) и выделяет требуемое количество памяти для его загрузки.

Функция CShellLink::_LoadIDList для парсинга содержимого структуры IDList

Затем начинается чтение данных из LINKTARGET_IDLIST. Каждый элемент этой структуры — это часть пути до объекта, на который ссылается ярлык. Система проходит по каждому такому элементу ItemID и читает его данные.

Путь записывается в кодировке ASCII и/или UTF-16. В легитимном ярлыке также имеются метаданные для каждого элемента пути. Ты их можешь наблюдать на скриншоте ниже, но для эксплуатации они нам ни к чему.

Блок IDList с метаданными в легитимном ярлыке

Формат типа — идентификатор класса CLSID. Поиск соответствия ведется в ветке реестра HKEY_CLASSES_ROOTCLSID. Например, в нашем случае GUID объекта равен 20d04fe0-3aea-1069-a2d8-08002b30309d, что в пространство имен оболочки Windows соответствует My Computer. Подробнее об этом мы поговорим на этапе написания эксплоита.

С хидером более-менее разобрались, теперь переносимся в конец LNK-файла, к разделу дополнительной информации ExtraData.

EXTRA_DATA = *EXTRA_DATA_BLOCK TERMINAL_BLOCK

Он может включать в себя много дополнительных структур.

EXTRA_DATA_BLOCK = CONSOLE_PROPS / CONSOLE_FE_PROPS / DARWIN_PROPS /
ENVIRONMENT_PROPS / ICON_ENVIRONMENT_PROPS /
KNOWN_FOLDER_PROPS / PROPERTY_STORE_PROPS /
SHIM_PROPS / SPECIAL_FOLDER_PROPS /
TRACKER_PROPS / VISTA_AND_ABOVE_IDLIST_PROPS

За парсинг этого раздела отвечает библиотека shlwapi.dll, а именно функция SHReadDataBlockList.

Вызов внешней функции SHReadDataBlockList в процессе выполнения


Функция SHReadDataBlockList для парсинга раздела ExtraData

Она читает все содержимое структуры EXTRA_DATA и загружает ее в память. После загрузки вызывается проверка валидности данных — функция IsValidDataBlock.

Функция IsValidDataBlock проверяет данные из ExtraData

IsValidDataBlock сверяет размеры и сигнатуры, и если проблем не обнаружено, то выполнение программы возвращается в функцию CShellLink::_LoadFromStream.

Из всего, что можно записать в файл с ярлыком, нас интересуют только SpecialFolderDataBlock и KnownFolderDataBlock. Так как разделы одинаковы по структуре, разберем только один из них.

Структура SpecialFolderDataBlock очень важна в процессе эксплуатации. Она используется, если файл, на который ссылается ярлык, находится в специальной папке системы или ссылается на нее. Можно также столкнуться с названием CSIDL (Constant Special item ID List) или KNOWNFOLDERID (пришло на смену CSIDL в Windows Vista), именно их ты встретишь в официальной документации.

Вообще, специальные папки — это псевдонимы для системных папок. Такими, например, являются «Корзина», «Панель управления» или AppData. Так вот, вместо использования абсолютных путей, которые могут быть разными в разных системах, в Microsoft придумали такие уникальные статичные псевдонимы. Они гарантируют программистам, что при обращении, например, к панели управления они точно в нее попадут, где бы программа ни выполнялась.

В некоторых таких папках объекты представляются особенным образом. И для того чтобы ярлык был вызван корректно, после загрузки блока IDList данные из него интерпретируются в соответствии с текущими установками типа папки.

Возможно, пока это звучит непонятно, но скоро мы детально разберем этот вопрос, и все встанет на свои места. Пока же возвращаемся к SpecialFolderDataBlock.

Сам раздел состоит из четырех блоков. Все блоки имеют один и тот же размер и тип данных — 4 байта, 32-битное беззнаковое целое.

Блоки раздела SpecialFolderDataBlock

Первый блок BlockSize, как можно понять из названия, — это общая длина раздела. Она фиксирована и имеет значение 0x00000010. В KnownFolderDataBlock — 0x0000001C.

Затем следует BlockSignature — сигнатура раздела. Ее значение тоже фиксировано — 0xA0000005. В KnownFolderDataBlock — 0xA000000B.

Следом за BlockSignature идет блок SpecialFolderID. Он как раз указывает, к какой из специальных папок относится ярлык. Их список можно посмотреть, например, тут. В рассматриваемом случае объект explorer.exe находится в папке Windows. Это специальная папка, которая имеет значение 0х24. Что мы и наблюдаем в файле ярлыка.

SpecialFolderID из ярлыка к файлу explorer.exe

Разница с KnownFolderDataBlock в том, что там используется полноценный GUID вместо целочисленного представления.

В структуре KnownFolderDataBlock используется полноценный GUID

Следующий важный блок — Offset. Это смещение в байтах, указывающее на элемент в блоке IDList. Так как в нашем ярлыке задан путь C:Windowsexplorer.exe, то смещение будет указывать на explorer.exe.

Блок offset структуры SpecialFolderDataBlock указывает на элемент из IDList

Обработкой структуры занимается функция CShellLink::_DecodeSpecialFolder.

Вызов функции _DecodeSpecialFolder

Сначала определяется тип использованной структуры по сигнатуре (блок BlockSignature). SpecialFolderDataBlock имеет сигнатуру 0xA0000005, а KnownFolderDataBlock — 0xA000000B. Функция SHFindDataBlock выполняет этот поиск в ExtraData.

Вызов функции SHFindDataBlock для поиска нужного раздела по сигнатуре

 


Особенности эксплуатации

Прежде чем сгенерировать ярлык, эксплуатирующий уязвимость, нужно скомпилировать DLL с нужной полезной нагрузкой. Здесь я не буду описывать весь процесс компиляции DLL, этого полно на просторах интернета. Для теста достаточно использовать готовую библиотеку из репозитория 3gstudent, которая запускает калькулятор.

Я переименую ее во что-нибудь менее монструозное, например test.dll.

Теперь нужно сгенерировать ярлык. Напомню, что я использую эксплоит авторства nixawk и ykoster. Он мультиплатформенный и написан на Python, никаких зависимостей. Что еще нужно?

./42429.py </path/to/lnk/file> </path/to/dll/for/load>

Путь до DLL — это очень важный параметр. Дело в том, что эксплоит будет располагаться на внешнем носителе и работать оттуда, а путь до объекта в ярлыке абсолютный. Это создает определенные сложности, так как буква диска будет меняться от машины к машине, потому что Windows присваивает ее автоматически.

Проблема обходится в лоб — созданием кучки ярлыков для каждой возможной буквы диска. Именно поэтому в репозитории по ссылке выше есть ярлыки на каждую букву алфавита.

Предположим, что мы знаем букву и это D:. Далее нам нужен любой носитель: флешка, дискета (если вдруг найдешь) или даже файл ISO. Вообще, эксплоиту без разницы, где он находится. Даже если ты поместишь сгенерированный ярлык на жесткий диск, пейлоад отработает при открытии папки с таким ярлыком. Съемный носитель используется в примере лишь как один из вариантов доставки полезной нагрузки на машину пользователя.

Создаем собственно сам файл LNK.

./42429.py test.lnk D:test.dll

Так как базовые знания о том, как устроены ярлыки, у нас имеются, то пробежимся по исходнику сплоита и проследим особенности создания вредоносного ярлыка.

Сначала хидер. Здесь все не сложнее, чем в безопасном ярлыке. В блоке LinkFlags используем флаги HasLinkTargetIDList и IsUnicode. Байты 0 и 7, помнишь?

189:         generate_SHELL_LINK_HEADER(),
...
...
013: def generate_SHELL_LINK_HEADER():
...
055:     shell_link_header = [
...
058:         b'x81x00x00x00',                                                 # "LinkFlags"      : (4 bytes)  0x81 = 0b10000001 = HasLinkTargetIDList + IsUnicode

Дальше дело за построением блока IDList.

197:         generate_LINKTARGET_IDLIST(path, name),
...
...
075: def generate_LINKTARGET_IDLIST(path, name):
...
114:     idlist = [
115:         # ItemIDList
116:
117:         generate_ItemID(b'x1fx50xe0x4fxd0x20xeax3ax69x10xa2xd8x08x00x2bx30x30x9d'),
118:         generate_ItemID(b'x2ex80x20x20xecx21xeax3ax69x10xa2xddx08x00x2bx30x30x9d'),

А вот как выглядит сама функция generate_ItemID.

88:     def generate_ItemID(Data):
89:         itemid = [
90:             struct.pack('H', len(Data) + 2),  # ItemIDSize + len(Data)
91:             Data
92:         ]
...
97:         return b"".join(itemid)

В строке 117 мы генерируем первый элемент списка. Как видишь, тут используется тот же GUID 20D04FE0-3AEA-1069-A2D8-08002B30309D, что и в ярлыке, который мы рассматривали в примере. Не забываем про порядок байтов little-endian.

Первый элемент структуры IDList в ярлыке-эксплоите

А вот второй элемент (строка 118) уже интереснее. GUID 21EC2020-3AEA-1069-A2DD-08002B30309D говорит нам о том, что элемент, на который ссылается ярлык, находится в пространстве имен «Панели управления». Подробнее о пространстве имен читай в MSDN.

Второй элемент структуры IDList в ярлыке-эксплоите. GUID 21EC2020-3AEA-1069-A2DD-08002B30309D

Дальше на основе переданного в качестве аргумента пути до библиотеки генерируется третий элемент в IDList. В нашем случае DLL лежит на диске D:.

119:         generate_ItemID(generate_cpl_applet(path)),

Смотрим, что это за функция.

099:     def generate_cpl_applet(path, name=name):
100:         name += b'x00'
101:         path += b'x00'
102:
103:         bindata = [
104:             b'x00x00x00x00x00x00x00x00x00x00x00x6ax00x00x00x00x00x00',
105:             struct.pack('H', len(path)),
106:             struct.pack('H', len(name)),
107:             path.encode('utf-16')[2:],
108:             name.encode('utf-16')[2:],
109:             b"x00x00"  # comment
110:         ]

Дальше создается секция ExtraData.

213:         generate_EXTRA_DATA()

Вот как это выглядит.

135: def generate_EXTRA_DATA():
...
163:     extra_data = [
164:         b'x10x00x00x00',
165:         b'x05x00x00xA0',
166:         b'x03x00x00x00',
167:         b'x28x00x00x00',
168:         b'x00x00x00x00'   # TERMINAL_BLOCK
169:     ]

Строка 166, как мы помним, — это SpecialFolderID, и он указывает на то, что объект находится в специальной папке. И эта папка — «Панель управления» (0x03 это CSIDL_CONTROLS).

Блок SpecialFolderID в ярлыке-эксплоите

В строке 167 находится смещение объекта в байтах, указывающее на нашу DLL. Оно считается относительно IDList. Так как за путь до библиотеки отвечает третий элемент, а перед ним идут два элемента по 0x14 байт, то смещение равно 0х28.

Смещение относительно структуры IDList указывает на третий элемент

Дальше эксплоит просто записывает все сгенерированные данные в файл ярлыка.

 


Время запускать

Итак, ярлык говорит системе о том, что объект объявляется частью «Панели управления» и обрабатывать его нужно особенным образом. «Панель управления» состоит из компонентов, называемых апплетами Control Panel Applets (CPLApplet). По большому счету это обычные DLL, которые имеют расширение .cpl и экспортируют функцию CPlApplet.

Вот и наша библиотека может спокойно сойти за такой апплет, если его правильно преподнести системе. Чем и занимается сгенерированный файл LNK.

В прошлом разделе мы остановились на этапе парсинга специальной директории.

Давай вернемся к этому процессу уже с реальным примером. Чтобы разобраться, что там происходит, я отряхнул от пыли свой WinDbg. Аттачимся к процессу explorer.exe и ставим прерывание на вызов функции _DecodeSpecialFolder.

!process 0 0 explorer.exe
.process /r /p ffffda016330b7c0
bu windows_storage!CShellLink::_DecodeSpecialFolder
Аттач к процессу explorer.exe и установка брейк-пойнта на _DecodeSpecialFolder

Теперь подключаем флешку к виртуальной машине и попадаем в дебаггер. Брейк-пойнт сработал. Мы находимся перед вызовом _DecodeSpecialFolder.

Потрейсим немножко вперед с помощью F10 и дойдем до функции SHFindDataBlock. Видим, что в данных из IDList найден блок SpecialFolderDataBlock.

Блок SpecialFolderDataBlock, загруженный в память

Затем управление переходит к функции SHCloneSpecialIDList, она возвращает указатель на структуру ITEMIDLIST, которая отвечает за указанную в SpecialFolderDataBlockспециальную папку (в нашем случае — «Панель управления», так как SpecialFolderIDравен 0х03).

Далее читается смещение, по которому можно найти элемент, содержащий путь до DLL.

Затем все это дело передается в функцию TranslateAliasWithEvent из той же библиотеки window.storage.dll. В ней происходит обработка объекта согласно указанным в ярлыке двум CLSID. Для этого из реестра считывается информация о них.

Дальше за дело берется функция CControlPanelFolder::ParseDisplayName из shell32.dll. Она пытается получить название псевдоэлемента панели управления, так как мы выдаем нашу библиотеку с калькулятором за него. Потом выполнение передается к CControlPanelFolder::_GetPidlFromAppletId.

Выполнение на этапе вызова функции _GetPidlFromAppletId

Функция читает название апплета из элемента ItemID. Эксплоит использует строку «Microsoft», но там, по сути, может быть что угодно.

099:     def generate_cpl_applet(path, name=name):
100:         name += b'x00'
174: def ms_shllink(path, name=b"Microsoft"):

Для получения дальнейших сведений об апплете система должна загрузить его. Это приводит нас к функции CPL_LoadCPLModule.

Отладка эксплоита. Момент вызова функции, загружающей апплет

Эта функция наконец-то загружает наш DLL при помощи LoadLibraryW. Поскольку используется именно эта функция, а не ее расширенная версия LoadLibraryExW, при загрузке библиотеки выполняется зашитый в нее пейлоад. Процесс запускается в контексте explorer.exe и, соответственно, получает максимальные возможные для текущего пользователя права.

Стек вызова функций до отработки пейлоада и результат его работы — запущенный калькулятор

 

Кратко о патче

Разумеется, разработчики не сидели сложа руки и выпустили патч для всех уязвимых версий Windows. Это один из самых маленьких фиксов, созданных в Microsoft, что я видел. По сути, добавилась всего одна функция перед вызовом CPL_LoadCPLModule.

Изменения в библиотеке shell32.dll после патча

Это функция _IsRegisteredCPLApplet. Она проверяет, зарегистрирован ли соответствующий апплет в панели управления, и если нет, то загрузка DLL отменяется. Вот как выглядит diff кода. Будем надеяться, что с четвертого раза у Microsoft все получилось как надо!

 

Выводы

В заключение хочется сказать: не всегда бессмысленно проверять то, что уже было исследовано до тебя. И, как показывает наглядный пример с этой уязвимостью, даже если выпущен патч — это совсем не повод опускать руки и считать, что баг устранен окончательно.


Report Page