• 🚨 Ещё можно успеть на курс «OSINT: технология боевой разведки» от Академии Кодебай

    🔍 Изучите методы разведки с использованием открытых источников (OSINT) для проведения успешных атак.
    🛠️ Освойте ключевые инструменты, такие как Maltego, TheHarvester и другие.
    🧪 Пройдите практические лабораторные работы, имитирующие реальную разведку.
    🧠 Развивайте навыки, которые помогут вам стать экспертом в области информационной безопасности.

    Запись открыта до 23 мая Подробнее о курсе ...

На проверке ASM. Программирование ОС [3] – BootUSB и таблица DMI/SMBIOS

LogoOsDev.webp

Эта третья часть темы о программировании кастомных операционных систем. Здесь рассмотрим интерфейсы «Desktop Management» DMI с усовершенствованной его версией «System Management» SMBIOS, а так-же работу с дисковыми накопителями «SATA Storage Device» в режиме AHCI. Поскольку тесты на виртуальных машинах типа QEMU и VirtualBox сильно ограничивают возможности и соответственно конечный результат, особое внимание уделяется созданию загрузочных флэшек USB без стороннего софта, в HEX-редакторе. Это позволит протестировать нашу ОС на реальном железе. Вот линки на первую и вторую части статьи.

Часть #3.

1. Создание загрузочной USB-Flash
• Внутренняя организация Flash накопителей​
• Вычисление размера в секторах LBA​
• Копирование загрузчика и ядра ОС​

2. Двоичная база SMBIOS
• Назначение​
• Поиск таблицы в памяти​
• Формат содержимого​



1. Создание загрузочной USB-Flash

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

Сейчас в инете можно найти огромное кол-во готового софта для создания Boot-USB, в числе которых Rufus, Sadru, AIO-Boot и многие другие. Однако проблема в том, что почти все эти зверьки заточены под создание установочных Win/Linux/MAC/DOS, а если и предлагает иные варианты операционных систем (как в нашем случае), то нужно быть готовым к длительным танцам с бубном. Именно по этой причине мы создадим загрузочный флэш-брелок вручную без специальных утилит, тем более что решается это дело буквально за пару минут.


1.1. Внутренняя организация Flash накопителей

USB диски отличаются от жёстких HDD только способом хранения данных – в USB роль запоминающих ячеек выполняют мосфеты (транзисторы) с устойчивым состоянием, а в HDD слой магнитного напыления. А вот логическая организация уже одинаковая: всё та-же таблица разделов в первом секторе «PartitionTable», и те-же секторы с нумерацией LBA «Logical Block Address». Однако есть и пара моментов, на которые стоит обратить внимание.

1. В классическом варианте USB поддерживает всего 1 раздел Volume, в то время как на HDD с разметкой MBR основных томов может быть макс 4. Конечно ничто не мешает создать и USB с несколькими разделами, но это уже нетипичная ситуация – вполне возможно Linux и проглотит подобного рода каприз флэшки, а вот Windows может и не принять пассажира на свой борт.​
2. Как правило, во-всех HDD первый раздел всегда начинается с сектора LBA(64) – это дань прошлому, когда использовалась трёхмерная геометрия пространства CHS (цилиндр, головка, сектор). Поскольку в одном цилиндре было строго 63 сектора, то раздел априори начинался с нового цилиндра, а не с его середины. Кстати это касается и твёрдотелых накопителей SSD, т.к. к ним предъявляется требование обратной совместимости. Но в силу того, что у USB нет физических цилиндров (концентрических треков на поверхности), их первый и единственный раздел может начинаться с любого сектора, хотя почему-то всеми утилитами форматирования был выбран именно LBA(128). Таким образом, между загрузчиком и первым разделом USB-Flash всегда имеется свободное пространство как минимум в 128-секторов, а это 128*512=64 Кбайт.​


1.2. Вычисление ёмкости USB-накопителя в секторах

Таким образом, вооружившись теорией можно создать загрузочную флэш вручную, просто открыв носитель в двоичном редакторе HxD и оформить подходящую «PartitionTable» для одного раздела. Однако заниматься таким фехтованием не имеет смысла, когда можно автоматизировать весь процесс например в виндовой утилите ком.строки DISKPART – это избавит нас от нудного вычисления общего кол-ва секторов на флэшке, чтобы установить валидный её размер. Значит жмём комбинацию Win+R --> diskpart, и дождавшись приветствия стучим по клаве в следующей последовательности:

Код:
DISKPART> list disk            <--------- Запрашиваем список имеющихся в наличии дисков

  Диск ###  Состояние      Размер     Свободно     Дин  GPT
  --------  ---------  -------------  ----------   ---  ---
  Диск 0    В сети          74 Gбайт  1024 Kбайт
  Диск 1    В сети          74 Gбайт      0 байт
  Диск 2    В сети        3854 Mбайт      0 байт


DISKPART> select disk 2        <--------- Установить фокус на USB диск (в моём случае №2)
          Выбран диск 2.


DISKPART> detail disk          <--------- Тест, что это реально наша Flash, а не хард с полезной инфой

   ADATA USB Flash Drive
   ИД диска             : 00000000
   Тип                  : USB
   Состояние            : В сети
   Путь                 : 0
   Конечный объект      : 0
   ИД LUN               : 0
   Путь к расположению  : UNAVAILABLE
   Только для чтения    : Нет
   Загрузочный диск     : Нет
   Диск файла подкачки  : Нет
   Диск спящего режима  : Нет
   Диск аварийного дампа: Нет
   Кластерный диск      : Нет

  Том   ###  Имя  Метка      ФС     Тип       Размер   Состояние
  ----- ---  ---  ---------  -----  --------  -------  ---------
  Том    5    H              RAW    Сменный   3853 Mб  Исправен


DISKPART> clean                               <--------- Обнулить текущий формат и геометрию
          Очистка диска выполнена успешно.

DISKPART> create partition primary            <--------- Создать основной раздел (по факту создаётся новая PartTable)
          Указанный раздел успешно создан.

DISKPART> select partition 1                  <--------- Установить на него фокус
          Выбран раздел 1.

DISKPART> active                              <--------- Сделать раздел активным/загрузочным (сигнатура 80h)
          Раздел помечен как активный.

DISKPART> assign                              <--------- Задать букву флэшке USB (иначе ОС потом её не увидит)
          Назначение точки подключения выполнено успешно.

DISKPART> exit                                <--------- Game Over!
          Завершение работы DiskPart...

С этого момента флэшка превратилась в загрузочную, а что она будет загружать – это уже зависит от нашего настроения. Обратите внимание, что мы не форматируем USB-Flash оставляя сырую файловую систему RAW, поскольку ядро нашей ОС будет работать без Linux/Win на чистом энтузиазме процессора CPU. Если сейчас вытащить брелок c порта и вставить вновь на место, то Win тут-же предложит нам отформатировать его, на что необходимо ответить категорическим отказом, иначе всё проделанное выше прямиком отправится коту под хвост.

FormatUsb.webp

1.3. Копирование загрузчика и ядра ОС на Flash

Теперь запустим двоичный редактор HxD, и через меню «Инструменты» откроем в нём новоиспечённую Boot-Flash, не забыв при этом снять галку «Только для чтения». Всё, кроме выделенной на скрине ниже «Partition Table» нам не нужно, а потому смело забиваем с адреса нуль этот хлам нулями, после чего сохраняемся по Ctrl+S.

Boot_LBA0.webp

Если скопировать выделенную область в новый файл, то получим наглядную картину геометрии нашей Boot-флэш. Критически важными здесь являются всего три поля – это флаг загрузочного раздела 80h в первом байте, и 2 дворда в хвосте. Значение первого равно 00000080h и это сектор LBA(128) с которого начинается раздел Volume, а в последние 4-байта утилита DISKPART на автомате вычислила и прописала общее число секторов в разделе. В данном случае видим значение 7.892.864 и если умножить его на размер одного сектора =512 байт, то получим ёмкость накопителя ~4 GB.

Таким образом, если у вас уже была отформатированная рабочая USB-Flash с валидно указанной геометрией в двух последних двордах, то чтобы сделать из неё загрузочную, достаточно просто указать флаг 80h в первом байте. Всё! ..и никаких сторонних утилит не нужно.

PartTable.webp

Прежний загрузчик нашей ОС был заточен под Floppy(A), но поскольку теперь в качестве носителя будем использовать USB-брелок, то придётся изменить стандартную функцию чтения секторов AH=2 прерывания int-13h на расширенную AH=42h. Аргументы ей передаются через специальный пакет-данных, и в примере ниже я выделил для этого пакета фрейм в стековой памяти. Обратите внимание, что первый сектор раздела вычисляется динамически по значению первого дворда offset 1С6h, в результате чего данный загрузчик можно будет использовать не только для USB-Flash, но и для жёстких дисков.

C-подобный:
         mov    ax,word[$$+0x01c6]  ;// Первый сектор раздела из PTable (см.предыдущий скрин)

         pushd  0                   ;// StartLBA Hight ------/----------> Итого 64 бита
         push   0 ax                ;// StartLBA Low   -----/
         push   0                   ;// Buffer Segment ----------/------> Куда копировать (приёмный буфер в памяти)
         push   0x0600              ;// Buffer Offset  ---------/
         push   32                  ;// Sector Counter -----------------> Сколько секторов: 32*512=64К
         push   16                  ;// Sizeof Packet  (размер пакета)

         mov    ah,42h          ;// функция 42h = чтение секторов с диска в память
         mov    dx,0x80         ;// номер диска
         mov    si,sp           ;// указатель на пакет
         int    13h             ;// зовём сервис диска
         add    sp,16           ;// восстановить стек от 'пушей

         push   cs 0x0600       ;// передать управление ядру ОС!
         retf

В скрепку положу готовый бинарь лоадера, а само ядро Kernel соответственно кладём в сектор LBA(128) флэшки, что представлено на скрине ниже. Повторюсь, что вставлять данные в редакторе HxD нужно не привычной комбинацией Ctrl+C, а «Вставкой с заменой» по Ctrl+B. Иначе размер носителя увеличится и получим ошибку.

LBA128.webp


2. Информационная база SMBIOS

Ещё в 2000-х Microsoft выдвинула жёсткие требования всем разработчикам материнских плат, мол если хотите, чтобы мы гоняли свою ОС на вашем железе, то предоставьте нам всю информацию о бортовом оборудовании своего «авто». Так от производителей появился сначала программный интерфейс DMI (Desktop Management), который позже был усовершенствован в текущий SMBIOS (System Management).

Суть в том, что в глобальном ROM-BIOS системы (которую пишут прогеры производителей мат.плат) имеется процедура для создания информационной базы, куда сбрасывается паспорт всех живых тушканчиков на мат.плате, начиная от CPU с чипсетом, и заканчивая портами подключения внешних устройств. Поскольку эта база создаётся биосом задолго до загрузки ОС, последние приходят уже на готовое – для выбора подходящего драйвера им остаётся только прочитать SMBIOS.

Такие были планы, но на практике Win/Linux сами при загрузке сканируют почву под ногами, не доверяя сведениям разрабов (да и технология Plug&Play предусматривает горячее подключение девайсов уже после загрузки ОС). Не смотря на это, интерфейс SMBIOS активно развивается – начиная с v3.0 имеется поддержка х64, а последняя v3.8 датируется августом 2024-года. В общем как ни крути, но для осдева это как-раз то, что доктор прописал.


2.1. Формат содержимого инфо-базы

На данный момент база SMBIOS включает в себя 46 таблиц. Каждая из них описывает отдельно взятый элемент материнской платы: например в таблице(0) хранится инфа о системном BIOS, в таблице(2) сведения о чипсете, в таблице(4) паспорт процессора CPU, и т.д. Размеры таблиц не фиксированы, и зависят от возможных свойств конкретного оборудования. Внутри базы все таблицы плотно прижаты друг к другу – где заканчивается одна, там сразу начинается следующая. Маркером окончания каждой из таблиц служит пара двоичных нулей 0000h.

Таблицы начинаются с одинакового для всех 4-байтного заголовка «Header», после которого идёт полезная нагрузка в виде бинарных данных, и в хвосте располагаются уже нуль-терминальные текстовые строки (если таковые имеются). Первый байт в заголовке хранит номер/идентификатор таблицы, а второй – размер бинарных данных в ней. В некоторых случаях данные кодируются, а коды оговариваются в спецификации, которую я прикрепил в скрепку к статье. Заголовок заканчивается 2-байтным дескриптором – для нас он не представляет интереса. Вот как это выглядит в утилите RW, где красным я выделил Header:

smbios_table.webp

Такой формат позволяет нам по первому байту определять тип таблицы в глобальной базе SMBIOS, а по второму байту прыгать сразу к её строкам. Например, в данном случае во-втором байте лежит значение(18h), и если использовать его как смещение Offset от начала текущей таблицы (см.зелёный блок), то упрёмся сразу в её текстовые строки. Каждая строка заканчивается одним терминальным нулём, а если встретим пару нулей, значит это конец текущей таблицы, и за ней будет идти сразу следущая.


2.2. Поиск базы SMBIOS в памяти ОЗУ

Адрес базы SMBIOS в системной памяти не регламентируется, и каждый вендор материнских плат волен сам выбирать её местоположение. Она может находится в любом месте начиная с сегментного адреса F000:0000. Найти базу можно сканированием памяти по сигнатуре SM на границе параграфа 16-байт. То-есть ставим указатель на сегмент F000:0000 и с шагом в 16-байт ищем строку «SM».

Обнаружив сигнатуру, упрёмся в заголовок самой базы, который числится в доках как SMBIOS_ENTRY. В этом заголовке прописывается версия базы, её размер и прочее, а так-же приоритетный для нас ещё один блок инфы с сигнатурой уже DMI (дань прошлому). Именно в этом блоке и будет лежать указатель на первую таблицу(0) в базе, после которой располагаются и все остальные. Кстати заголовок базы SMBIOS как-правило лежит в одном месте ОЗУ, а непосредственно массив таблиц уже вообще в другом. Структура заголовка базы имеет такой прототип:

C-подобный:
struct SMBIOS_ENTRY               ;// Offset
   SmbSignature     dd  0         ;// 00      _SM_
   Checksum         db  0         ;// 04
   Length           db  0         ;// 05
   Version          db  0,0       ;// 06
   MaxStructSize    dw  0         ;// 08
   EntryRevision    db  0         ;// 10
   Padding          db  5 dup(0)  ;// 11

   DmiSignature     db  5 dup(0)  ;// 16      _DMI_
   DmiChecksum      db  0         ;// 21
   DmiTableLength   dw  0         ;// 22
   DmiTableAddr     dd  0         ;// 24  <--------- Адрес таблицы(0) в базе SMBIOS
   DmiStructCount   dw  0         ;// 28
   wNumSMBStruc     dw  0         ;// 30
ends

smbios_base.webp

Немаловажную роль играет версия SMBIOS в заголовке базы, т.к. некоторые урезаны и имеют неполное кол-во таблиц. Например в первой (наиболее стабильной) версии v2.0 было всего 16 таблиц, а в v2.8 вообще 9 штук. У меня имеются несколько перечисленных ниже спецификаций, из которых я собрал такой сводный лист – имейте это в виду. В своём ядре ОС я буду выводить информацию только из выделенных коричневым таблиц:

smbios.webp

Полный исходник сбора информации из инфо-базы SMBIOS положу в скрепке, а здесь приведу лишь пример поиска её в системной памяти:

C-подобный:
;//********************************************************************
;//******************  Меню SMBIOS  ***********************************
;//********************************************************************
align 16
@SmbiosPage:
         call   ClearScreen             ;// очистить окно
         mov    di,(160*2)+74           ;//
         call   SetCheckBox             ;// вставим подчёркивание меню
         mov    dword[CurrentType],0    ;// номер таблицы(0)

         push   es 0xf000               ;// поиск сигнатуры "_SM_"
         pop    es                      ;// от 000F:0000 до 000F:FFFF
         xor    di,di                   ;//
@@:      cmp    dword[es:di],'_SM_'     ;// сравнить с шаблоном
         je     @f                      ;// если нашли
         add    di,16                   ;// иначе сл.параграф (шаг 16 байт)
         cmp    di,0xffff               ;// всё проверили?
         jnz    @b                      ;// нет – на повтор
         pop    es                      ;//
         jmp    @exitSmBios             ;// иначе: на выход

;//----- Нашли заголовок базы SMBIOS ---------------------------------
@@:      mov    ax, [es:di+6]           ;// версия базы в формате BCD
         ror    ax,8                    ;//    ...(коррекция Major/Minor)
         mov    dx, [es:di+22]          ;// размер базы
         mov    ebx,[es:di+24]          ;// адрес таблицы(0) в базе
         pop    es                      ;//
         mov    [SmbEntry],di           ;// запомнить данные в переменных
         mov    [SmbVersion],ax         ;// ^^^^
         mov    [SmbSize],dx            ;//   ^^^^
         mov    [DmiEntry],ebx          ;//      ^^^^

Как результат, на двух/своих стационарах я получил следующие логи, которые совпадают с отчётами сторонних утилит по сбору информации о системе. Так, в первом блоке видим паспорт биоса мат.платы, во-втором модель и версию чипсета, в третьем достаточно полная инфа о процессоре, включая частоту внешней шины и кол-во логических/физических ядер, и даже тип сокета (правда кэши я поленился пропарсить). Но что наиболее актуально, так это размер установленной физической памяти DDR-SDRAM в слотах, и если повезёт – её производителя как на втором скрине. А вот тип DDR(2,3,4) к сожалению не указывается в базе SMBIOS и если нужно, то придётся организовать чтение м/схемы SPD на модуле памяти, для чего потребуется доступ к шине i2c.

Зато у нас уже есть размер физ.памяти (на моих машинах это 4 и 16 гигов), который нельзя получить в реальном режиме никакими иными средствами. Он нам потребуется в части(5) статьи, для организации перехода в защищённый режим с длинной моделью памяти LongMode x64 (см.предпоследний пункт меню).

G31_SMBIOS.webp

Сори за качество скринов, т.к. делал их на свой смартфон:

H61_SMBIOS.webp


3. Заключение

Чтобы не было каламбура, идентификацию и работу с накопителями ATA/SATA оставим для следующей части, тем-более, что для них в пользовательском интерфейсе нашей ОС выделен отдельный пункт меню. В скрепке лежит спека на SMBIOS v3.8, а так-же исходники с готовыми бинарями USB-загрузчика и ядра Kernel. Всем удачи, пока!
 

Вложения

Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!

Курс AD