Pull to refresh
313
0
Николай Шлей@CodeRush

Firmware Security Engineer

Send message
Денег станет достаточно, чтобы про них больше не думать до старости, а дальше можно жить где угодно, в том числе и в Европе.
Атакуют при этом чаще всего не идею, а реализацию.
Intel BootGuard сам по себе отработал штатно, и корень доверия он вполне себе обеспечил, т.к. в PEI-томе, накрытом им, ничего никто не менял. Зато драйвер BootGuardPei, написанный кстати именно AMI, а не OEMом, проверив хеши DXE-тома и обнаружив несовпадение из с эталоном (который тоже не даст исправить именно Intel BootGuard, потому что они в файле в PEI-томе лежат) вместо того, чтобы машину завесить или перезагрузить, поставил единицу в HOB и продолжил работу. Это потом уже BootGuardDxe должен был среагировать на эту единицу и выдать пользователю надпись «хеши поломались, все пропало», но вот незадача, его удалили уже, и потому реагировать на эту сиротливую единицу стало некому.
В итоге платформа замечательно работает с модифицированным DXE-томом, цепочка доверия нарушена, все счастливы, а Gigabyte виноват только в том, что не стал перепроверять реализацию цепочки доверия, предоставленную ему IBV.
Наверняка пофиксили, да. Если поняли, если согласовали фикс с AMI, если получили обновленный референс-код, если интегрировали его, если выпустили обновление, если запретили откат на него, если пользователь это обновление поставил. Несколько многовато если, на мой взгляд.
Очень много информации, нужной для 100% правильной пересборки, в образе либо нет, либо она там в крайне неудобном виде и ее придется доставать при помощи хитрого парсинга, дизассемблера и такой-то матери. Те же номера динамических PCD или AMI'шных SDL TOKENов брать неоткуда, и потому при крупных изменениях все равно придется половину пересобирать, а отличить крупные от некрупных труднее, чем не заморачиваться и собирать каждый раз заново.
Если вы про полезное применение UEFITool, то их масса полезных уже сейчас, но в билды встраивать именно эту утилиту я все равно не стал бы, даже если результат получился бы немного быстрее, чем у нынешней билд-системы в EDK2 (которая тормозит просто забей) — надежность низкая, плюс всякие вендорские фишки поддерживаются весьма слабо.
С последним предложением я бы очень сильно поспорил, потому что в теории теория от практики тоже не отличается, а на практике всю цепочку доверия большинства IBV и OEM уже не раз обходили, и еще не раз обойдут, потому что никто вообще не заморачивается проверкой того, что занятие то действительно бесполезное. А когда начали проверять, неожиданно оказалось, что вся «защита» обходится удалением драйвера BootGuardDxe, который раньше на бит в HOBе реагировал, при помощи упомянутого уже UEFITool'а, упс.
Не надо лучше, не удаляйте все артефакты после успешной сборки, и повторная сборка много времени не займет.
Как разработчики спецификации не старались, все равно не получилось избавиться от всех горизонтальных и вертикальных связей, и потому такая «полевая хирургия» иногда работает хорошо, а иногда не работает никак.
Если бы там было тривиально, уже были бы публичные готовые решения, а пока более менее готовое что-то есть только у Педро (EfiSwissKnife).
С другой стороны, даже не готовое, кривое и на коленке — сильно лучше, чем ничего., поэтому даже такие «сугубо теоретические» статьи я поддерживаю всеми руками. Если найдете возможность выложить что-то — отлично, если нет — другим будет проще подступиться к проблеме.
Статья отличная, спасибо, но «что делать, чтобы собрать данные для графа» было в принципе ясно с самого начала, и сложность там не в отсутствии хороших идей, а в отсутствии реализаций этих хороших идей. Вот и тут видит око, а исходников нет.
Нужна эта функция на самом деле буквально паре человек, а автоматизация ее прямо в UEFITool'е потребует включения туда дизассемблера, поиска по сигнатурам и затем сотен часов отладки еще. Гораздо лучше, на мой взгляд, со стороны UT ограничится UEFIDump'ом и все дальнейшее делать скриптами для IDA/R2/Binja/чтотамувас, потому что оно все специально под подобные задачи заточено.
hc->frameList = VMAlloc(1024 * sizeof(u32) + 8292);
hc->frameList = ((int)hc->frameList / 4096) * 4096 + 4096;
hc->qhPool = (UhciQH *)VMAlloc(sizeof(UhciQH) * MAX_QH + 8292);
hc->qhPool = ((int)hc->qhPool / 4096) * 4096 + 4096;
hc->tdPool = (UhciTD *)VMAlloc(sizeof(UhciTD) * MAX_TD + 8292);
hc->tdPool = ((int)hc->tdPool / 4096) * 4096 + 4096;

