• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Переход от общего понимания о UEFI к фактическому дампу прошивки UEFI

yashechka

Генератор контента.Фанат Ильфака и Рикардо Нарвахи
Эксперт
Регистрация
24.11.2012
Сообщения
2 344
Реакции
3 563
Введение

На момент написания статьи спецификация UEFI уже достигла версии 2.8. Поскольку стандарту уже почти 20 лет (устаревшая версия спецификации 1.1 была выпущена в конце 2002 года), можно с уверенностью предположить, что большинство людей, работающих в сфере информационной безопасности, хотя бы раз слышали термин UEFI за свою карьеру. Немного более смелая, но все же относительно безопасная ставка - это поставить деньги на утверждение, что, помимо простого знания о его существовании, большинство профессионалов в области безопасности также имеют некоторое смутное представление о том, что такое UEFI и чего им пытаются достичь. В быстром и не очень научном опросе, который мы провели с помощью некоторых из наших коллег, большинство из них знали, что UEFI имеет дело с микропрограммами, что это, по сути, замена устаревшему BIOS IBM PC и что он служит основой, на которой могут быть реализованы несколько функций, связанных с безопасностью, например Secure Boot.

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

Хотя это и понятно, нынешнее положение вещей все еще довольно далеко от оптимального.Причина тому двоякая. Во-первых, повсеместно распространен UEFI. Он повсеместен в том смысле, что прошивку, совместимую с UEFI, можно найти практически повсюду, начиная от низкопроизводительных SOC Raspberry PI, проходящих через все основные ноутбуки и настольные компьютеры, и заканчивая серверами сверхвысокого уровня. Вторая причина важности безопасности UEFI связана с многоуровневой природой современной компьютерной архитектуры, в которой каждый уровень стека защищен ровно настолько, насколько безопасен уровень ниже него. Поскольку микропрограммное обеспечение "сидит" очень близко к нижней части этого стека (обычно прямо над оборудованием), угроза на уровне микропрограмм может поставить под угрозу безопасность всей системы, обходя при этом многие традиционные базирующиеся на ядре или даже гипервизорах средства защиты.

Этот пост - первый из серии постов, в которых мы попытаемся пролить свет на эту тему с заявленной целью помочь большому количеству исследователей в сообществе освоить "поезд" UEFI. В этом посте мы в основном сосредоточимся на предоставлении теоретических знаний, а также практических знаний, необходимых для получения дампа прошивки UEFI, часто находящейся на микросхеме SPI. Следующие публикации будут продолжены там, где эта публикация заканчивается и будет обсуждать методы реверсинга, отладки и фазинга отдельных драйверов UEFI. Хотя поездка не особенно удобна и временами может быть ухабистой, мы считаем, что вид, который открывается с вершины "горы UEFI", стоит такого приключения.

SPI Flash & PCI устройства

Прежде чем реверсить или фаззить, нам сначала нужно выяснить, где хранится прошивка UEFI и как мы можем ее получить, т.е. сбросить его автономную версию на диск для дальнейшего анализа. Насколько нам известно, описать эту процедуру без ссылки на этот превосходный технический документ ESET (https://www.welivesecurity.com/wp-content/uploads/2018/09/ESET-LoJax.pdf) о вредоносной программе LoJax практически невозможно. Короче говоря, LoJax был буткитом, который до и в течение 2018 года эксплуатировал некоторую неверную конфигурацию оборудования в качестве средства заражения прошивки UEFI жертвы. Из-за низкоуровневой природы заражения LoJax обладал уникальной степенью стойкости: он мог выдержать переустановку ОС, замену жесткого диска и большинство других методов, которые ИТ-персонал обычно использует для очистки зараженных машин.

Чтобы иметь возможность выполнить заражение, LoJax сначала дампит содержимое микропрограммы UEFI, патчит ее вредоносной полезной нагрузкой, а затем зашивает обратно. Основываясь на этом описании, становится ясно, что мы можем получить собственную прошивку, просто следуя по пути, обозначенному LoJax. Ниже приводится соответствующий отрывок из раздела 4 технического документа, в котором описывается процесс:

"Задача инструмента ... получить базовый адрес региона BIOS во флэш-памяти SPI, а также его размер. Эта информация содержится в регистре хост-интерфейса SPI "BIOS Flash Primary Region". Все регистры интерфейса хоста SPI отображаются в памяти в Корневом Сложном Регистровом Блоке (RCRB), базовый адрес которого можно получить, прочитав правильный регистр конфигурации PCI. ReWriter_read получает этот адрес, используя RwDrv IOCTL 0x22840 и считывая правильное смещение (в нашем случае 0xF0). Как только известны базовый адрес и размер региона BIOS, инструмент дампа считывает соответствующее содержимое флэш-памяти SPI и записывает его в файл на диск ".

К сожалению, этот абзац не имеет большого смысла для тех, кто еще не знаком с некоторыми из терминологии и сокращений, распространенных в мире UEFI. Поэтому, чтобы упростить работу нашей пищеварительной системы, мы должны посвятить следующие несколько разделов ее разбивке на части, разоблачению мистификации терминов и обеспечению того, чтобы процесс был хорошо налажен. Мы уделим особое внимание флэш-памяти SPI, а также некоторым аспектам стандарта PCI.

SPI флеш память

Последовательный Периферийный Интерфейс, или сокращенно SPI, представляет собой полнодуплексный синхронный последовательный интерфейс, который используется для подключения устройств к процессорам. Среди прочего, эти устройства могут включать в себя микросхемы памяти, датчики и даже другие процессоры. В нашем случае нас в основном интересует конкретная микросхема флеш-памяти, припаянная к материнской плате и подключенная к процессору через SPI. Типичная емкость памяти для этого чипа составляет 16 МБ, и современные системы обычно оснащены парой из них, что дает общую емкость хранения 32 МБ. Микросхема SPI представляет для нас особый интерес, потому что обычно именно там хранится образ прошивки UEFI вместе с множеством других важных прошивок системы, таких как прошивка Gigabit Ethernet или прошивка Intel Management Engine.

1.jpg



Хотя аппаратные детали, относящиеся к протоколу SPI, интересны сами по себе, они выходят за рамки данной статьи. В целях нашего обсуждения мы ограничимся только программным интерфейсом, предоставляемым контроллером SPI. Контроллер SPI сам по себе является устройством PCI, поэтому некоторые предварительные сведения о топологии PCI необходимы для общего понимания.

PCI устройства

Peripheral Component Interconnect, или сокращенно PCI, представляет собой спецификацию, которая пытается навести некоторый порядок на огромном диком западе различных аппаратных периферийных устройств, производимых разными поставщиками и работающих по различным протоколам. Немного упрощая, мы можем сказать, что PCI достигает этой цели, используя два различных механизма: первый - это выделенное адресное пространство, а второй - стандартизованные данные конфигурации для каждого устройства. Опять же, как и в случае с микросхемой SPI, аппаратные детали здесь не представляют особого интереса. Мы рассмотрим только минимальный набор деталей, связанных с программным обеспечением, которые помогут нам двигаться дальше.

Согласно спецификации PCI, каждое PCI-совместимое устройство имеет так называемый адрес PCI. Этот адрес состоит из 3 отдельных полей: идентификатора шины, идентификатора устройства и идентификатора функции. В технической литературе принято обозначать эти адреса как триплеты B.D.F. Также стоит отметить, что в большинстве систем все устройства PCI в конечном итоге подключены к одной шине, поэтому иногда это опускается для краткости.

2.jpg


В дополнение к собственному адресному пространству, спецификация PCI также требует, чтобы каждое PCI-совместимое устройство предоставляло буфер длиной 256 байт, обычно называемый "пространством конфигурации". Пространство конфигурации может предоставить нам множество информации об устройстве, такой как его идентификатор устройства, его идентификатор поставщика и расположение диапазонов MMIO для устройства.

3.jpg


PCI I/O

Хорошо известный факт, касающийся архитектуры x86, заключается в том, что она поддерживает не один, а два различных варианта операций ввода-вывода:

  • Ввод-вывод на основе порта: который имеет отдельное 16-битное адресное пространство и использует две выделенные машинные инструкции для чтения или записи данных в устройство (IN и OUT, соответственно).
  • Ввод-вывод с отображением памяти: в котором диапазон физических адресов зарезервирован и сопоставлен с регистрами устройства вместо DRAM. Поскольку ЦП почти всегда использует виртуальные, а не физические адреса для обращения к памяти, для использования MMIO ядро операционной системы должно предоставить API для генерации действительного виртуального сопоставления с заданным физическим адресом. В Windows, например, это как раз и является целью функции MmMapIoSpace.
4.jpg

Устройства ввода-вывода на PCI можно рассматривать как своего рода гибридный подход между вводом-выводом на основе портов и вводом-выводом с отображением памяти. Процедура состоит из 3 основных шагов:

  1. Во-первых, идентификаторы шины, устройства и функции, а также смещение в пространстве конфигурации преобразуются в одно 32-битное значение. Обычно это делается по формуле: 0x80000000 | bus << 16 | device << 11 | function << 8 | offset
  2. Затем измененное значение записывается в порт ввода-вывода 0xCF8, обычно называемый PCI_CONFIG_ADDRESS.
  3. Наконец, данные, относящиеся к устройству, могут быть прочитаны или записаны через порт ввода-вывода 0xCFC, также известный как PCI_CONFIG_DATA.
Простую реализацию процедуры чтения PCI на языке C можно найти в драйвере режима ядра CHIPSEC:


5.jpg


Эксперимент: PCI I/O

Если мы хотим запачкать руки, мы можем использовать отличный инструмент RWEverything (которым также злоупотребляет буткит LoJax) для проведения небольших экспериментов с PCI. После установки и запуска RWEverything нас приветствует следующий не очень удобный экран:

6.jpg


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

7.jpg



В качестве упражнения давайте попробуем прочитать первый DWORD с устройства PCI 0.31.3 (HD Audio Controller на моем компьютере, при необходимости отрегулируйте числа в соответствии с вашей системой). Согласно процедуре чтения, представленной выше, значение, которое должно быть записано в порт 0xCF8, задается как:

0x80000000 | 0 << 16 | 31 << 11 | 3 << 8 | 0

который оценивается как 0x8000fb00. Зная это, мы можем указать RWEverything выполнить чтение с помощью следующих команд:

8.jpg


Основываясь на структуре конфигурационного пространства PCI, мы можем сделать вывод, что возвращаемое значение 0xA1708086 фактически состоит из двух 16-битных слов: идентификатора поставщика и идентификатора устройства. Чтобы преобразовать их из произвольных битов и байтов в значимые фрагменты информации, мы можем использовать репозиторий pci-ids, который представляет собой огромную и постоянно поддерживаемую базу данных различных идентификаторов PCI.

9.jpg


В качестве альтернативы ручному использованию команд IN и OUT мы можем просто использовать команду rpci32. Её использование избавляет нас от необходимости самостоятельно оценивать эффективный адрес PCI:

10.jpg


Наконец, мы можем полностью отказаться от интерфейса командной строки и просто просматривать конфигурационное пространство PCI прямо из графического интерфейса:

11.jpg


Как, вероятно, уже было отмечено, RWEverything - очень мощный инструмент в наборе инструментов исследователя безопасности прошивки. Как и любой другой мощный инструмент, у него довольно крутая кривая обучения, и обычно он предоставляет более одного способа выполнить задачу. Настоятельно рекомендуется проявлять особую осторожность при использовании RWEverything. Безответственное использование может вызвать неожиданное поведение, сбои или даже поломку машины.

Излишне говорить, что процесс прохождения через порты ввода-вывода 0xCF8 и 0xCFC каждый раз, когда требуется доступ к устройству PCI, громоздок, подвержен ошибкам (если выполняется вручную), а также не очень эффективен с точки зрения ЦП. Чтобы устранить эти недостатки, устройства PCI обычно используют пространство конфигурации, чтобы предоставить до 6 различных регистров базового адреса, или сокращенно BAR. Эти BAR обычно являются указателями на области физической памяти, где может иметь место MMIO.

12.jpg


Подводя итог этому разделу, PCI использует порты ввода-вывода 0xCF8 и 0xCFC для облегчения чтения и записи в пространство конфигурации. Обычно этот механизм используется для чтения одного или нескольких BAR, а затем используется гораздо более интуитивно понятный и быстрый подход MMIO для связи с устройством и выдачи ему команд.

Получение прошивки

Как упоминалось ранее, контроллер SPI (который отвечает за флэш-память SPI) сам по себе является устройством PCI. Есть несколько способов узнать его адрес PCI, но окончательным источником истины, несомненно, являются мануалы Intel Platform Controller Hub. Обязательно выберите тот, который лучше всего соответствует версии вашего чипсета, иначе результаты могут стать непредсказуемыми!

Например, наша тестовая машина оснащена PCH серии 300, поэтому соответствующую таблицу можно найти здесь (https://www.intel.com/content/dam/w...es-chipset-on-package-pch-datasheet-vol-1.pdf). Внимательно просматривая раздел 4.2.1 - «Устройства и функции PCI», мы можем определить, что этот конкретный контроллер SPI доступен нам по адресу PCI 0.31.5.

13.jpg

Далее нам нужно выяснить, какие BAR активны для контроллера SPI. Для этого просто просмотрим конфигурационное пространство PCI устройства с помощью RWEverything:


14.jpg


На скриншоте ясно видно, что у контроллера SPI только один активный BAR из 6. Таким образом, отныне мы можем называть его просто SPIBAR без риска быть двусмысленным. Затем нам нужно выяснить, какие регистры SPI отображаются на физический адрес, на который указывает SPIBAR. Опять же, таблицы данных Intel не разочаровывают, когда дело доходит до этих мельчайших деталей:

15.jpg


Программный подход к дампу флэш-памяти SPI довольно сложен и основан на четко определенном манипулировании этими регистрами. По сути, важную роль в этом процессе играют 3 регистра:

  • Регистр адреса флэш-памяти, часто сокращенно FADDR. Этот регистр просто содержит линейное 32-битное смещение от начала флэш-памяти SPI.
  • Регистры Flash-данных, часто сокращенно FDATAX. Фактически это массив регистров, каждый из которых имеет длину 4 байта. После завершения цикла чтения эти регистры будут заполнены необработанными байтами, считанными из флэш-памяти.
  • Регистр управления флэш-памятью аппаратной последовательности, часто сокращенно HSFC. Этот регистр используется для передачи команд контроллеру SPI и состоит из нескольких полей. Особый интерес для нас представляют:
  • Поле счетчика байтов флэш-данных, часто сокращенно FDBC. Мы используем это поле, чтобы указать количество байтов, которое мы хотим прочитать/записать. Поскольку это поле имеет длину всего 6 бит, максимальное количество байтов, которое может быть обработано за один цикл, ограничено 64.

  • Поле цикла прошивки (FCYCLE). Это 2-битное поле кодирует тип операции, которую мы хотим выполнить. Допустимые значения для этого поля: 0b00 для чтения, 0b10 для записи или 0b11 для удаления блока.

  • Поле Flash Cycle Go, часто сокращенно FGO. Установка этого бита в 1 дает команду контроллеру SPI выполнить операцию с флэш-памятью SPI, как определено полями FDBC и FCYCLE.

Графически регистр HSFC часто изображают так:

16.jpg

Точная процедура дампа флэш-памяти SPI с использованием этих регистров подробно описана в прекрасной книге «Руткиты и буткиты», но по сути она состоит из следующих шагов:

  1. Запишите интересующее нас смещение в регистр FADDR
  2. Запишите размер данных, которые мы хотим прочитать, в HSFC.FDBC.
  3. Установите HSFC.FCYCLE в 0b00, чтобы указать операцию чтения флэш-памяти.
  4. Запустить цикл, установив бит HSFC.FGO
  5. Подождите, пока данные будут готовы, опросив один из регистров состояния
  6. Прочитать возвращенные данные из соответствующего регистра FDATAn
  7. При необходимости повторите
Тот же набор операций изображен графически в документе LoJax и упоминается здесь для ясности:

17.jpg



Эксперимент: дамп заголовка SPI

В качестве эксперимента мы можем выполнить вышеупомянутую процедуру, чтобы попробовать сдампить заголовок флэш-памяти SPI. И снова для этого воспользуемся интерфейсом командной строки RWEverything. Те из вас, кто предпочитает более питонический подход, возможно, захотят поближе познакомиться с проектом Divination от @depletionmode, который должен предоставить примерно эквивалентные возможности.

Мы уже знаем, что SPIBAR для нашей тестовой системы расположен по физическому адресу 0xFE010000. Основываясь на смещениях из рисунка 7, мы можем вывести абсолютные адреса других соответствующих регистров SPI:
  • FADDR = SPIBAR + 0x8 = 0xFE010008
  • HSFC = SPIBAR + 0x6 = 0xFE010006
  • FDATAn = SPIBAR + 0x10 + n * 4

Зная это, мы можем указать контроллеру SPI прочитать первые 64 байта флэш-памяти, используя следующие команды:

18.jpg


После выполнения команд чтения мы видим, что регистры FDATA0-FDATA3 просто заполнены единицами, а FDATA4 содержит магическое значение 0x0FF0A55A. Быстрый поиск в Google покажет, что это значение является сигнатурой, используемой для указания, что флэш-память SPI работает в так называемом "режиме дескриптора" (подробнее об этом позже). Это очень убедительное свидетельство того, что операция чтения действительно была успешно завершена.

Хотя ручной подход, который мы только что описали, работает отлично, он (к сожалению) плохо масштабируется, и очень желателен более автоматизированный и надежный подход. К счастью для нас, оказалось, что chipsec имеет возможности дампа SPI, начиная с 2014 года. Тем из вас, кто не знаком с chipsec, мы настоятельно рекомендуем проверить их репозиторий и, если возможно, внести свой вклад в проект. Вкратце, chipsec можно охарактеризовать как "структуру оценки безопасности платформы", что означает, что она способна выполнять строгий набор тестов на действующей системе, выявляя распространенные уязвимости микропрограммного обеспечения и неправильные настройки.


19.jpg


В дополнение к своему основному набору тестов, chipsec также действительно хорошо справляется с отвлечением многих сложностей современной прошивки от конечного пользователя. Например, вся процедура дампа SPI аккуратно обернута в одну простую команду Python:

python chipsec_util.py spi dump c:\rom.bin

20.jpg


Вот и все! Конечно, в этой команде нет никакой магии. Под капотом chipsec будет выполнять все шаги, описанные выше, чтобы выполнить свою работу. Любопытный читатель может счесть полезным взглянуть на реализацию дампера в том виде, в котором он представлен в функции read_spi_to_file.

Совет: если вы чувствуете себя достаточно смелым, вы можете использовать драйвер RWEverything в сочетании с chipsec для выполнения получения прошивки. Это избавляет вас от необходимости загружаться в режиме подписи и самостоятельно создавать драйвер режима ядра chipsec. Обратной стороной является то, что поддержка RWEverything считается экспериментальной для chipsec, поэтому есть небольшая вероятность столкнуться с BSOD при ее выполнении. Также помните, что, хотя RWEverything является полезной утилитой, она значительно увеличивает поверхность атаки, поскольку любая программа пользовательского режима может использовать свои низкоуровневые примитивы доступа для выполнения высокопривилегированных операций, поэтому используйте ее осторожно!

Чтобы включить помощник RWEverything, сначала раскомментируйте его строку импорта из файла chipsec/helper/helpers.py:

21.jpg


После этого вы можете передать флаг –helper rwehelper для большинства команд, связанных с chipsec. В результате команда получения прошивки просто становится:

python chipsec_util.py --helper rwehelper spi dump c:\rom.bin

Распаковка прошивки

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

22.jpg


Совет: при выборе ресурса UEFITool для загрузки предпочтительнее использовать версии, отмеченные значком "NE". Эти сборки происходят из ветки new_engine, которая, среди прочего, содержит массивную базу данных UEFI GUID для преобразования неопределенных байтовых блобов, таких как FC510EE7-FFDC-11D4-BD41-0080C73C8881, в гораздо более удобные и значимые имена, такие как AprioriDxe.


23.jpg


UEFITool не только позволяет просматривать скопированные флэш-образы SPI, но также поддерживает извлечение отдельных файлов для дальнейшего анализа. В большинстве случаев нас будут интересовать реальные исполняемые образы, которые можно дизассемблировать или отлаживать. Чтобы сделать дамп, расширяйте нужный файл прошивки до тех пор, пока не появится раздел с именем "PE32 image section". Затем вы можете извлечь его, просто щелкнув по нему правой кнопкой мыши и выбрав "Extract body" из раскрывающегося меню.

24.jpg


Другие известные кандидаты для распаковки образов прошивки UEFI включают:

  • UEFIExtract: это простая утилита командной строки, которая поставляется вместе с UEFITool. Он поддерживает распаковку отдельных файлов (однозначно идентифицируемых по GUID), а также целых томов прошивки. Используя этот инструмент, вы создадите полную структуру каталогов, представляющую образ прошивки, включая различные файлы метаданных.
  • UEFI_REtool: это набор скриптов Python и плагинов IDA, которые можно использовать (среди прочего) для распаковки образа прошивки. В отличие от UEFIExtract, он не пытается извлекать файлы метаданных, а только образ PE.
  • uefi_firmware_parser: Это больше похоже на библиотеку синтаксического анализа прошивки, чем на полноценный инструмент. Используйте его, если вы хотите создать на его основе свои собственные инструменты или провести интерактивные эксперименты из оболочки IPython.
  • Ghidra-firmware-utils: это плагин для ghidra, позволяющий загружать дополнительные ПЗУ PCI, образы ПЗУ, файлы TE (модули PEI используют эту урезанную версию формата PE) и включает сценарий ghidra для именования GUID и распространения типов UEFI.

Заключение

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

Автор https://labs.sentinelone.com/moving...about-uefi-to-actually-dumping-uefi-firmware/
Автор перевода: yashechka
Переведено специально для https://xss.pro
 
Будет, их там 4 должно быть
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх