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

Статья Reversing the AMD Secure Processor (PSP) - Part 1: Design and Overview

samarie

HDD-drive
Пользователь
Регистрация
06.04.2023
Сообщения
34
Реакции
83
Депозит
0.00
Оригинальная статья: https://dayzerosec.com/blog/2023/04/17/reversing-the-amd-secure-processor-psp.html
Переведенно специально для xss.pro

AMD Platform Security Processor или "PSP" - это очень интересная технология, которая является критически важной для работы всех современных процессоров AMD. О нем довольно мало информации в сети, и поэтому часто неправильно понимают что он делает и лишь догадываются об этом. Он находится на вершине цепочки доверия, отвечает за инициализацию процессора и облегчает аттестацию (TPM), криптографию с аппаратным ускорением и технологию Secure Encrypted Virtualization (SEV).

Примечание: Хотя AMD-SP является современным "правильным названием", я буду сокращать его до "PSP" в этих сообщениях, чтобы избежать путаницы с другими популярными вариантами использования аббревиатуры "SP".

66e3b38341129ea452f0d.png

(снимок матрицы AMD "matisse" zen2 IO и кремния, из которого состоит PSP. Photo credit: Fritzchens Fritz for the die shot and misdake for annotation viewer - https://misdake.github.io/ChipAnnotationViewer/?map=Matisse_IOD).



Современные версии PSP используют встроенное в матрицу ядро ARMv7 Cortex-A5, которое запускается до того, как ядра x86 выходят из режима сброса. Каждая микросхема ядра (CCD) содержит ядро PSP, причем CCD-0 является главным PSP. Чтобы упростить ситуацию, все, о чем мы будем говорить, будет происходить на главном PSP, есть некоторая синхронизация и коммуникация, которая происходит между матрицами, но мы будем игнорировать это здесь.



Среди прочего, одной из основных обязанностей PSP является инициализация среды для x86, включая инициализацию динамического ОЗУ (DRAM) и загрузку BIOS x86. У него также есть собственное статическое ОЗУ (SRAM). Поскольку все эти вещи находятся на столь ранней стадии процесса загрузки и выполняются на уровне, близком к кремниевому, по ним практически нет документации (или, по крайней мере, публичной документации), поскольку большинству производителей не нужно знать о внутреннем устройстве и они могут рассматривать PSP как "черный ящик".



Я нашел PSP и то, как она работает, увлекательным, особенно из-за последствий для безопасности, поэтому я сделал ее своим побочным проектом 80/20 на несколько месяцев. К счастью, я не был в полном неведении, поскольку мне удалось найти обновление BIOS, содержащее версии прошивок с большим количеством строк и полезной информации. Кроме того, доступны спецификации Cortex-A5 [1], код драйвера Linux для CCP с открытым исходным кодом [2], а потрясающее исследование, проведенное PSPReverse, пролило на него много света [3]. Посткоды и коды ошибок загрузчика также находятся в открытом доступе [4], что очень помогает определить, что делает прошивка в любой момент времени.



Если вы ищете больше информации о PSP, вам следует ознакомиться с презентациями конференции PSPReverse и репозиториями, ссылки на которые приведены внизу этой статьи. Здесь я постараюсь рассказать о тех вещах, о которых не говорилось в этих презентациях или которые не были освещены подробно, и лишь бегло объясню совпадающие тезисы.



Эта статья является первой частью серии из двух частей блога. Здесь мы сосредоточимся на архитектуре PSP в целом, расположении ее памяти и некоторых основах работы загрузки программ. В следующих постах я расскажу о конкретных компонентах более подробно. Большая часть реверсинга была выполнена с помощью нашего общедоступного загрузчика AMD-SP [5] в Binary Ninja, который можно найти внизу вместе с другими ресурсами.



Краткий обзор


PSP состоит из пяти основных компонентов. BootROM (он же "On-Chip Bootloader"), Initial Program Loader/IPL (он же "Off-Chip Bootloader"), загрузчик AMD Generic Encapsulated Software Architecture (AGESA), защищенная ОС и криптографический сопроцессор (CCP).

Основной задачей BootROM является проверка и загрузка начального программного загрузчика (IPL) из флэш-памяти SPI. Это делается через скрытую "файловую систему прошивки PSP" AMD, которая состоит из таблицы ввода прошивки (FET) с различными потенциальными фиксированными смещениями во флэш-памяти [6]. В FET содержатся смещения таблиц каталогов PSP и BIOS, которые, в свою очередь, содержат записи для всех необходимых файлов прошивки и данных. Эти "файлы" могут быть сжаты, подписаны и зашифрованы. Как правило, все файлы, связанные с прошивкой, должны быть сжаты и подписаны, как минимум, а в более поздних итерациях еще и зашифрованы.



IPL действует как ядро загрузчика и работает в EL1 (что эквивалентно ring0 на x86 CPU). Оно в основном выполняет функции отладки для внутреннего использования AMD (режим диагностики), устанавливает таблицы страниц и другие вещи, необходимые для пользовательского пространства, а также предоставляет системные вызовы (svcs) для выполнения чувствительных действий в пользовательском пространстве (таких как загрузка и проверка прошивки из флэш-памяти или выполнение криптографических операций) [7]. Таким образом, IPL или внечиповый загрузчик является "ядром" загрузчика до тех пор, пока все не будет инициализировано и не будет запущена защищенная ОС.



После инициализации загрузчика, загрузчик AGESA (или "ABL") является первой частью кода, запускаемой в пользовательском пространстве (EL0, эквивалент ring3 на x86). Он состоит из микропрограммы ABL0, которая затем загружает и выполняет ABL1-7. То, что делают эти этапы ABL, выходит за рамки данной статьи, но в основном они отвечают за подготовку DRAM, загрузку блока управления системой (SMU) для управления питанием и сенсорной функциональностью, настройку BIOS и выполнение других действий, необходимых для работы x86.



После завершения ABL остается только запустить безопасную ОС или ядро TEE. Оно заменяет всю SRAM и перезаписывает IPL. Это операционная система, с которой x86 будет общаться через MMIO для облегчения доступа к таким вещам, как CCP, SEV API и (f)TPM.

529e39753bad0c259c011.png

Связующим звеном всего этого является криптопроцессор Crypto Co-Processor (CCPv5). CCP - это встроенный в процессор блок интеллектуальной собственности (IP), который может выполнять операции AES, SHA, RSA, ECC, true RNG и zlib inflate. Он также используется в качестве механизма копирования DMA через passthrough, с помощью которого BootROM и IPL загружают данные из флэш-памяти. Хотя это считается частью PSP, на самом деле он несколько изолирован, и определенные данные могут быть заблокированы в CCP и сделаны нечитаемыми даже для самой PSP. CCP - это отдельный зверь и заслуживает отдельной статьи в блоге, которая будет опубликована в ближайшее время.



Безумие MMIO
PSP выполняет некоторые низкоуровневые операции и вынуждена общаться с различными устройствами для выполнения задач. Хотя SPI флеш и CCP являются основными среди них, есть также такие вещи, как чипсет (он же "Fusion Controller Hub" или FCH), однократно программируемые (OTP) предохранители, таймеры, контроллеры прерываний, выделенный TPM (если он есть) и другие. Все это должно поместиться в 32-битное адресное пространство, предоставляемое Cortex-A5. В основе всего этого лежит Memory-Mapped I/O (MMIO).



Вот как выглядит адресное пространство для капсулы 1_0.8.0.84 AGESA.

25887f04182b2b200c552.png

Как вы можете видеть, SRAM - это лишь одна небольшая часть адресного пространства. Вы часто будете видеть ссылки на "SMN" и "Syshub" в этой статье, а также в коде PSP. SMN означает "Сеть управления системой", и, как и сам PSP, он почти не документирован. Однако по своей сути это сеть для общения с другими устройствами, находящимися или подключенными к системе-на-чипе (SoC). Эти слоты инициализируются и конфигурируются через область MMIO, которая сопоставляет адреса SMN/SYSHUB с адресами Advanced eXtensible Interface (AXI) для PSP.



AXI - это протокол межплатформенной шины для связи с внутренними IP-блоками, разработанный компанией ARM. Логично, что мы видим его здесь, поскольку PSP необходимо взаимодействовать с различными IP-блоками, включая таймеры, блоки управления памятью (MMU) и другие.



Если SMN используется для взаимодействия с устройствами SoC, то Syshub используется для получения представлений об оперативной памяти x86 и FCH/чипсете. Он также сопоставляется с адресами AXI.

2fd034d3a191ef29e7320.png

Syshub / слоты X86


Давайте рассмотрим, как это работает более подробно. Здесь есть две заметные функции, MapSysHub (которая используется в большинстве мест, как описано выше) и MapSysHubFchMMIO, которая используется для таких вещей, как защищенный RTC и запись почтовых индексов:

ca6e78e3fda47dd9f2364.png

Эти функции вызывают одну и ту же внутреннюю функцию, но с немного разными аргументами.

Код:
void* MapSysHubFchMMIO(int32_t arg1, int32_t arg2) {
     return _MapSysHubInternal(arg1, arg2, 6, 0xc0000000);
}

void* MapSysHub(int32_t arg1, int32_t arg2) {
     return _MapSysHubInternal(arg1, arg2, 4, 0xc0800000);
}

Сопоставление


Можно предположить, что первый и второй аргументы карты содержат адрес/хэндл Syshub, третий аргумент - это какой-то тип, а четвертый - неизвестный набор флагов. Ниже приведена моя реверсия внутренней функции (некоторые вещи сокращены для краткости).

Код:
void* _MapSysHubInternal(int addrLow, int addrHigh, int type, int flags) {
 uint32_t min_slot = 0;
 uint32_t max_slot;

 if (type != 1 && type != 4 && type != 5 && type != 6)
  return 0;

 // Example: 0xfffdfc000080 -> 0x3FFF40 | 0x3F = 0x3FFF7F
 // In effect, this is a divide by 0x4000000 (64MB, slot size)
 int frame_num = (addrHigh << 16) >> 10 | addrLow >> 26;
 if (type == 1 || type == 4) {
  // Non-MMIO
  max_slot = 7;
 } else {
  // MMIO
  min_slot = 8;
  max_slot = 0xE;
 }

 struct syshub_unk_1* mmio_unk1 = 0x32304D8;
 struct syshub_unk_2* mmio_unk2 = 0x32303E0;
 struct syshub_mmio_slot* mmio_slot  = 0x3230000 + (min_slot * 0x10);
 uint32_t slot = min_slot;

 for (; slot <= max_slot; slot++) {
  // Check if already mapped
  if (mmio_slot->unk_00h == r5_2 &&
   mmio_slot->type == type &&
   mmio_unk->slot_map[slot] == arg4) {
   break;
  }

  // Free slot list
  if (*(uint8_t*) (0x105A0 + slot) == 0)
   break;

  mmio_slot++;
 }

 // ... Check if slot is in-use / non-zero data and bail if so

 // Mark slot as occupied and indicate the next slot as free
 *(uint8_t*) (0x105A0 + slot) = *(uint8_t*) (0x105A0 + slot) + 1;

 mmio_slot->frame_num = frame_num;
 mmio_slot->unk_04h   = 0x12;
 mmio_slot->type      = type;    // offset 0x08
 mmio_slot->unk_0Ch   = type;

 mmio_unk2->slot_map[slot] = 0xFFFFFFFF;
 mmio_unk1->slot_map[slot] = flags;

 void *axiAddr = (addrLow & 0x3FFFFFFF) + (slot * 0x4000000) + 0x4000000; // 64MB slot size
 return axiAddr;
}

Здесь мы видим, что всего имеется 15 слотов. Слоты 0-7 могут быть использованы для доступа к памяти (DRAM), тогда как слоты 8-14 зарезервированы для MMIO к FCH. Есть также некоторый код для отслеживания того, какие слоты свободны и какие флаги к ним подключены. Поскольку PSP является однопоточной одноядерной средой, блокировка для этих доступов отсутствует (хотя барьеры памяти используются).



Физический адрес, к которому вы хотите получить доступ, кодируется как "номер кадра". Это похоже на номера кадров физических страниц (PFN), если вы знакомы с этой концепцией, но вместо номера страницы, который вы получаете делением на размер страницы, номер кадра - это физический адрес / 64MB (размер слота Syshub).



Когда эти регистры в пространстве MMIO записываются, контроллер AXI увидит это и сопоставит запрошенный адрес Syshub со слотами памяти X86, размер которых составляет 64 МБ (0x4000000). В AXI база слота находится по адресу 0x04000000. Это дает конечный диапазон адресов от 0x04000000 до 0x40000000, что является огромной частью адресного пространства.



Ниже приведена диаграмма процесса происходящего здесь.

79920c5aeb22f70805e1e.png

Разгруппировка
Снятие карты тривиально и просто включает запись нуля в соответствующие регистры MMIO. Единственное, о чем стоит беспокоиться, это о том, что плохой агент в пользовательском пространстве PSP использует системный вызов для разворачивания критически важного для безопасности региона.

Код:
int UnmapSysHub(void* axiAddr) {
 uint32_t slot = ((uint32_t) axiAddr - 0x4000000) >> 0x1A;

 // Ensure slot is valid and in-use
 if (slot == 0 || slot > 0xE || *(uint32_t*) (0x105A0 + slot) == 0) {
  return BL_ERR_INVALID_ADDRESS;
 }

 if (axiAddr == g_secure_region) { // data_e11c
  return BL_ERR_UNMAP_PSP_SECURE_REGION;
 }

 // Mark slot as freed
 *(uint8_t*) (0x105A0 + slot) = *(uint8_t*) (0x105A0 + slot) - 1;

 struct syshub_unk_1* mmio_unk1 = 0x32304D8;
 struct syshub_unk_2* mmio_unk2 = 0x32303E0;
 struct syshub_mmio_slot* mmio_slot = 0x3230000 + (slot * 0x10);

 memset(mmio_slot, 0, sizeof(struct syshub_mmio_slot));
 mmio_unk2->slot_map[slot] = 0;
 mmio_unk1->slot_map[slot] = 0;

 return 0;
}

POST-коды
В качестве примера того, где используется Syshub, можно привести запись POST-кодов в чипсет через шину LPC. Порт 0x80 доступен по физическому адресу 0xfffdfc000080.

Код:
int32_t log_postcode(int code) {
 int final_postcode = code | 0xee000000  // ee = BL prefix
 WriteSysHubPostCode(0xfc000080, 0xfffd, final_postcode, 4)
 printf("*** POSTCODE: 0x%08X\n", final_postcode)
 // ...
}

int WriteSysHubPostCode(uint32_t addrLow, uint32_t addrHigh, uint32_t val, uint32_t size) {
 int err;
 uint32_t* postcode_slot = _MapSysHubInternal(addrLow, addrHigh, 6, 0xc0000000)

 if (postcode_slot == NULL)
  return BL_ERR_SYSHUBMAP_FAILED;

 err = write_dword(postcode_slot, val, size);

 if (err == 0)
  return UnmapSysHub(postcode_slot);
 return err;
}

Вы можете заметить, что POST-коды имеют ширину 32 бита, что может вызвать недоумение, поскольку большинство дисплеев почтовых кодов, которыми оснащены материнские платы и картридеры, отображают только 8 бит / 2 цифры. Здесь возможны два варианта:
  1. Чипсет и производитель материнской платы фильтруют и/или переводят эти POST-коды в 8-битные POST-коды.
  2. Отображаются только последние 8 бит, а верхние 24 бита усекаются. В этом есть определенный смысл, поскольку младшие 8 бит являются наиболее значимыми, а старшие биты используются в основном для обозначения фазы загрузки. Например, POST-коды IPL имеют префикс 0xee00...., а POST-коды ABL имеют префикс 0xea00......
Полный список PSP и ABL POST-кодов можно найти в открытых документах [5] [6].

Слоты SMN
Слоты SMN работают аналогично слотам Syshub, хотя они гораздо меньше. Для этого мы рассмотрим функцию ReadFuseBits(), которая отображает данные, закодированные в предохранителях на кристалле. В данном конкретном случае она вызывается процедурой InitSecureUnlock, которая проверяет, отключен ли режим "безопасной разблокировки" через предохранители. Предположительно, эта безопасная разблокировка связана с отладкой, и предохранители безопасной разблокировки перегорают на розничных процессорах, таким образом позволяя только процессорам с инженерной сборкой получить к ней доступ на стоковой прошивке.

060cb1fd92f8c1e04dfd7.png

Функция ReadFuseBits довольно проста:



Код:
int ReadFuseBits(void *dest, uint32_t offset_in_bits, uint32_t num_bits) {
 if (offset_in_bits << 0x1B != 0 || num_bits == 0)
  return BL_ERR_INVALID_PARAMETER;

 uint8_t* fuses_base_addr = MapSmn(0x5d000);
 if (fuses_base_addr == NULL)
  return BL_ERR_OUT_OF_RESOURCES;

 uint8_t* fuses_addr = fuses_base_addr + ((offset_in_bits / 8) & 0xFFFFFFFC);
 uint32_t len = (1 + (num_bits / 8)) & 0xFFFFFFFC;
 memcpy(dest, fuses_addr, len);

 UnmapSmn(fuses_base_addr);
 return 0;
}

Сопоставление
Мы вернулись в 32-битное пространство, поэтому для адреса SMN поставляется только один 32-битный аргумент. Поскольку этот тип памяти менее сложен, задействовано меньше регистров MMIO.

Код:
void* MapSmn(void* smnAddr) {
 int slot = 0;
 uint32_t free_slot_bitmap = g_smn_slot_bitmap; // data_e238

 // Find a free slot
 while (free_slot_bitmap != 0) {
  free_slot_bitmap = free_slot_bitmap >> 1;
  slot++;
 }

 // Out of slots
 if ((0x20 - slot) == 0x20)
  return 0;

 // Example: 0x5d000 -> 0 (as 0x5d000 is in the first 1MB of SMN)
 // In effect, this is a divide by 0x100000 (1MB, slot size)
 int frame_num = (smnAddr >> 20 << 16) + (*(uint32_t*) (g_data_e0f4 + 0xA60) << 28);
 struct smn_mmio_slot *mmio_slot = 0x3220000 + (((0x20 - slot) << 1) & 0xFC);

 if (slot == 1) {
  mmio_slot->ctrl = (mmio_slot->ctrl & 0xFFFF0000) + (frame_num >> 16);
 } else {
  mmio_slot->ctrl = mmio_slot->ctrl + unk_2;
 }

 free_slot_bitmap = free_slot_bitmap & ~(1 << (0x1F - (0x20 - slot)));
 return (smnAddr & 0xFFFFF) + ((0x20 - slot) * 0x100000) + 0x1000000; // 1MB slot size
}

На первый взгляд кажется, что слотов 64, однако все не так просто. Например, если слот равен 1, индекс к MMIO будет (0x20 - 0x1) >> 1 << 2 == 0x3C. Слот 2 также разрешается в 0x3C. Слот 3 затем разрешается в 0x38, то же самое для слота 4, и так далее. На практике у нас есть 16 слотов MMIO. Судя по этому коду и работе исследователей PSPReverse, похоже, что каждый слот MMIO может управлять двумя слотами SMN. Это дает возможность иметь 32 слота SMN, сопоставленных в любой момент времени.

В отличие от 40 Мб слотов Syshub, SMN относительно малы - всего 1 Мб (0x100000). База слотов AXI равна 0x1000000, что дает диапазон слотов SMN от 0x1000000 до 0x3000000, который идет до границы области MMIO.

Разметка памяти (SRAM)​


Мы рассмотрели SMN и syshub, это покрывает большую часть адресного пространства, не относящегося к SRAM. Есть еще несколько устройств, таких как таймеры и т.п., сопоставленных с MMIO, а также CCP, но, как я уже упоминал, это заслуживает отдельного внимания и здесь рассматриваться не будет.

Остается SRAM, которая, вероятно, более знакома большинству. Вы можете рассматривать ее как "оперативную память" PSP (она не может использовать DRAM, поскольку еще не инициализирована!).



Таблицы страниц IPL
Одним из первых действий IPL или внечипового загрузчика является инициализация таблиц страниц через регистры управления базой таблицы трансляции (TTBCR) и базовые регистры таблицы трансляции (TTBR), а также настройка регистра управления доступом к домену (DACR).


Код:
mov     r0, #0b100010  // 4KB, PD1
mcr     p15, #0, r0, c2, c0, #0x2  // TTBCR
ldr     r0, translation_table_one  {0x14000}
mcr     p15, #0, r0, c2, c0  // TTBR
ldr     sp, data_47c  {0x3f000}
ldr     r2, data_480  {setup_page_tables}
blx     r2  {setup_page_tables}

Он устанавливает размер границы в 4 КБ на TTBR0 и отключает Translation Lookaside Buffer (TLB) на TTBR1. Сама таблица страниц находится по адресу SRAM 0x14000 в этой конкретной версии. Посмотрев на setup_page_tables(), мы можем понять, как распределена память.

Код:
void setup_page_tables() {
 uint32_t* first_level_table[]  = 0x14000;
 uint32_t* second_level_table[] = 0x13c00;

 // Set page table base
 first_level_table[0] = (uint32_t) second_level_table | (0xF << L1_POS_DOMAIN);
 for (uint32_t i = 1; i < 0x10; i++) {
  first_level_table[i] = PAGE_INVALID;
 }
 // ...

 uint32_t pa; // physical address to map to

 // Map privileged read-only range (code)
 for (uint32_t va = 0; va < 0xe000; va += 0x1000) {
  pa = va;
  set_second_level_descriptor(second_level_table, va, pa, L2_SMALL_PAGE | L2_AP_SVC | L2_AP_SVC_RO);
 }

 // Map privileged read/write range (data)
 for (uint32_t va = 0xe000; va < 0x15000; va += 0x1000) {
  pa = va;
  set_second_level_descriptor(second_level_table, va, pa, L2_LARGE_PAGE | L2_AP_SVC | L2_AP_SVC_RW);
 }

 // Map shared pages (read-only in user)
 pa = 0x3d000;
 for (uint32_t va = 0x50000; va < 0x52000; va += 0x1000) {
  set_second_level_descriptor(second_level_table, va, pa, L2_LARGE_PAGE | L2_AP_USR | L2_AP_SVC_RW | L2_AP_USR_RO);
  pa += 0x1000;
 }

 // ...

 // Map user read/write range (code+data)
 for (uint32_t va = 0x15000; va < 0x3d000; va += 0x1000) {
  pa = va;
  set_second_level_descriptor(second_level_table, va, pa, L2_SMALL_PAGE | L2_AP_USR | L2_AP_SVC_RW | L2_AP_USR_RW);
 }
}

Мы видим, что в большинстве случаев виртуальные адреса соотносятся 1:1 с физическими адресами. Интересно, что в то время как IPL имеет защищенные от записи страницы для кода, пользовательские страницы отображены как чтение/запись для кода и данных. Я не знаю точно, почему это так, возможно, эти записи изменяются позже через обработку системных вызовов. В любом случае, мы видим разделение привилегий и диапазоны адресов:



0x00000 - 0x15000 - это привилегированные страницы, причем первые 14 страниц зарезервированы для кода, а последние 7 - для данных.

0x15000 - 0x3D000 - пользовательские страницы, все из которых (по крайней мере, изначально) предназначены для чтения/записи.

0x50000 - 0x52000 - это некое общее виртуальное отображение, в которое пользовательское пространство имеет доступ только для чтения.



PSPReverse обнаружил, что BootROM отображается на высокие диапазоны адресов (0xFFFF0000) и не отображается в SRAM, оставляя IPL для загрузки по адресу SRAM 0x0. Последняя страница SRAM (объем которой зависит от чипа) зарезервирована для служебной страницы BootROM, которая содержит различную информацию, такую как открытый ключ RSA AMD, количество CCX (Core Complex), количество ядер на CCD (Core Chiplet Die) и другую аппаратную информацию.



Сложив все это вместе, мы получаем следующую примерную схему SRAM:

f23f9987ac9c3cfa99a1f.png

Загрузка AGESA (ABL)
Ища записи индекса и некоторые полезные строки, мы можем найти место, где PSP переключается в пользовательский режим и выполняет AGESA. Вот фрагмент HLIL из функции, которую я назвал ExecAGESA():

11ca188bda097adf28eda.png

На скриншоте выше g_abl0_addr инициализирован на 0x15000 ранее в процессе загрузки. jump_to_abl0() выполняет переключение в пользовательский режим и переходит к точке входа ABL0, которая находится сразу после заголовка файла PSP по адресу 0x15100 (более подробную информацию о заголовке файла PSP можно найти в документации Coreboot по ссылке внизу статьи и в PSPTool) [6].

Код:
jump_to_abl0:
push    {r4, r5, r6, r7, r8, r9, r10, r11, r12, lr}
mov     r5, #0
mcr     p15, #0, r5, c7, c5 // ICIALLU - Invalidate instruction caches to PoU
mov     r5, #0b11010000     // MASK_ABT | MASK_FIQ | MODE_USR
msr     spsr_fsxc, r5       // Write to Saved Program Status Register
ldr     lr, g_abl0_entry  {0x15100}  // data_4a4
sub.s   pc, lr, #0  {0x15100}

С этого момента мы вышли из режима супервизора (EL1) и работаем в пользовательском режиме (EL0). ABL0 действует как загрузчик пользовательского пространства и выполняет загрузку и выполнение ABL1-7 через системные вызовы.

Код:
int run_abl_stage(int abl_num, int psp_fw_entry_type, /* ... */) {
 int err;
 uint32_t size = 0x19700;
 
 err = syscall(SVC_ENTER, psp_fw_entry_type, 0x16200, &size);

 if (err == 0 && size != 0) {
  syscall(SVC_DEBUG_PRINT, "\nCalling ABL ");
  syscall(SVC_DEBUG_PRINT_EX, abl_num);

  void (*abl_entrypoint)(uint32_t) = (void (*)(uint32_t)) 0x16300;
  err = abl_entrypoint(0x2f900);

  syscall(SVC_DEBUG_PRINT, "\nReturned from ABL ");
  syscall(SVC_DEBUG_PRINT_EX, abl_num);
 }

 if (err == 0 || size == 0)
  err = 1;
 return err;
}

void main(/* ... */) {
 int err;
 enum PSP_BOOT_MODE boot_mode;

 // ...

 syscall(_SVC_X86_COPY_FROM_PSP, 0xfc000080, 0xfffd, 0xea00e0fc, 0x4); // postcode write 0xe0fc
 syscall(SVC_DEBUG_PRINT, "\nABL0 - Main ABL Execution\n");
 syscall(SVC_GET_BOOT_MODE, &boot_mode);

 // ...

 if (boot_mode == PSP_BOOT_MODE_S3_RESUME ||
  boot_mode == PSP_BOOT_MODE_S0i3_RESUME) {
  // ...
  err = run_abl_stage(4, 0x34, /* ... */);
  if (err != 0)
   run_abl_stage(7, 0x37, /* ... */);
 } else {
  // ...
  err = run_abl_stage(1, 0x31, /* ... */);
  if (err == 0) {
   err = run_abl_stage(5, 0x35, /* ... */);
   if (err == 0) {
    err = run_abl-stage(2, 0x32, /* ... */);
    if (err == 0) {
     err = run_abl_stage(6, 0x36, /* ... */);
     if (err == 0) {
      err = run_abl_stage(3, 0x33, /* ... */);
     }
    }
   }
  }
 }

 syscall(_SVC_X86_COPY_FROM_PSP, 0xfc000080, 0xfffd, 0xea00e0fd, 0x4); // POST code write 0xe0fd
 syscall(SVC_DEBUG_PRINT, "\nAll ABLs Complete (pass control back to PSP BL\n");
 syscall(SVC_EXIT, err);
}

ABL0 всегда будет оставаться отображенным и активным, пока работают ABL1-7, поэтому ему также необходимо собственное пространство. ABL1-7 загружаются по адресу 0x16100 с точкой входа по адресу 0x16200. Первым аргументом точки входа является адрес стека, который устанавливается в 0x2f900.



Здесь есть еще несколько интересных моментов. Во-первых, то, какие ABL будут запущены, зависит от режима загрузки (который система получает через syscall). Если система находится в спящем режиме (S3/S0i3), необходимо запустить только ABL4 или ABL7 в качестве запасного варианта. Из любого другого состояния (т.е. холодная загрузка из S5) необходимо выполнить несколько этапов в порядке 1 -> 5 -> 2 -> 6 -> 3. Должен признаться, что я ожидал, что этапы ABL будут выполняться в порядке возрастания, но, похоже, это не так. Я полагаю, что этот порядок также зависит от чипа и не является фиксированным для всех микроархитектур AMD.



Точное описание того, что делает каждая стадия, потребует изменения кода AGESA, и в данном случае это не входит в задачу. Однако я скажу, что ABL4 и 7, похоже, предназначены для переустановки тактовых частот через блок управления системой (SMU) и выполнения тестов памяти. Такие вещи, как обучение DRAM, не выполняются.

Заключение и выводы​

PSP - это неотъемлемая часть технологии в процессорах AMD, о существовании которой многие даже не подозревают. Это не то, о чем большинство людей должны заботиться при использовании своих ПК или написании программного обеспечения. Однако из-за недокументированной природы и шифрования, используемого в прошивке, ее также пугают неким супер-черным ходом. Несомненно, PSP обладает чрезвычайно мощными возможностями, способностью общаться с любым устройством, подключенным к SMN, и практически произвольным доступом к DRAM.



Взломанная PSP может стать интригующей целью. Доступ практически ко всей памяти и возможность DMA через CCP могут быть невероятно полезны, особенно если вы пытаетесь обойти такие средства, как безопасная виртуализация (SEV) и безопасность на основе виртуализации (VBS). Ключи и другие секреты также могут быть утечены или переданы с помощью чего-то вроде атаки SP-глюка PSPReverse [8].

Однако больше всего меня заинтересовала технология, о которой я хотел бы рассказать, и есть еще много вещей, которые просто не поместились в этот и без того длинный пост. Так что следите за продолжением!

Ресурсы​

 


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