memset(hc->qhPool, 0, sizeof(UhciQH) * MAX_QH);
memset(hc->tdPool, 0, sizeof(UhciTD) * MAX_TD);
memset(hc->frameList, 0, 4 * 1024);

Не делайте так никогда, пожалуйста.

Если вам нужно выделить памяти с определенным выравниванием, сделайте функцию для этого, какую-нибудь VMAllocAligned(), принимающую выравнивание в качестве параметра.
Сейчас у вас в коде куча магических констант (8292 выглядит как опечатка в 8192, например), памяти выделено больше чем нужно (и потому guard page при выходе за границы может не сработать), вызов VMFree на любой из выделенных подобным образом указателей освободит что-то непонятное в большинстве случаев, а memset зануляет не весь выделенный буфер.
Хотели сделать имплант, а получилось в итоге неплохое введение в передачу данных по SMBus.
Вызывать прерывания, это, конечно, намного проще, чем стандартные функции через стандартный же ABI…

А PE/COFF-файл можно не только самому собрать в хекс-редакторе, но и любая фигня, способная делать исполняемые файлы для Windows это умеет уже.

Стало именно проще, причем практически везде. Если вы не хотите в это верить — дело ваше, а нам тут пока ехать надо, с мартышками или нет.

BIOS умер под собственным грузом легаси и костылей, не успев за развитием железа, и то, что пришло ему на смену — лучше во всех отношениях. Если вы думаете что это заговор мартышек — я не смогу вас переубедить.
U-boot и прочие embedded device tree вещи на АРМах привели уже к тому, что у каждой собаки свой собственный загрузчик, совместимый примерно ни с чем, при этом какой-нибудь вообще пятистадийный, и у него там внутри половина EFI, кусок линукса, тут написано вендором, тут написано сообществом, тут рыбу заворачивали.
Чем быстрее они там передут хоть на какой-то интерфейс стандартный (даже пусть это будут UEFI и ACPI, фиг с ним), тем лучше будет для их популярности и применимости, на мой взгляд.
Баян нужен был самим авторам прошивки по большей частью, потому что эта простая заглушка стала с развитием железки под ней настолько сложной, что во времена чипсета P55 там творилась уже настолько жесткая вакханалия, что у меня нет слов описать ее. JerleShannara смог бы, я думаю, если у него настроение будет.
То, что потом этот уже имеющийся баян дали загрузчикам ОС совершенно бесплатно, т.е. даром — это хорошо, а не плохо, потому что работа не пропала зря.
А этот EFI_SYSTEM_TABLE где висит? В воздухе? Уже для того, чтобы собрать бинарник, который EFI признает «своим» нужна масса тулов, просто в hexeditor'е вы этого не сделаете.
Висит в памяти, вывесил DxeCore, вам отдадут указательна на нее вторым параметром точки входа (т.е. в регистре EDX).

Нет. «На чём угодно» не получится. «Hello, world» для EFI — сложнее, чем весь BIOS первых 80386х! Во всяком случае у меня сложилось такое впечатление.
Это именно впечатление. «Hello World» для EFI выглядит вот так:
SystemTable->ConOut->OutputString(gST->ConOut, L"Hello World!");
Т.е. от языка нужны следующие возможности: генерация файла PE/COFF и вызов функции с MS x64 ABI.

Про проблемны реализации EFI спорить не готов, потому что выйдет очень длинно, но проблемы именно с CMOS/NVRAM — они исключительно от желания снизить стоимость решения (т.е. не ставить дорогой CMOS SRAM заметного объема) при возросшых требованиях к нему (железо усложнилось кратно).
Я перечитал два раза, и думаю, что понял наконец, о чем речь. Вам, видимо, не нравится тот факт, что EFI пытается абстрагировать сложность оборудования от пользователя, и делает это громадным количеством кода, который вам бы не хотелось запускать, а он все равно запускается и повлиять на это крайне не просто.
Альтернатив тут несколько, но выстрелить может, на мой взгляд, только одна — LinuxBoot, который сейчас развивают Google и Facebook. Там ребята выкинули из EFI весь верхний уровень (тот самый Extensible Firmware Interface) и заменили его ядром Linux, которое основную систему затем загружает kexec'ом без всяких интерфейсов, и умирает после этого практически без следа.
Идея отличная и работает замечательно, но для загрузки произвольной ОС (которой нужен ACPI, к примеру) не подходит, а если начать подводить — снова получится EFI, и далеко не факт, что во второй раз получится лучше.
А вот чтобы взаимодействовать с EFI — вам нужна масса весьма сложного кода, который, от того, что вы его скачали с сайта и запустили «не глядя» проще не становится.

Не нужна там никакая масса кода, все взаимодействие идет через EFI_SYSTEM_TABLE, которая по факту обыкновенная таблица указателей на функции. Ничего хитрого в работе с EFI нет, и взаимодействовать с ним можно и в машинном коде, и на ассемблере, и на чем угодно. Придется узнать немного о формате PE, и соглашении о вызовах Microsoft x64, и о том, что зачем нужен ExitBootServices(), но это все значительно проще, чем BIOS Interrupt Call с его магическими номерами, магическими адресами, нереальным режимом, управлением адресной линией А20, ресетом через порт клавиатуры и ручным пробросом VGA через мосты.
Вы, мне кажется, смотрите на GNU EFI SDK или EDK2 и думаете, что они необходимы и без них с EFI нельзя договориться. Если так — вы ошибаетесь.

Мне очень нравится идея концептуальной простоты, но реализации таковой не выдерживают, к сожалению, столкновения с реальностью, начальством и отделом маркетинга, и потому либо не существуют, либо не используются. Приходится ехать на том, что есть, и на вот этом ехать с EFI значительно веселее, чем без него, на мой взгляд.
Если коротко — Intel ACM, который процессор еще до ресет-вектора найдет через FIT, проверит его подпись при помощи МЕ, и затем исполнит, а он уже передаст управление на legacy reset vector, или сразу на SEC core.
Нужно это для организации root-of-trust для verified boot и measured boot, загрузки микрокода из FIT, и других задач, с которыми нужно закончить до начала исполнения пользовательского кода, но которые при этом нельзя выполнить на МЕ.
Вот тут @matrosov хорошо написал про ACM и BootGuard, почитайте если интересно.
Мы, мне кажется, друг друга не понимаем.
"Положите загрузчик на диск по вот этому пути, сделайте в нем функцию EFIAPI efi_main(EFI_HANDLE Handle, EFI_SYSTEM_TABLE *SystemTable), которую вам прошивка и вызовет" — это вот, на мой взгляд, в разы проще спецификации multiboot, прерываний этих номерных, карты памяти e820 и прочего EBDA.
EFI — это интерфейс, а сложным он стал от того, что оборудование стало сложным, плюс вагон особенностей реализации наконец-то решили стандартизировать.
От способа загрузки чудовищность зависит напрямую, с EFI-загрузкой нам доступны все сервисы и возможности прошивки с человеко-понятном виде, с нормальными именами, со спецификацией открытой. А с легаси — кривая эмуляция на IoTrap-ах, трамплины в 16-битный режим и обратно, и прочие костыли для совместимости с седой древностью, которые теперь приходится заводить через жертвоприношения.
Если ваша ОС нормально работает с EFI-загрузкой — ей и пользуйтесь, а CSM отключайте — от него на современном железе одни проблемы.
Так что если ваша цель — не потратить несколько лет на разборки с этими механизмами, а загрузить какую-нибудь OS… то лучше сделать вид, что мы вернулись в 1980е и у нас простой и бесхитростный 80386й «под капотом».

Лучше не надо, потому что те механизмы, которые делают современный ПК похожим на 80386 — это такое хтоническое чудовище, что с ними лучше не сталкиваться никогда. UEFI имеет в разы более простой интерфейс с прошивкой, и лучше изучить один раз код Linux EFI stub, или любого минимального EFI-загрузчика, и забыть про этот легаси-ад навсегда.
Чуть подробнее.
Прямо от ресет-вектора выполняется так называемая прошивка или firmware (firm она потому, что это ровно между hard и soft). Выполняется она на том же центральном процессоре, что и все остальное (начинается исполнение с одного конкретного ядра, обычно нулевого, которое называют BootStrap Processor, а все остальные ядра ничего не делают, пока не получат специальное SIPI-прерывание). Код прошивки исполняется прямо из ПЗУ, подключенного к одной из простых шин — LPC/SPI/eSPI, а затем копируется в ОЗУ после того, как это самое ОЗУ удасться найти и проинициализировать (процесс этот называется тренировкой, memory training).
По сути своей, прошивка состоит из двух относительно независимых слоев, нижний из которых занимается инициализацией конкретного оборудования (процессора, памяти, шин), абсолютно необходимого для верхнего слоя и ОС, и верхнего, который реализует интерфейс к этой самой ОС, и которым ОС пользуется для своих собственных целей. Само слово BIOS (т.е. базовая система ввода-вывода) — оно именно про интерфейс (который полностью называется BIOS Interrupt Call, т.к. использует программные прерывания практически для всего), а UEFI уже и называется именно интерфейсом.
Intel Management Engine — это отдельный процессор со своей собственной прошивкой (хранящейся обычно на том же ПЗУ, что и основная), стартующий раньше центрального и исполняющий различные сервисные задачи, которые на центральном процессоре исполнять по разным причинам не удобно.
Тов. khim прав в том, что тут надо книги писать и не одну, и одной Beyond BIOS тут не обойдешься, потому что она исключительно про одну (хоть и популярную) реализацию верхнего уровня прошивок для современных архитектур.
Кроме UEFI/TianoCore можно посмотреть на coreboot/Intel FSP/AMD AGESA/U-boot (это реализации именно нижнего уровня), а для верхнего — LinuxBoot и SeaBIOS.
Думаю, любой разработчик, пишущий критичный с точки зрения безопасности код, в идеале должен хорошо владеть семантикой языка, на котором он пишет, а также знать о его подводных камнях. Применительно к C это означает, что необходимо знать семантику переполнения и тонкости неопределённого поведения.

Спорить очень трудно, но я попробую. Вместо того, чтобы требовать знания тонкостей реализации переполнения, нужно требовать использования функций, аналогичных os_*_overflow из macOS, которые для GCC и CLang отображаются на встроенные, а для остальных выполены макросами.
В итоге получается, что при осторожном программировании на С любой арифметический оператор — потенциальный источник проблем, и должен быть либо заменен на вышеупомянутую функцию даже если автор кода мамой клянется, что он все до этого 3 раза проверил.
В общем, не надо надеяться только на профессионализм людей (потому что не очень хороший день бывает даже у очень матерых волков), лучше надеяться на процессы и автоматику, именно поэтому стоит посмотреть на более безопасный Rust в качестве замены крайне опасного С, и научиться пользоваться valgrind, asan, ubsan и статическими анализаторами.

Information

Rating
Does not participate
Date of birth
Registered
Activity

Specialization

Инженер встраиваемых систем, Системный инженер
Ведущий