- Статус
- Оффлайн
- Регистрация
- 28 Авг 2022
- Сообщения
- 90
- Реакции
- 110
В последнее время (эдак год-полтора) на чит-сцене появилось несколько публичных решений для SMM (System Management Mode). Эти решения как «боевые», так и чисто исследовательские. В основном тема эксплуатации SMM с точки зрения читов обсуждается на англоязычных форумах, но на СНГ сцене как-то по этому поводу тихо.
Я решил, что будет неплохой идеей немного осветить тему, как вообще вся эта шняга работает и как её можно использовать с точки зрения читов, ну или собственных исследований по безопасности платформы. Я в принципе занимаюсь этой темой весьма долго и мне есть что рассказать. Я по большей части буду делать отсылки к AMD, но при необходимости постараюсь впихнуть и Intel. С последним я работал намного меньше, чем с первым, отсюда и больший уклон в красных. Да и примерчики тоже на красных будут.
Также, стоит сказать; Я подразумеваю, что вы уже знакомы с устройством как минимум UEFI, работы процессора и некоторых других вещей. Совсем базовые концепции я детально разбирать не буду.
Режим работает независимо от любых других режимов, и, по сути, является прозрачным для всех остальных (а также наиболее привилегированным). Часто можно увидеть упоминание «Ring -2» (в топологии колец защит), что и есть SMM.
Код режима SMM защищён от модификаций извне (любой другой режим, кроме RoT (Root-of-Trust: Intel ME, AMD SP (AMD PSP))). Очевидно, что SMM-код должен как-то защищаться, ну и соответственно сперва он должен быть инициализирован.
Отлично, теперь нам стало понятнее, когда загружается код режима SMM. Но тут есть как минимум ещё один вопрос: куда он загружается?
Вообще, ещё на этапе ранней инициализации, UEFI конфигурирует два MSR:
Что находится внутри SMRAM?
SMRAM имеет свою определённую «планировку» внутри, в зависимости от количества ядер процессора, мы можем увидеть следующее (на примере AMD):
Теперь, подробнее разберём, что вообще здесь творится:
SMM драйвера также работают изолировано в своём адресном пространстве, но, технически, так как SMRAM может достигать размера 4 гигабайт, то и сами драйвера могут тоже обращаться к адресам до 4 гигабайт, но, вообще, нынче вводятся некоторые изменения в эти особенности. Однако, дальше это может нам понадобиться.
Мы уже коснулись этой темы, но теперь пройдёмся по ней глубже. Для входа в режим требуется срабатывание SMI (System Management Interrupt) прерывания. Есть несколько способов триггера прерывания, мы разберём все из них. Первым будут прерывания от чипсета или периферии на материнской плате. Это чисто аппаратные прерывания, которые триггерят отдельный пин на самом чипсете. Зачастую он назван весьма прямолинейно:
Такие прерывания можно триггерить через периферию, например, от xHCI. В принципе, варианты есть, надо только найти их.
Вторым типом прерываний будут те, для которых по адресу записи ввода/вывода на который платформой установлена необходимость триггера SMI (иногда можно услышать название IO Trap).
Третьим же будет прерывания, которые срабатывают при записи в определённый порт ввода/вывода, зачастую это
Стоит также помнить, что SMI являются одними из самых привилегированных прерываний, что их нельзя «проигнорировать» (cli), и что процессор обработает их в первую очередь (например, если одновременно поступили SMI и NMI прерывания – обработается сначала первое). Выход из режима происходит только по достижению инструкции RSM (Resume From System Management Mode). Инструкция возвращает поток управления в процедуру, которая была прервана SMI, предварительно вернув состояние процессора до триггера SMI.
Well, запись в порт
По выполнению работы обработчика прерывания, ядро процессора восстановит своё состояние, прочитав из SMM Save State, поток управления достигнет инструкции RSM, и, ядро продолжит своё выполнение после инструкции
Тут может встать вопрос: «А что, если обращение к зарезервированным адресам TSEG произойдёт без SMI?». Ну, вообще на этот вопрос есть весьма простой ответ. Например, у AMD, если приглядеться на одну из картинок выше, в
А значит, мы увидим следующую картину:
Это был пример для AMD, но и для Intel это вроде бы тоже справедливо (если я правильно помню), думаю, что пример равнозначен для двух производителей процессоров.
Надо сказать, что это только про софтварные прерывания (SW SMI), есть немного более сложный алгоритм срабатывания для ACPI SMI (мы их тоже затронем), например. Но, в конечном итоге всё действительно сводится к записи в порт, триггера SMI# пина на чипсете или запись в специальные адреса ввода/вывода. Мы немного познакомились с SMM и его работой, а значит, время для демагогий.
Те же античиты, например, если и интересуются режимом SMM (а в последнее время они реально интересуются, по крайней мере, их разработчики), то делают это «слабо» на текущий момент. Например, можно в тупую просто мониторить счётчик прерываний (что автоматически подразумевает большое количество ложных срабатываний) или же пытаться вычислить «специфичные» участки памяти, используемые, как коммуникация между режимами. Их вычисление тоже не самая простая задача, да и также может генерировать огромное количество ложных срабатываний. На текущий момент остаётся только глядеть маппнутый образ SPI Flash в физической памяти, сопоставлять каждый GUID драйвера (если вы незнакомы с устройством прошивок, то каждый драйвер имеет свой GUID), в надежде, что появится какой-то неизвестный идентификатор. Это, может и действенно, но кто будет держать весь этот список говна? Да и что делать, если вендоры введут какой-то новый драйвер? Собственно, на текущий момент с детектом SMM драйверов всё немного сложно. Но это не значит, что защитные решения стоят на месте.
Единственная проблема, которая может быть – вендор. Intel и AMD уже давно ввели и вводят некоторые штуки, предназначенные для защиты прошивки и целостности платформы от сторонних драйверов. О них речь пойдёт ближе к концу статьи.
В качестве библиотеки я использую реализацию
Хорошо, что должен сделать наш драйвер? В первую очередь, он должен проверить, находится ли он в SMM (параноидально, но так принято, например, для комбинированных DXE-SMM драйверов), затем, получить таблицу SMM сервисов, а потом зарегистрировать SMI обработчик. Собственно, на картинке ниже это и представлено:
В спецификации UEFI есть
Окей, мы получили таблицу сервисов SMM, теперь же, мы зарегистрируем обработчик SW SMI. Делается это через
Отлично! У нас есть зарегистрированный обработчик, который будет доступен по записи в порт
У меня есть дамп с SPI чипа своего старого ноутбука, мы ещё вернёмся к аппаратной составляющей, так что пока просто держим это в голове.
Драйвер интересен тем, что регистрирует протокол с адресом буфера для коммуникации между PSP и внешним миром. Коммуницировать с PSP можно, конечно, по-разному, но AMD сделало это более удобным способом. Протокол существует исключительно в SMM, поэтому, почему бы не получить его адрес?
Обработчик должен получить адрес протокола
Кстати, SW SMI обработчикам важно, какое ядро находится в SMM. Обработчики имеют одинаковый набор аргументов, но в случае с SW SMI всё немного интереснее. Если вы взгляните на код, то увидите аргумент
Ну, мы написали небольшой SMM драйвер. Теперь нам надо с ним как-то общаться, правда ведь? Напишем теперь DXE драйвер, который триггернёт прерывание и выплюнет нам адрес протокола.
Да собственно тут нечего особо писать. Нам пригодится немножечко ассемблер и прямые руки, но для начала, точка входа:
Теперь, надо триггернуть порт
Объявим прототип в отдельном заголовке, модифицируем входную точку DXE драйвера:
Теперь мы можем общаться с SMM драйвером. Переходя к этапу сборки, нам нужно будет написать пару конфигов, но можно воспользоваться существующими в EDKII, нащупать самостоятельно что нужно. Останется только отредактируовать конфиг в
P.S: Вероятнее всего, ещё придётся пошаманить с
Это чип от GigaDevice формата SOIC8/SOP8. В качестве программатора у меня xGecu T48, но можно использовать любой другой. В качестве адаптера для чипа я буду «прищепку», она как раз подходит для зажима пинов SOIC8/SOP8:
Здесь нужно не ошибиться с расположением пинов, да и с самим вендором чипа и его вольтажом. Благо, софтина от xGecu умеет в самостоятельное определение по айдишнику. У меня уже есть дамп образа прошивки с чипа, вам же сначала нужно будет его прочитать и сохранить куда-нибудь. Теперь переходим в
После скачивания тулзы, можно приступить к поиску жертвы на замену. В моём чипе лежал дебажный SMM драйвер для SMBus, поэтому я могу смело заменить его целиком. Вам же, возможно, потребуется дальнейший поиск или модификация существующих SMM драйверов. Всё что нам остаётся сделать – заменить драйвер на наш:
После замены сохраним бинарь с каким-нибудь другим именем, а дальше прошьём наш чип:
Нам останется только найти любую USB флешку, форматнуть в FAT32, закинуть бинарь с EFI Shell (можно собрать или найти самому), закинуть наш DXE драйвер и идти смотреть, что вообще происходит. Как и ожидалось – SMM драйвер выплюнул нам адрес протокола (извиняюсь за классные фотки):
Можно убедиться, что адрес действительно лежит в SMRAM, для этого нам достаточно проверить
Круто, у нас есть свой первый рабочий драйвер! Но ведь это можно развить во что-то большее, ведь так? Да и как отлаживать всё это дело? Об этом мы и поговорим сейчас.
Отладка SMM драйверов
Несмотря на предоставление EDKII эмулятора (
В качестве системы для отладки можно использовать, например, QEMU. EDKII предоставляет
Вендоры используют такую штуку, как JTAG, аппаратный интерфейс для отладки. Иногда также используются специальные отладочные платы (по рассказам некоторых знакомых – такое бывает). Угарнее становится тогда, когда ты можешь нащупать JTAG-пины на «продуктовых» платах, но чаще всего это обрубки от некогда существовавшего интерфейса:
У Intel есть такая штука, как DCI, по сути, модифицировав прошивку (DCI нужно включать самостоятельно), и сделав самопальный DCI шнур из USB – уже можно пробовать что-то делать. По крайней мере, когда-то можно было, сейчас не знаю. Ещё Intel предоставлял вот такие прекрасные вещи:
Yep, эта беспонтовая херня будет вам стоить дохера, а ещё вам придётся подписывать NDA.
Собственно, самым простым вариантом для самопальных исследований будет QEMU (я опущу процессорный БДСМ с замерами таймингами старта чипсета для определения возможных отладочных интерфейсов).
Для его регистрации отдельный протокол не используется, используется только
Как же нам коммуницировать с этим обработчиком? Теперь вернёмся к DXE драйверу. Спецификация предоставляет три версии протокола для коммуникации, мы воспользуемся
Таким образом мы можем свободно коммуницировать с SMM посредством ACPI SMI обработчика! Теперь разберёмся с тем, как это работает. Помните SMM Core, который присутствует на карте SMRAM выше? Так вооооооот…
Протокол для коммуникации на самом деле отправит нас
Наверное, это всё, что касается ACPI SMI..
SMM «в оригинале» работает в реальном режиме, а значит, для адресации ему доступны только первые 4 гигабайта физической памяти. Это большая проблема для x64 систем, в особенности для адресов расположенных выше 4 гигабайт. У нас есть два решения:
Не вдаваясь глубоко в суть вопроса (это действительно отдельная большая тема) – первый сперва находит в SPI чипе отдельный модуль, который верифицирует стартовую часть прошивки, где находится основной модуль, который проверифицирует основную часть прошивки. Вот такая цепь доверенной загрузки.
AMD PSB работает схожим образом, но немного интереснее. Если IBG в случае «правильной» конфигурации, записанной в FPF, просто не загрузит платформу, то PSB, судя по всему, не загрузит модифицированный (или заменённый) драйвер (судя по моим тестам).
Также, уже на новых чипсетах Intel есть SMM Isolation, который ломает приколы с адресацией в SMM, вот
Ну, вот и краткое описание того, что представляет из себя SMM, как под него писать, куда можно ориентироваться при разработке смешных штук. Возможно, это кому-то пригодится или хотя бы заинтересует.
В идеале, если нацеливаться на игоры – смотреть на SMI# пин чипсета, как он триггерится и при каких событиях, я упоминал тот же xHCI, с чего, в принципе, можно и начать. Меньше оставляешь следов – живёшь дольше.
Полезные материалы
-
- Спецификации
- Спецификации
Я решил, что будет неплохой идеей немного осветить тему, как вообще вся эта шняга работает и как её можно использовать с точки зрения читов, ну или собственных исследований по безопасности платформы. Я в принципе занимаюсь этой темой весьма долго и мне есть что рассказать. Я по большей части буду делать отсылки к AMD, но при необходимости постараюсь впихнуть и Intel. С последним я работал намного меньше, чем с первым, отсюда и больший уклон в красных. Да и примерчики тоже на красных будут.
Также, стоит сказать; Я подразумеваю, что вы уже знакомы с устройством как минимум UEFI, работы процессора и некоторых других вещей. Совсем базовые концепции я детально разбирать не буду.
SMM как режим работы процессора
Для начала стоит просто понять, что из себя подразумевает SMM – это привилегированный режим работы процессора на базе архитектуры x86_64, управляющий аппаратным обеспечением ПК. Более детально, режим работает, например, с управлением энергопотребления платформы, ну или исполняет управляющий OEM-код (ref: AMD SP (AMD PSP), Intel ME), эмулирует какие-нибудь устаревшие вещи, обрабатывает ошибки чипсета или памяти.
Режим работает независимо от любых других режимов, и, по сути, является прозрачным для всех остальных (а также наиболее привилегированным). Часто можно увидеть упоминание «Ring -2» (в топологии колец защит), что и есть SMM.
Инициализация режима и где обитает код для SMM
Очевидно, что SMM код должен инициализироваться где-то в начале работы платформы, когда уже инициализированы и сконфигурированы чипсет и память, была пройдена первичная валидация образов драйверов. Собственно, так оно и есть: платформа инициализирует SMM код ещё на стадии загрузки. DXE Dispatcher получает HOB (Hand-off Block) с SMM драйверами, где в первую очередь находится SMM IPL (SMM Initial Program Loader), который загружает остальные SMM драйвера. Этот же код будет доступен на протяжении всей работы платформы, до подачи сигнала на определённый пин (CPURST#) чипсета. Попроще изображено на иллюстрации ниже:
Вообще, ещё на этапе ранней инициализации, UEFI конфигурирует два MSR:
SMM_ADDR и SMM_MASK для AMD, а для Intel – SMMR_PHYS_BASE и SMMR_PHYS_MASK. Комбинация двух регистров устанавливает определённый сегмент DRAM, называемый TSEG, в который входит SMRAM – память под код SMM. Этот же сегмент памяти будет защищён от модификаций извне, а также «невидим» для других режимов. Сам сегмент может быть весьма большим, в зависимости от настроек платформы, например, 16 мегабайт и даже больше. Защита сегмента также настраивается отдельными конфигурационными битами. Например, у AMD это SMMLock в HWCR.
Что находится внутри SMRAM?
- SMM Base – это базовый адрес SMM для конкретного ядра процессора, на его основе считаются следующие две вещи;
- SMM Entrypoint – входная точка для SMM, устанавливается для каждого ядра, на них же переходит управление при срабатывании SMI прерываний. Это весьма забавная цепочка из переходов: Real Mode -> Protected Mode -> Long Mode, а только потом начнёт выполняться код SMI обработчика;
- SMM Save State – сохранённое состояние всех регистров процессора перед входом в SMM;
- SMM Core – «пограничный» драйвер, обрабатывает прерывания (определённые), регистрирует и предоставляет таблицу сервисов SMM. Об этом драйвере речь зайдёт позже;
- SMI Handlers – обработчики SMI прерываний, о прерываниях мы поговорим в следующей главе.
SMM драйвера также работают изолировано в своём адресном пространстве, но, технически, так как SMRAM может достигать размера 4 гигабайт, то и сами драйвера могут тоже обращаться к адресам до 4 гигабайт, но, вообще, нынче вводятся некоторые изменения в эти особенности. Однако, дальше это может нам понадобиться.
Вход в SMM и выход из него
Мы уже коснулись этой темы, но теперь пройдёмся по ней глубже. Для входа в режим требуется срабатывание SMI (System Management Interrupt) прерывания. Есть несколько способов триггера прерывания, мы разберём все из них. Первым будут прерывания от чипсета или периферии на материнской плате. Это чисто аппаратные прерывания, которые триггерят отдельный пин на самом чипсете. Зачастую он назван весьма прямолинейно:
Вторым типом прерываний будут те, для которых по адресу записи ввода/вывода на который платформой установлена необходимость триггера SMI (иногда можно услышать название IO Trap).
Третьим же будет прерывания, которые срабатывают при записи в определённый порт ввода/вывода, зачастую это
0xB2, но на всякий случай можно проверить поле SmiCmd в Fixed ACPI Description Table таблице ACPI.Стоит также помнить, что SMI являются одними из самых привилегированных прерываний, что их нельзя «проигнорировать» (cli), и что процессор обработает их в первую очередь (например, если одновременно поступили SMI и NMI прерывания – обработается сначала первое). Выход из режима происходит только по достижению инструкции RSM (Resume From System Management Mode). Инструкция возвращает поток управления в процедуру, которая была прервана SMI, предварительно вернув состояние процессора до триггера SMI.
Разбираемся на примере
Допустим, пусть Core#0 выполняет какой-нибудь код. На пути выполнения пусть будет инструкция
out 0xB2, 0x41:
0xB2 сигнализирует чипсету, что поток данных и инструкций переходит в SMM, а значит, время проверить, какие адреса зарезервированы TSEG. Во время перехода, конечно же, сохраняется состояние регистров до триггера SMI (в SMM Save State). Таким образом, поток данных и инструкций переходят в SMM:
out.Тут может встать вопрос: «А что, если обращение к зарезервированным адресам TSEG произойдёт без SMI?». Ну, вообще на этот вопрос есть весьма простой ответ. Например, у AMD, если приглядеться на одну из картинок выше, в
SMM_MASK MSR есть биты TE и AE (ASEG нам не особо интересен, поэтому, я и не упоминаю его). Если эти биты валидны (равны 1), и обращение происходит не в контексте прерывания, то, ядро процессора просто обратится к MMIO:
Надо сказать, что это только про софтварные прерывания (SW SMI), есть немного более сложный алгоритм срабатывания для ACPI SMI (мы их тоже затронем), например. Но, в конечном итоге всё действительно сводится к записи в порт, триггера SMI# пина на чипсете или запись в специальные адреса ввода/вывода. Мы немного познакомились с SMM и его работой, а значит, время для демагогий.
SMM как вектор атаки на игрушки
Чисто технически, SMM это весьма интересный вектор атаки с точки зрения защитных решений – режим, прозрачно работающий для ОС, который никто не может нормально проверить на работу каких-либо приколов в нём (на самом деле может, но об этом позже).
Те же античиты, например, если и интересуются режимом SMM (а в последнее время они реально интересуются, по крайней мере, их разработчики), то делают это «слабо» на текущий момент. Например, можно в тупую просто мониторить счётчик прерываний (что автоматически подразумевает большое количество ложных срабатываний) или же пытаться вычислить «специфичные» участки памяти, используемые, как коммуникация между режимами. Их вычисление тоже не самая простая задача, да и также может генерировать огромное количество ложных срабатываний. На текущий момент остаётся только глядеть маппнутый образ SPI Flash в физической памяти, сопоставлять каждый GUID драйвера (если вы незнакомы с устройством прошивок, то каждый драйвер имеет свой GUID), в надежде, что появится какой-то неизвестный идентификатор. Это, может и действенно, но кто будет держать весь этот список говна? Да и что делать, если вендоры введут какой-то новый драйвер? Собственно, на текущий момент с детектом SMM драйверов всё немного сложно. Но это не значит, что защитные решения стоят на месте.
Единственная проблема, которая может быть – вендор. Intel и AMD уже давно ввели и вводят некоторые штуки, предназначенные для защиты прошивки и целостности платформы от сторонних драйверов. О них речь пойдёт ближе к концу статьи.
Пишем простой SMM драйвер
Наверное, стоит написать что-нибудь простенькое и забавное. Напишем драйвер, регистрирующий SMI обработчик. Пока это будет софтварный (SW SMI) обработчик, но позже мы познакомимся с ещё одним.
В качестве библиотеки я использую реализацию
Пожалуйста, авторизуйтесь для просмотра ссылки.
(есть ещё GNU-EFI, но это объективно кал). Так как я ещё и особенный, в качестве среды разработки у меня Visual Studio, я использую базу в виде
Пожалуйста, авторизуйтесь для просмотра ссылки.
(правда, модифицированную под свои нужды, но вам будет достаточно собрать ещё пару библиотек при желании). Собирать мы свой драйвер будем через тулчейн EDKII, так что, я дополнительно покажу ещё и настройку конфигурационных файлов.Хорошо, что должен сделать наш драйвер? В первую очередь, он должен проверить, находится ли он в SMM (параноидально, но так принято, например, для комбинированных DXE-SMM драйверов), затем, получить таблицу SMM сервисов, а потом зарегистрировать SMI обработчик. Собственно, на картинке ниже это и представлено:
Пожалуйста, авторизуйтесь для просмотра ссылки.
EFI_SMM_BASE2_PROTOCOL, который, предоставляет сервис для проверки, запущены ли мы в SMM, а также предоставляет сервис для получения
Пожалуйста, авторизуйтесь для просмотра ссылки.
EFI_SMM_SYSTEM_TABLE2 (извините за тавтологию). Получить указатель на протокол EFI_SMM_BASE2_PROTOCOL можно через
Пожалуйста, авторизуйтесь для просмотра ссылки.
LocateProtocol из
Пожалуйста, авторизуйтесь для просмотра ссылки.
EFI_BOOT_SERVICES. Отлично! Теперь, напишем код:
SMM Driver EP:
EFI_SMM_SYSTEM_TABLE2 *Smst = NULL;
EFI_STATUS
EFIAPI
SmmMain(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
) {
EFI_SMM_BASE2_PROTOCOL *SmmBase2 = NULL;
EFI_STATUS Status = SystemTable->BootServices->LocateProtocol(&gEfiSmmBase2ProtocolGuid, NULL, (VOID **)&SmmBase2);
if(EFI_ERROR(Status))
return Status;
BOOLEAN InMmRam = FALSE;
SmmBase2->InSmm(SmmBase2, &InMmRam);
if(!EFI_ERROR(Status) && InMmRam) {
Status = SmmBase2->GetSmstLocation(SmmBase2, Smst);
if(EFI_ERROR(Status))
return Status;
} else {
return EFI_DEVICE_ERROR;
}
return EFI_SUCCESS;
}
Окей, мы получили таблицу сервисов SMM, теперь же, мы зарегистрируем обработчик SW SMI. Делается это через
Пожалуйста, авторизуйтесь для просмотра ссылки.
EFI_SMM_SW_DISPATCH2_PROTOCOL. Обработчику будет нужен собственный номер, пусть это будет 0x99. Для регистрации обработчика нужно передать адрес используемой функции в качестве этого обработчика:
Register SW SMI Handler:
EFI_STATUS
EFIAPI
SwSmiHandler(
IN EFI_HANDLE DispatchHandle,
IN CONST VOID *Context OPTIONAL,
IN OUT VOID *CommBuffer OPTIONAL,
IN OUT UINTN *CommBufferSize OPTIONAL
) {
return EFI_SUCCESS;
}
EFI_STATUS
EFIAPI
RegisterSwSmi(
VOID
) {
EFI_SMM_SW_DISPATCH2_PROTOCOL *SwDispatch2 = NULL;
EFI_STATUS Status = gSmst->SmmLocateProtocol(&gEfiSmmSwDispatch2ProtocolGuid, NULL, (VOID **)&SwDispatch2);
if(EFI_ERROR(Status))
return Status;
EFI_SMM_SW_REGISTER_CONTEXT SwSmiRegCtx = { 0 };
SwSmiRegCtx.SwSmiInputValue = 0x99;
EFI_HANDLE Handler = NULL;
return SwDispatch2->Register(SwDispatch2, SwSmiHandler, &SwSmiRegCtx, &Handler);
}
Отлично! У нас есть зарегистрированный обработчик, который будет доступен по записи в порт
0xB2 значения 0x99. Теперь, можно что-нибудь придумать для своего обработчика. Моей жертвой станет ещё один SMM драйвер от AMD – AmdPspP2CmboxV2. Это драйвер для коммуникации между внешним миром и сопроцессором AMD PSP, расположенным на чипсете, вот тут видно:
PSP_MBOX_SMM_BUFFER_ADDRESS_PROTOCOL и отдать его нам обратно. Как обработчик его вернёт? Ну, для этого мы будем использовать SMM Save State, в который ядро процессора сохраняет своё состояние. Это делается через ещё один
Пожалуйста, авторизуйтесь для просмотра ссылки.
– EFI_SMM_CPU_SAVE_STATE_PROTOCOL. Кешируем последний, а первый получим прям в обработчике. В качестве регистра, в который мы положим результат, будет использоваться r15. Нам также нужно будет извлечь GUID нужного нам протокола.Кстати, SW SMI обработчикам важно, какое ядро находится в SMM. Обработчики имеют одинаковый набор аргументов, но в случае с SW SMI всё немного интереснее. Если вы взгляните на код, то увидите аргумент
CommBuffer. В случае триггера SW SMI, туда будет передана
Пожалуйста, авторизуйтесь для просмотра ссылки.
EFI_SMM_SW_CONTEXT, в которой будет индекс ядра процессора, перешедшего в SMM. Это важно помнить.
Modified Handler:
typedef struct _PSP_MBOX_SMM_BUFFER_ADDRESS_PROTOCOL {
EFI_PHYSICAL_ADDRESS PspMboxSmmBuffer;
UINT64 PspMboxSmmFlagAddr;
} PSP_MBOX_SMM_BUFFER_ADDRESS_PROTOCOL;
EFI_STATUS
EFIAPI
SwSmiHandler(
IN EFI_HANDLE DispatchHandle,
IN CONST VOID *Context OPTIONAL,
IN OUT VOID *CommBuffer OPTIONAL,
IN OUT UINTN *CommBufferSize OPTIONAL
) {
EFI_SMM_SW_CONTEXT *SwCtx = (EFI_SMM_SW_CONTEXT *)CommBuffer;
UINTN CpuIdx = SwCtx->SwSmiCpuIndex;
EFI_GUID PspCmboxProtocolGuid = { 0x579CB2CB, 0x3403, 0x4B26, { 0x84, 0xCD, 0x72, 0x89, 0xFC, 0x91, 0x4D, 0x35 } };
PSP_MBOX_SMM_BUFFER_ADDRESS_PROTOCOL *PspMbox = NULL;
EFI_STATUS Status = gSmst->SmmLocateProtocol(&PspCmboxProtocolGuid, NULL, (VOID **)&PspMbox);
if(EFI_ERROR(Status))
return Status;
// ignore last status, no need to execute another handlers
gSmmCpu->WriteSaveState(gSmmCpu, sizeof(EFI_PHYSICAL_ADDRESS), EFI_MM_SAVE_STATE_REGISTER_R15, CpuIdx, (VOID **)&PspMbox);
return EFI_SUCCESS;
}
Ну, мы написали небольшой SMM драйвер. Теперь нам надо с ним как-то общаться, правда ведь? Напишем теперь DXE драйвер, который триггернёт прерывание и выплюнет нам адрес протокола.
Пишем DXE драйвер
Да собственно тут нечего особо писать. Нам пригодится немножечко ассемблер и прямые руки, но для начала, точка входа:
DXE Driver EP:
#include <Uefi.h>
#include <Base.h>
#include <Library/BaseLib.h>
#include <Library/UefiLib.h>
EFI_STATUS
EFIAPI
DxeMain(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
) {
Print(L"[ + ] Triggering 0xB2 port\n");
return EFI_SUCCESS;
}
Теперь, надо триггернуть порт
0xB2 с номером нашего обработчика. Я особенный, поэтому напишем небольшой код на MASM:
SW SMI Port Write:
PUBLIC _swsmi_trigger
.code
;
; Prototype: VOID _swsmi_trigger(EFI_PHYSICAL_ADDRESS *Output)
;
_swsmi_trigger PROC PUBLIC
push rcx
xor rax, rax
mov rax, 099h
out 0B2h, ax
pop rcx
mov qword ptr[rcx], r15
ret
_swsmi_trigger ENDP
END
Объявим прототип в отдельном заголовке, модифицируем входную точку DXE драйвера:
Modified DXE EP:
EFI_STATUS
EFIAPI
DxeMain(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
) {
Print(L"[ + ] Triggering 0xB2 port\n");
EFI_PHYSICAL_ADDRESS PspMboxBuf = 0;
_swsmi_trigger(&PspMboxBuf);
Print(L"[ + ] PSP MBOX Communication buffer: 0x%llX\n", PspMboxBuf);
return EFI_SUCCESS;
}
Теперь мы можем общаться с SMM драйвером. Переходя к этапу сборки, нам нужно будет написать пару конфигов, но можно воспользоваться существующими в EDKII, нащупать самостоятельно что нужно. Останется только отредактируовать конфиг в
Conf/target.txt. В ACTIVE_PLATFORM прописать путь до исходника драйверов, в TARGET прописать условный RELEASE, а в TARGET_ARCH прописать X64. После этого можно билдить.P.S: Вероятнее всего, ещё придётся пошаманить с
Conf/tools_def.txt, в частности, вырубить параметр «предупреждения как ошибки» и немного побаловаться с MASM командами.Модификация содержимого SPI чипа
Далее мы проведём тесты непосредственно на хардвари, так как драйвер весьма простой и ничего нам не сломает. Но я всё равно не несу ответственности за сохранность вашего оборудования. Мы позже поговорим об отладке SMM драйверов и почему это весьма больная тема. А пока, я выбрал своего подопытного – это мой старенький ноутбук, года эдак 2019, если не раньше. В первую очередь нам нужно нащупать SPI чип. На текущий момент на платах все флешки 8-пиновые и бывают двух форм-факторов: SOIC8/SOP8 и WSON8 (различия можно нагуглить). Зная, как они выглядят, можно начинать брут-форсить плату в поиске чипа. Желательно ещё обращать на маркировки чипов, чаще всего это будет какой-нибудь Winbond, Macronix или GigaDevice. С освещением у меня всё плохо, извиняйте:
Пожалуйста, авторизуйтесь для просмотра ссылки.
(лучше всего версии 0.28.0). Он нам нужен на старом движке, так как в новом нет поддержки модификации образов.После скачивания тулзы, можно приступить к поиску жертвы на замену. В моём чипе лежал дебажный SMM драйвер для SMBus, поэтому я могу смело заменить его целиком. Вам же, возможно, потребуется дальнейший поиск или модификация существующих SMM драйверов. Всё что нам остаётся сделать – заменить драйвер на наш:
SMM_ADDR и SMM_MASK MSR, из SMM_MASK мы вычислим размер SMRAM:
Отладка SMM драйверов
Несмотря на предоставление EDKII эмулятора (
Пожалуйста, авторизуйтесь для просмотра ссылки.
) – он всё же существует для отладки DXE драйверов, в то время как SMM драйвера требуют другого подхода.В качестве системы для отладки можно использовать, например, QEMU. EDKII предоставляет
Пожалуйста, авторизуйтесь для просмотра ссылки.
– пакет с драйверами прошивки для QEMU. В случае выбора этого варианта, нужно будет просто закинуть наши драйвера в OvmfPkg, подредактировать конфиги и собрать пакет, затем явно указать QEMU, что он должен грузиться именно с него. Есть множество плюсов такого подхода, например:- Не нужно покупать дополнительное оборудование;
- Можно отправлять отладочные сообщения в Serial I/O порты;
- QEMU предоставляет GDB Stub;
- С пинка можно добиться отладки на уровне сурсов.
Вендоры используют такую штуку, как JTAG, аппаратный интерфейс для отладки. Иногда также используются специальные отладочные платы (по рассказам некоторых знакомых – такое бывает). Угарнее становится тогда, когда ты можешь нащупать JTAG-пины на «продуктовых» платах, но чаще всего это обрубки от некогда существовавшего интерфейса:
Собственно, самым простым вариантом для самопальных исследований будет QEMU (я опущу процессорный БДСМ с замерами таймингами старта чипсета для определения возможных отладочных интерфейсов).
Развиваем концепцию во что-то большее
Коммуницировать посредством SMM Save State и аутично записывать в порт номер своего обработчика безусловно хорошо, но что, если мы хотим передавать относительно большие массивы данных? Окей, для начала познакомимся с ещё несколькими типами SMI:
- Root SMI – это «корневые» прерывания, которые срабатывают при каждом SMI. То есть, если произошёл триггер по записи в порт – Root SMI тоже сработает;
- ACPI SMI – из названия не сложно догадаться, для чего они сделаны. Они имеют весьма удобный метод для коммуникации с внешним миром;
- Periodic SMI – это прерывания, срабатывающие по определённому тайм-ауту, заданным APIC. Они требуют большего контроля, так как при загрузке системы APIC перенастраивается.
ACPI SMI как удобный метод коммуникации
Как и любые SMI обработчики – этот также регистрируется в SMM, только для него нужно указывать собственный GUID при регистрации. Собственно, вернёмся к нашему SMM драйверу и перепишем его:
SMM Driver with ACPI SMI Handler:
EFI_STATUS
EFIAPI
AcpiSmiHandler(
IN EFI_HANDLE DispatchHandle,
IN CONST VOID *Context OPTIONAL,
IN OUT VOID *CommBuffer OPTIONAL,
IN OUT UINTN *CommBufferSize OPTIONAL
) {
return EFI_SUCCESS;
}
EFI_STATUS
EFIAPI
RegisterAcpiSmi(
VOID
) {
EFI_HANDLE Handler = NULL;
EFI_GUID AcpiSmiHandlerGuid = { 0xA3715FCB, 0x05F9, 0x4FD6, { 0xA3, 0x5B, 0x31, 0x2F, 0x29, 0xD5, 0x08, 0xA3 } };
return gSmst->SmiHandlerRegister(AcpiSmiHandler, &AcpiSmiHandlerGuid, &Handler);
}
EFI_STATUS
EFIAPI
SmmMain(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
) {
EFI_SMM_BASE2_PROTOCOL *SmmBase2 = NULL;
EFI_STATUS Status = SystemTable->BootServices->LocateProtocol(&gEfiSmmBase2ProtocolGuid, NULL, (VOID **)&SmmBase2);
if(EFI_ERROR(Status))
return Status;
BOOLEAN InMmRam = FALSE;
SmmBase2->InSmm(SmmBase2, &InMmRam);
if(!EFI_ERROR(Status) && InMmRam) {
Status = SmmBase2->GetSmstLocation(SmmBase2, gSmst);
if(EFI_ERROR(Status))
return Status;
// register ACPI SMI handler
Status = RegisterAcpiSmi();
if(EFI_ERROR(Status))
return Status;
} else {
return EFI_DEVICE_ERROR;
}
return EFI_SUCCESS;
}
Для его регистрации отдельный протокол не используется, используется только
Пожалуйста, авторизуйтесь для просмотра ссылки.
SmiHandlerRegister из таблицы сервисов SMM.Как же нам коммуницировать с этим обработчиком? Теперь вернёмся к DXE драйверу. Спецификация предоставляет три версии протокола для коммуникации, мы воспользуемся
Пожалуйста, авторизуйтесь для просмотра ссылки.
– EFI_SMM_COMMUNICATION_PROTOCOL. Также, нам нужно будет выделить пул памяти, так как именно в нём будут «конвоироваться» данные из внешнего мира в SMM. Тут возникнет вопрос: «А как ACPI SMI обработчик узнает, что обращаются именно к нему, а не к другому?». Спецификация UEFI предоставляет решение в виде
Пожалуйста, авторизуйтесь для просмотра ссылки.
– это структуры с GUID обработчика, которые помещаются в начало буфера для коммуникации. Это будет проще увидеть на коде:
DXE Driver:
EFI_STATUS
EFIAPI
DxeMain(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
) {
EFI_SMM_COMMUNICATION_PROTOCOL *SmmCommunication = NULL;
EFI_STATUS Status = SystemTable->BootServices->LocateProtocol(&gEfiSmmCommunicationProtocolGuid, NULL, (VOID **)&SmmCommunication);
if(EFI_ERROR(Status)) {
Print(L"[ ! ] LocateProtocol returned 0x%llX (%r)\n", Status, Status);
return Status;
}
// allocate communication buffer
UINTN Size = 0x40;
VOID *CommPool = NULL;
Status = SystemTable->BootServices->AllocatePool(EfiRuntimeServicesData, Size, &CommPool);
if(EFI_ERROR(Status)) {
Print(L"[ ! ] LocateProtocol returned 0x%llX (%r)\n", Status, Status);
return Status;
}
// craft header
EFI_SMM_COMMUNICATE_HEADER *Hdr = (EFI_SMM_COMMUNICATE_HEADER *)CommPool;
EFI_GUID AcpiSmiHandlerGuid = { 0xA3715FCB, 0x05F9, 0x4FD6, { 0xA3, 0x5B, 0x31, 0x2F, 0x29, 0xD5, 0x08, 0xA3 } };
CopyGuid(&Hdr->HeaderGuid, &AcpiSmiHandlerGuid);
Hdr->MessageLength = Size;
// place here your data
// Hdr->Data = [ your data here ]
// fire SMI
Status = SmmCommunication->Communicate(SmmCommunication, CommPool, &Size);
// process output
return Status;
}
Таким образом мы можем свободно коммуницировать с SMM посредством ACPI SMI обработчика! Теперь разберёмся с тем, как это работает. Помните SMM Core, который присутствует на карте SMRAM выше? Так вооооооот…
Протокол для коммуникации на самом деле отправит нас
Пожалуйста, авторизуйтесь для просмотра ссылки.
. В этом драйвере, по заголовку, будет определён тип обработчика – если GUID нет в заголовке, то драйвер делать с этим
Пожалуйста, авторизуйтесь для просмотра ссылки.
, а просто отправит его дальше. Если же в заголовке буфера GUID есть, то, SMM Core
Пожалуйста, авторизуйтесь для просмотра ссылки.
какой обработчик был зарегистрирован с переданным ему GUID. Если такого нет – он вернёт определённый статус, а если есть –
Пожалуйста, авторизуйтесь для просмотра ссылки.
в порт 0xB2 ноль (любая запись в этот порт триггерит SMI), а там платформа уже
Пожалуйста, авторизуйтесь для просмотра ссылки.
, что к чему. То есть, мы видим следующую картину:
Работа с памятью в SMM
SMM «в оригинале» работает в реальном режиме, а значит, для адресации ему доступны только первые 4 гигабайта физической памяти. Это большая проблема для x64 систем, в особенности для адресов расположенных выше 4 гигабайт. У нас есть два решения:
- Самостоятельно создавать собственную страничную иерархию выходить в длинный режим. Платформа умеет это делать, но только в случаях выхода из режима S3 и если прилетает капсульный апдейт;
- Модифицировать страницы SMRAM и ремаппать каждую «внешнюю» таблицу памяти к себе в SMRAM.
Пожалуйста, авторизуйтесь для просмотра ссылки.
.SMM и вендоры
Не самый нужный пункт статьи, но стоит понимать, что вендоры всё же заботятся о целостности прошивки, в особенности об SMM. Например, у Intel и AMD есть «загрузочные» решения - Intel Boot Guard и AMD Platform Secure Boot. Они срабатывают ещё до передачи управления SEC Core (ещё одна стадия, которая в разговоре не упоминалась), то есть, код для проверки подлинности находится уже в микрокоде процессора. На примере Intel будет схожая цепочка:
AMD PSB работает схожим образом, но немного интереснее. Если IBG в случае «правильной» конфигурации, записанной в FPF, просто не загрузит платформу, то PSB, судя по всему, не загрузит модифицированный (или заменённый) драйвер (судя по моим тестам).
Также, уже на новых чипсетах Intel есть SMM Isolation, который ломает приколы с адресацией в SMM, вот
Пожалуйста, авторизуйтесь для просмотра ссылки.
хорошо написано.Послесловие
Ну, вот и краткое описание того, что представляет из себя SMM, как под него писать, куда можно ориентироваться при разработке смешных штук. Возможно, это кому-то пригодится или хотя бы заинтересует.
В идеале, если нацеливаться на игоры – смотреть на SMI# пин чипсета, как он триггерится и при каких событиях, я упоминал тот же xHCI, с чего, в принципе, можно и начать. Меньше оставляешь следов – живёшь дольше.
Полезные материалы
-
Пожалуйста, авторизуйтесь для просмотра ссылки.
- Спецификации
Пожалуйста, авторизуйтесь для просмотра ссылки.
и
Пожалуйста, авторизуйтесь для просмотра ссылки.
- Спецификации
Пожалуйста, авторизуйтесь для просмотра ссылки.
и
Пожалуйста, авторизуйтесь для просмотра ссылки.
Последнее редактирование: