среда, 13 апреля 2022 г.

I was visited by death again: UEFI руткиты в схемах и мемах, поговорим о заражении

 

Спустя, наверное, продолжительное время, я потихоньку собираюсь с силами (мне просто надоело залипать в Krita), что не может меня не радовать. Я хочу запустить цикл статей, где мы будем говорить о достаточно непонятной вещи — о руткитах, находящихся между ОС и прошивкой (между 0 и -2 кольцами). Эта по сути вводная статья, где я допускаю много воды и щитпосчу, объясняю все на теории, в дальнейших статьях цикла я постараюсь подобного более не допускать (или допускать, но меньше). Что же, стоит ввести в курс дела, дать понятие, что такое UEFI/EFI.


Кто такой этот ваш UEFI/EFI

UEFI/EFI – это некий интерфейс, который связывает ОС и программы железяк, для успешного взаимодействия оных друг с другом. О таких микропрограммах (или микрокоде) мы когда-нибудь обязательно поговорим, сейчас вам стоит попытаться не путать наш UEFI с другой, не менее интересной штукой — HAL (Hardware Abstraction Layer), о нем мы тоже поговорим в одном из будущих райтапов, надеюсь. Так вот, задачей UEFI/EFI является корректное связывание микропрограмм и программ ОС, т.е правильная инициализация оборудования и последующая передача управления к загрузчику основной ОС (на самом деле это поле не паханное).

Не вижу смысла говорить, что UEFI/EFI был создан в качестве замены более устаревшего интерфейса — BIOS. Однако, я все же это вам напомню. Раньше интерфейс UEFI назывался EFI, но в последствие был переименован в UEFI (к сожалению я точно не вспомню, когда это произошло), поэтому мы обусловим, что для удобства в последующих статьях мы будем называть наш интерфейс UEFI.


UEFI руткиты, особенности и несколько забавных фактов

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

Первое, что стоит обусловить — существует среда драйверов, называемая EFI Byte Code (EBC), она предоставляет нам независимую среду от UEFI платформы для драйверов устройств. В таком случае у фирмварного ПО должен быть интерпретатор EBC (последняя ревизия спецификации вышла в марте 2021 года):


Также, введем понятие EFI Shell – оболочки, командной строки для управления и выполнения необходимых вещей пользователю (например, обновление UEFI). EFI Shell находится на ПЗУ материнской платы, собсна, где и все драйвера UEFI. Кстати, EFI Shell это тоже приложение, но я думаю, что это очевидно.


Благодаря всему описанному выше, мы можем вывести для себя понятие, что такое UEFI руткит:

UEFI руткит — драйвер интерфейса UEFI, выполняемый до этапа загрузки основной ОС и находящийся в специальном пространстве EBC, созданный с целью получения полного контроля над компьютером пользователя.


Кстати, хочу еще кое-что приметить у UEFI:

- Полная поддержка TCP/IP стека. Да, даже HTTP. UEFI предоставляет нам все необходимые протоколы. Славно, не правда ли? (Можете почитать об UNDI на досуге)

- Одна из интересных особенностей — драйвера носят MZ и PE сигнатуры.

В дальнейшем мы будем основываться на тианкоровской реализации API UEFI, я об edk2.

Конечно, все не так просто.


Как мы можем добраться до поражения UEFI

И мы немного уплываем из UEFI и переносимся в трансцендентность (ыыы).


Из обычного пользователя мы добраться до UEFI никак не сможем. Для того, чтоб добраться для UEFI нам необходимо повысить привилегии каким-либо способом, что нам позволит стать админом. Не думаю, что это удивительно, да и я считаю это тоже очевидным, существуют тысячи реализаций эксплойтов для повышения локальных привилегий, на том же github. Но это не та самая загвоздка.

Настоящей занозой в жопе будет добираться до интерфейса UEFI. Я когда-то выдвигал у себя предположение, что нам необходимо неким образом «заэкслойтиться» так, чтоб мы могли писать в SPI Flash, обходя, допустим, BIOSWE (механизм защиты SPI от записи, если я не ошибаюсь). К сожалению, такие штуки очень платформозависимы. Таким примером может послужить CVE-2017-3197, который затрагивает GB-BSi7H-6500 и GB-BXi7-5775.


Также, теоретически мы можем изменять уже занятые UEFI-переменные, по типу SecureBoot. Для этого, кстати, есть "высокоуровневое" API у Windows: GetEnvironmentVariable и SetEnvironmentVariable. Их также можно дергать из NTAPI.

 


А, ну и никто никогда не отменял атаки на SMRAM.

Однако, мы еще можем поговорить о кое чем еще.


Runtime Services и процесс загрузки интерфейса

Интересной концепцией может послужить атака на SPI Flash во время выгрузки рантайм сервисов интерфейса. Как это происходит? DXE IPL (Driver Execution Environment Initial Program Loader) распаковывает FV_MAIN (содержит в себе сжатые UEFI драйвера), затем DXE Dispatcher диспатчит рантайм драйвера из FV_MAIN в память. Параллельно с этим также работает SMM, который инициализируется в SMRAM, он в принципе занимается тем же самым, что и DXE IPL. Так вот, DXE драйвера отваливаются после загрузки ОС, тем самым остаются только рантайм драйвера (это на самом деле любые драйвера оборудования) и драйвера SMM Core в адрессном пространстве системы. Тем самым, мы технически можем атаковать подобные драйвера из kernel mode (нулевое кольцо). Однако, я не уверен, что это может дать нам запись в SPI, но теоретически может нам дать внесение правок в рантайм драйвера (мое любимое ROP), ну или я тупой немного.


Так что в идеале мы должны искать путь чистой записи в SPI, но это немного не в моих силах уже.


Юмореськи


 

Самое время для моего любимого щитпоста (весь мой контент это по сути щитпост, только тсс).


Однажды я пытался сдампить UEFI, чтоб у меня была хоть какая-то база для сплойтинга. В итоге я убил свой компостер к хуям. Ну, зато батя (мы с ним потели над KSE) поржал, да. Компостер живой, не беспокойтесь, могу даже сфоткать потом.

Вторая юмореська — RWEverything (программа для взаимодействия с подобными низкоуровневыми штуками, как UEFI, SPI и т.д) все еще не видит нормально SPI, а если и видит — начинает выкидывать Access Violation. Я не знаю, что там намудрили инженеры AMD, но у меня сгорела жопа, да и это мемная хуйня получается.


Вывод

Сегодня мы получили представление об UEFI, а также немножечко поговорили, как мы можем добраться до UEFI. Не знаю, дельно это или нет, но я все же решил вынести это в отдельный топик/статью, т.к в следующих частях мы уже окончательно уедем в UEFI и edk.

Да, как я говорил — мы постараемся разобраться с edk2 и что-то на нем сделать, желательно что-то рабочее.

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


BTW, спасибо за потраченное время, увидимся, солнышки!

четверг, 2 декабря 2021 г.

Как мы на Kaspersky в KSE охотились

 

Где-то примерно неделю назад один мой коллега приметил одну замечательную вещь: Kaspersky интересным образом протянул свои ручки к Kernel Shim Engine.

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

Вводные

Начнем с понимания, что это вообще за зверь такой - Kernel Shim Engine.
Технически Kernel Shim Engine (далее KSE) предоставляет "обертки" для драйверов устройств, а также обеспечивает дополнительную поддержку и обработку ошибок драйверов устройств. У него есть своя специальная база данных, мы затронем её позже.

Также стоит поговорить о кое чем еще.

Инициализация шимов

Если говорить в кратце и не вдаваться в подробности работы, то шимы инициализируются на этапе загрузки системы, где-то сразу после того, как инциализировался HAL (Hardware Abstraction Layer) и WMI (Windows Management Instrumentation) с ETW (Event Tracing for Windows). После прогрузки HAL, WMI и ETW вызывается функция KseInitialize(), а после нее KseRegisterShim(). Эти функции официально не документированы, но находятся в некоторых заголовочных файлах WDK.

Инициализация "Built-in" шимов драйверов

В конце инициализации HAL происходит инициализация шима DriverScope, этот шим преоставляется по стандарту системой:

Затем, во время инициализации WMI/ETW также инициализируется функция KseVersionLieInitialize(). Данная функция инициализирует шимы KmWin7VersionLie, KmWin8VersionLie и KmWin81VersionLie (хоть тесты и производились на десятке, но подобный шим с версией "10" я не обнаружил):


Потом вызывается функция KseSkipDriverUnloadInitialize(), которая инициализирует шим SkipDriverUnload.

Функции Касперского в ядре

Окей, вроде +- разобрались. Теперь, какие функции были найдены мной, затрагивающие шимы:

Доступ был только к функции к KseKasperskyInitialize():

Сперва мы решили посмотреть, как поведет себя Касперский при манипуляции с реестром, а конкретнее:

  • HKEY_LOCAL_MACHINE\System\CurrentControlSet\Policies\Microsoft\Compatibility
    С ключами DisableDeviceFlags и DisableDriverShims (REG_DWORD), их значения равны единицы, очевидно по названиям ключей, что они делают при значении 0x01.
  • HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Compatibility
    С ключем DisableFlags (REG_DWORD) со значением 0x01 (отключает функциональность драйвера).

В нашем случае Касперский никак себя не повел, тем более он спокойно дал изменить значения регистров, т.е эти ключи реестра не мониторятся Касперским.

Как поломать KSE

Можно сломать KSE двумя путями: мониторинг драйвера через Verifier и с помощью удаления drvmain.sdb.
Мы вполне имеем право поставить Verifier на драйвер Касперского, ну или удалить сам drvmain.sdb, но скорее всего это вызовет BSOD.

drvmain.sdb

Мы все же решили для начала порыться в drvmain.sdb. Стоит сказать, что до мониторинга и удаления мы не дошли, а почему - вы поймете позже.

drvmain.sdb - стандартная база данных "легальных" драйверов и не только. Помимо драйверов, там также находится информация о шимах и других вещах.
Этот файл находится в "%systemroot%/apppatch/". Файлы с расширением sdb можно открыть, к примеру, через SDBExplorer.

Видим в drvmain вот такую картину:


Да, список драйверов Касперского, ничего удивительного. Можно было бы ставить Verifier на klhk.sys и идти отлавливать шим Касперского, но не все так просто.

KSHIM и почему все печально закончилось

В drvmain, как и упоминалось выше, есть не только перечисление драйверов, а также некоторые шимы.
Представляются они в виде структуры KSHIM, которая по сути является структурой KSE_SHIM, только слегка измененной:

typedef struct _KSE_SHIM {
  _In_ ULONG Size;
  _In_ PGUID ShimGuid;
  _In_ PWCHAR ShimName;
  _Out_ PVOID KseCallbackRoutines;
  _Inopt_ PVOID ShimmedDriverTargetedNotification;
  _Inopt_ PVOID ShimmedDriverUntargetedNotification;
  _In_ PVOID HookCollectionsArray;
} KSE_SHIM, *PKSE_SHIM;

Вот как выглядит KSHIM Касперского:

Заметьте, что поле MODULE несет значение "NT kernel component". А теперь можем посмотреть на ndis шимы:

 

Да, тут поле MODULE со значением "ndis". Думаю, можно догадаться, в чем проблема.

Ага, это значит, что шим Касперского существует в пространстве самого ядра системы. А это значит, что кроме как накидывая Verifier на ядро мы отследить шим Касперского не сможем. Вот, только если накидывать его на ядро, сломается не только Касперский, но и сама система. Прикольно, правда?

Возможный путь отлова шима Касперского

В мою голову лезет только анализ дампа системы. Т.е мы должны завершить систему аварийно, при этом сгенерировав дамп системы, где был запущен Касперский. Команда ".crash" (если не ошибаюсь) из windbg вполне решит эту проблему. Я, правда, не уверен, что мы сможем вытащить что-то дельное, но попытка не пытка, как говорится.


Послесловие и догадки

Теоретически, _KSE_SHIM - пользовательский ввод. Есть смысл посидеть над драйвером ndis.sys (сама структура оттуда). Однако, стучать туда через непривелегированного пользователя мы не сможем, но вполне вероятно, что до туда сможет достать TrustedInstaller.

четверг, 5 августа 2021 г.

Путешествие по недрам системы: анализ дампа, недокументированные NT-функции и причем здесь GDI

 

Мы снова встретились? Хах, неважно.

Я как всегда, пинал сосиски. Прервав мое пинание сосисок, мне написал @vcprocles с вот таким вопросом:

 
 

Вот так, это послужило мотивацией для взятия образа дампа (за что спасибо) и дальнейшего написания этой статьи, хоть и сумбурной слегка. Фактически, эта статья - ответ на вопрос от Procles.

Так как core analyzer решил не жрать дамп, я воспользовался windbg, благо он позволяет пожирать безразмерные дампы (мне дали с размером ~1.5 gb). Можно и побаловаться, открываем windbg и анализируем дамп:


Можно заметить, что у нас не прогрузился PEB, но это не важно. Нас интересуют вот эти 2 строки:

Arg2: 0xfffff8011adfdff2, Address of the instruction which caused the bugcheck
Arg3: 0xffffd601e1ff2920, Address of the context record for the exception that caused the bugcheck

В этом сегменте произошел сбой, предварительно вызвав "bugcheck":

0xc0000005 - "Нарушение прав доступа"

Теперь более-менее понятно, что произошло. Скорее всего, какой-то указатель сослался на неинициализированный сегмент памяти или банально переполнился стек. Мне стало интересно, что творилось в регистрах и что смогло вызвать сбой, поэтому я решил поглазеть на это все:



Если честно, то все по стандарту, вызвался XRSTORS, восстановив состояние x87 из XSAVE и вызвался SwapContext для сохранения вообще всего в _KTHREAD, если не ошибаюсь.

Гораздо полезнее будет посмотреть на поток в момент сбоя:

Вызвались дебаг-функции перед сбоем. Чудно. Также мы видим причину этого сбоя, некий NtUserReleaseDС убил нам систему. В общем, отсюда мы также можем понять, что именно произошло:

Да, проблема с указателями и стеком.. В ядре..

Мне также захотелось заглянуть в черный ящик. BSD и NTFS мне ничего не дали, но PNP выдал вот это:

Не могу сказать, что подключенный носитель мог вызвать сбой, хотя могла произойти какая-то вещь, связанная с носителем, что повлекло за собой BSOD. Procles предположил, что причиной мог стать андервольтинг (т.к вызвался процесс ThrottleStop.exe).

Нырнем!


Немного глубже

Мы уже посмотрели, что происходило в потоке и выяснили, что повлияло на BSOD и процесс, вызвавший это, но есть несколько более "глубоких" вещей, которые стоит рассмотреть.

Все же мы посмотрим еще раз на поток, глубже, чем ранее:

Мне пригодится адресное пространство на момент сбоя, я выделил его. Мне было интересно, что происходило не только с функциями, но и с драйверами. К сожалению или к счастью, драйвера вообще не упоминались в сегменте, но обнаружилось несколько интересных вещей:

Хардварная функция с 1 битом


 Подозрительное прерывание

 

Тоже прерывание. Об этой функции интернетам ничего не известно


Все это принадлежит HAL (Hardware Abstraction Layer), что возможно имеет отношение к подключенному носителю во время сбоя, тот самый, что выдал мне PNP.

Но это также двояко. С одной стороны мог повлиять андервольтинг, а с другой внутренняя ошибка.

Если честно, то я немного в тупике. Я проанализировал также процесс (ThrottleStop.exe) на наличие других аномальных вещей, но особо ничего не нашел. Возможно, на вопросы ответит NtUserReleaseDС, стоит проверить.


Недокументированные NT-функции. Ресёрч

Стоит сказать, что у Винды есть два основных API: WinApi (aka Win32) и NTAPI. Если первый документирован и функции предельно понятны, то второй таковым не является, он почти весь является недокументированным и в некоторой степени устаревшим, эдакий атавизм, да. Но все же есть несколько сайтов, предоставляющие нам немногочисленную документацию по некоторым функциям.

NtUserReleaseDС как раз пришел к нам из NTAPI и входит в разряд недокументированных, поэтому будем рыться в чем есть.

Я первым делом сразу полез в исходники ReactOS, чтоб посмотреть, что данная функция делает, но забавно, что от нее избавились еще в далеком 2007:

https://reactos.org/pipermail/ros-diffs/2007-August/018150.html

Да, ничего нам оно не даст. Но я обнаружил интересную ссылку, где содержалась искомая функция: https://systemroot.gitee.io/pages/apiexplorer/d7/d9/usercli_8h-source.html. Ииии, да.. Это обертка другой функции:

#define NtUserReleaseDC(hwnd,hdc)  NtUserCallOneParam((ULONG_PTR)(hdc), SFI__RELEASEDC)

Что же, придется что-то делать. Благо, функция была в ReactOS и тут мы уже можем от чего-либо отталкиваться: https://doxygen.reactos.org/d6/dee/simplecall_8c_source.html (строка 145). Стоит сказать, что там есть парочка неаккуратных мест, где возможна ошибка, по типу:

psmwp->acvr = ExAllocatePoolWithTag(PagedPool, count * sizeof(CVR), USERTAG_SWP);

Или:

Result = (DWORD_PTR)DesktopHeapAddressToUser((PVOID)Param);

Подобное разыменовывание или "неаккуратная" аллокация вполне могла быть причиной краша системы.


Причем здесь GDI?

GDI - это компонент, составляющий пользовательский интерфейс. Он не отвечает за построение окон или меню-баров. Его дело - отображение шрифтов, работа с палитрами и прочим.

Так причем GDI? Дело в том, что при поиске ответа что из себя представляет NtUserReleaseDС я все время косвенно сталкивался с топиками по GDI. И знаете... Все оказалось намного проще когда я убрал приставку "Nt":



Да, то, что мы искали - нативная функция, судя по всему зарезервированная Виндой. В это же время она имеет свой прототип в WinApi. Как-то так.


Так какая истинная причина ошибки?

Не был освобожден "контекст устройства" (DC) для передачи следующему приложению, что повлекло за собой неожиданные последствия.


Вот такие пирожки с котятами. Будьте здоровы и не стреляйте себе в колена указателями.


~slowdeath.

==========

Вы всегда можете купить мне сигареты:

4890 4947 2029 0345 (qiwi)

I was visited by death again: UEFI руткиты в схемах и мемах, поговорим о заражении

  Спустя, наверное, продолжительное время, я потихоньку собираюсь с силами (мне просто надоело залипать в Krita), что не может меня не радов...