Инженеры Microsoft тщательно оберегают свой системный периметр, жёстко пресекая попытки вторжения в ядерное пространство Win из вне. Анархия и вседозволенность закончилась с приходом ХР-SP3, когда юзеру был закрыт доступ к устройству \\Device\PhysicalMemory. До этого, в XP-SP2 и ниже, функцией NtOpenSection() можно было без проблем открыть любую область физической памяти как на чтение, так и на запись, со-всеми вытекающими последствиями. В результате, черви и прочая малварь чувствовала себя в ядре вольготно, поэтому мелкософт поспешил прикрыть эту лавочку. Таким образом, системе XP-SP3 выпала честь оказаться единственной, где юзеру был закрыт полный доступ к физ.памяти ОЗУ.
Однако корпорации строят операционные системы для потребителей, а им явно не понравилось такое решение. Так, чтобы не спровоцировать очередную "бурю в стакане", начиная с Win-Vista системный протекторат снизошёл до рядового программиста и в состав интерфейса Win32-API включил функцию GetSystemFirmwareTable(). Её возможности ограничены чтением самых верхних адресов физ.памяти в диапазоне от
до
. В эту область попадают системные таблицы, такие как: биосы бортовых устройств загрузки (например встроенное видео), а так-же ACPI и SMB-структуры. Отметим, что объект \\Device\PhysicalMemory так и не вернулся обратно, и на данный момент у нас по-прежнему к нему нет доступа. В данной статье рассматриваются методы чтения "FirmwareTable", что она из-себя представляет и какую имеет практическую ценность.
-------------------------------------------------
Оглавление:
1. SMBios – общие сведения;
2. Формат структур SMB;
3. Перечисление доступных таблиц – EnumSystemFirmwareTables();
4. Программное чтение и разбор данных SMBIOS;
5. Заключение.
1. SMBIOS – общие сведения
Таблица "System Management BIOS" (SMB) хранится внутри системного BIOS/EFI, и при включении машины копируется в оперативную память компьютера. Она включает в себя характеристики буквально всех физических устройств материнской платы, начиная от процессора и заканчивая различными датчиками. Такой перенос данных избавляет ОС от необходимости проверять оборудование напрямую, чтобы определить перечень активных устройств. Из централизированной базы SMB черпают информацию такие утилиты прикладного уровня как WMI – Windows Management Instrumentation, что в дословном переводе означает "инструментарий управления Win".
Предшественником инфраструктуры SMBIOS был разработанный в далёком 1996-году интерфейс DMI-BIOS (Desktop Management Interface), который выкупила группа независимых разработчиков DMТF (Distributed Management Task Force) и похоронила его уже спустя 3-года. На смену DMIB, в преддверии ХХI-века они предложили удовлетворяющий современным требованиям х64 более продвинутый SMBIOS – последняяверсия 3.4.0спецификации датирована июлем 2020-года, т.е. интерфейс активно развивается.
Основную проблему при разборе этой базы представляет переменный размер её структур. Каждая из структур описывает отдельный девайс. Согласно спецификации, в таблице SMB зарезервировано место для 127 описателей, хотя на практике используются максимум 44. Производители материнских плат и биосов оставляют за собой право выбирать, характеристики каких именно устройств включать в эту таблицу. В результате, мы не можем предсказать состав и приходится обходить все имеющиеся структуры по номерам их типов "Type" (благо они строго регламентированы). Из всего знакомого мне софта, лишь программа"RW-Utility"корректно парсит эту таблицу – её скрин с деталями структуры типа(0) представлен ниже:
https://forum.antichat.xyz/attachmen...95571d73f4.png
Не смотря, что спецификация SMBIOS детально расписывает поля всех структур, понять что-либо из их описания трудно – эта дока скорее для уже "прокаченных" юзеров. Зато как видим, в программе RW приводится дамп структуры, и ниже идёт информационная нагрузка каждого из его байтов. По логам этой утилиты мне удалось создать универсальный инклуд с описанием всех типов (см.скрепку). Теперь, проецируя структуры инклуда на дамп, можно выводить необходимую инфу на консоль.
В таблице ниже собраны все регламентированные спецификацией записи, хотя на практике список гораздо меньше и напрямую зависит от настроения разработчика биос. Например если в дампе мы встретим структуру под номером(6), значит она описывает характеристики модуля памяти DDR. Соответственно структура под номером(4) представляет собой паспорт центрального процессора и т.д.:
https://forum.antichat.xyz/attachmen...c2534e3d8a.png
2. Формат информационных структур SMB
API-функция из библиотеки kernel32.dll под названием GetSystemFirmwareTable() возвращает в указанный буфер всю таблицу SMBIOS, если передать ей соответствующий аргумент. Повторюсь, что эта функция доступна только начиная с Win-Vista, т.е. на семёрке и выше. Приёмный буфер будет предварять не имеющая непосредственного отношения к таблице, 8-байтная структура "RawSMBiosData", где указывается версия SMB и размер скопированных данных. Вот как её описывает документация:
C-подобный:
Код:
struct RawSMBiosData
Method db
0
;
// ---> метод доступа (устарело)
MjVer db
0
;
// старшая часть версии SMB (мажор)
MnVer db
0
;
// младшая часть версии SMB (минор)
DmiRev db
0
;
// ---> версия DMI (устарело)
Length dd
0
;
// размер таблицы данных
ends
Далее, в буфере последовательно плечом-к-плечу следуют уже сами полезные структуры, которые начинаются с 4-байтного заголовка "Header", и заканчиваются терминальной парой нулей. Дескриптор структуры абсолютно не интересен – при разборе таблицы SMB ставку будем делать только на тип (см.перечень выше), и длину заголовка:
C-подобный:
Код:
struct HEADER
Type db
0
;
// Внимание!!! Тип очередной структуры
Length db
0
;
// размер заголовка с данными
Handle dw
0
;
// дескриптор структуры (не важен)
ends
Как правило, помимо закодированных данных, в структурах имеются и текстовые строки. Суть в том, что если к началу структуры прибавить размер её заголовка, то мы в аккурат упрёмся в начало массива текстовых строк. Поскольку все строки нуль-терминальные, то с выводом их на консоль проблем не возникает. Как только встретим пару нулей, значит достигли конца структуры, и к ней сразу примыкает следующая.
Чтобы продемонстрировать это, я открыл полученный дамп в HEX-редакторе и выделил первые три структуры цветом. Здесь видно, что таблицу предваряет вспомогательная "RawSMBiosData" (см.серый блок на рис.ниже), которая прямым текстом сообщает нам версию 2.6 и размер данных 0х00000813 = 2067 байт. Далее следует зеленый блок, где в первом байте хранится тип структуры, а во-втором – размер её заголовка: 0x00 и 0x18 соответственно. Обратите внимание, что каждая из структур заканчивается терминальной парой нулей – именно на них мы будем ориентироваться, чтобы при разборе структур не вылетать за их границы:
https://forum.antichat.xyz/attachmen...d0affc8fcf.png
В качестве примера рассмотрим первую структуру типа(0), которая описывает характеристики системного BIOS..
Судя по дампу ниже, текстовые строки начинаются со-смещения 18h, и это-же значение хранится в виде размера структуры во-втором байте от начала. Таким образом, если планируем вывести на консоль только строки, то для доступа к ним достаточно к началу структуры прибавить её размер. Данный алгоритм действителен для любой структуры, внутри таблицы SMBIOS:
Код:
Код:
Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
----------------------------------------------------------
00000000 00 18 00 00 01 02 00 F0 03 0F 90 DE 8B 7F 01 00 .......р..ђЮ‹...
00000010 00 00 33 05 08 10 FF FF 41 6D 65 72 69 63 61 6E ..3...яяAmerican
00000020 20 4D 65 67 61 74 72 65 6E 64 73 20 49 6E 63 2E Megatrends Inc.
00000030 00 56 34 2E 34 00 31 31 2F 32 35 2F 32 30 30 39 .V4.4.11/25/2009
00000040 00 00 ..
struct TYPE0 ;//
;
//----- Запрос поддержки RSMB ------------------------------//
;
// bswap преобразует имя поставщика в обратный порядок байт,
;
// в регистр EAX вернётся размер сохранённых в буфере данных.
cinvoke printf
,
mov eax
,
'RSMB'
bswap eax
invoke EnumSystemFirmwareTables
,
eax
,
buff
,
512
push eax
cinvoke printf
,
,
eax
pop ecx
;
// размер данных
shr ecx
,
2
;
// разделить на 4 = длина цикла в двордах
mov esi
,
buff
;
// адрес источника с данными
@@
:
lodsd
;
// берём очередной элемент из буфера
or eax
,
eax
;
// проверить его на нуль
je @firm
;
// выйти из цикла, если равно
push esi ecx
;
//
cinvoke printf
,
,
eax
;
// иначе: вывод элемента на консоль
pop ecx esi
;
//
loop @b
;
// промотать цикл ECX-раз..
;
//----- Запрос поддержки FIRM -----------//
;
// на системах с BIOS в буфере получим С0000 и Е0000,
;
// на системах с UEFI в буфер ничего не возвращается
@firm
:
mov eax
,
'FIRM'
bswap eax
invoke EnumSystemFirmwareTables
,
eax
,
buff
,
512
push eax
cinvoke printf
,
,
eax
pop ecx
shr ecx
,
2
mov esi
,
buff
@@
:
lodsd
or eax
,
eax
je @acpi
push esi ecx
cinvoke printf
,
,
eax
pop ecx esi
loop @b
;
//----- Запрос доступных таблиц ACPI -----//
;
// в отличии от двух предыдущих, в буфер возвращаются текстовые строки
@acpi
:
mov eax
,
'ACPI'
bswap eax
invoke EnumSystemFirmwareTables
,
eax
,
buff
,
512
push eax
cinvoke printf
,
,
eax
pop ecx
;
// размер возвращённых данных
shr ecx
,
2
;
// /4 = длина цикла
mov esi
,
buff
;
// источник для LODSD
mov edi
,
text
;
// приёмник для STOSD
@@
:
lodsd
;
// EAX = имя очередной таблицы ACPI (4-символа)
stosd
;
// сохранить в приёмнике для вывода на консоль
push edi esi ecx
cinvoke printf
,
,
text
;
// напечать как строку
pop ecx esi edi
sub edi
,
4
;
// возвратить указатель приёмника на место
loop @b
;
// промотать цикл ECX-раз..
@saveToFile
:
;
//===== Читаем данные и сбрасываем их в файлы ======================
;
// Начинаем с регионов памяти С0000 и Е0000 запрашивая их отдельно.
cinvoke printf
,
mov eax
,
'FIRM'
bswap eax
mov ebx
,
0xC0000
;
// диапазон С0000-DFFFFh
invoke GetSystemFirmwareTable
,
eax
,
ebx
,
buff
,
256
*
1024
mov
[
fSize
]
,
eax
;
// размер возвращённой инфы
mov esi
,
cName
;
// указатель на имя выходного файла
stdcall WriteData
;
// сбросить данные в файл!
cinvoke printf
,
,
[
fSize
]
mov eax
,
'FIRM'
bswap eax
mov ebx
,
0xE0000
;
// диапазон E0000-FFFFFh
invoke GetSystemFirmwareTable
,
eax
,
ebx
,
buff
,
256
*
1024
mov
[
fSize
]
,
eax
mov esi
,
eName
stdcall WriteData
;
// сбросить данные в файл!
cinvoke printf
,
,
[
fSize
]
;
//===== Запрашиваем базу Raw-SMBIOS ===============================
;
// и так-же сохраняем её в файл ===================================
mov eax
,
'RSMB'
bswap eax
mov ebx
,
0
invoke GetSystemFirmwareTable
,
eax
,
ebx
,
buff
,
256
*
1024
mov
[
fSize
]
,
eax
mov esi
,
rName
stdcall WriteData
cinvoke printf
,
,
[
fSize
]
;
//===== Запрашиваем базу ACPI с сохраненим в файл всех её таблиц =========
;
// Для начала нужно получить список доступных таблиц,
;
// после чего считывать их в цикле по одной ==============================
invoke _lcreat
,
aName
,
0
;
// создать общий для всех таблиц файл
mov
[
fHndl
]
,
eax
;
// запомнить его дескриптор
mov
[
fSize
]
,
0
;
// очистить поле с размером
mov eax
,
'ACPI'
;
// запрос на перечисление ACPI
bswap eax
;
// восстановить порядок байт
invoke EnumSystemFirmwareTables
,
eax
,
acpiTbl
,
128
mov ecx
,
eax
;
// ECX = размер возвращённых данных
shr ecx
,
2
;
// /4 = длина цикла (кол-во таблиц)
mov esi
,
acpiTbl
;
// источник
@@
:
lodsd
;
// EAX = имя очередной ACPI-таблицы
mov ebx
,
'ACPI'
;
// EBX = первый аргумент (тип запроса)
bswap ebx
;
//
push eax ebx ecx esi
invoke GetSystemFirmwareTable
,
ebx
,
eax
,
buff
,
256
*
1024
add
[
fSize
]
,
eax
;
// суммируем размеры всех таблиц
invoke _lwrite
,
[
fHndl
]
,
buff
,
eax
;
// запись очередной таблицы в файл!
pop esi ecx ebx eax
loop @b
;
// промотать цикл ECX-раз..
invoke _lclose
,
[
fHndl
]
;
// прибить файл
cinvoke printf
,
,
[
fSize
]
;
//===== КОНЕЦ ПРОГРАММЫ ==================================
@exit
:
cinvoke gets
,
buff
;
//
cinvoke exit
,
0
;
//
;
//===== Вспомогательная процедура записи в файл ==========
;
// принимает в ESI указатель на имя файла
proc WriteData
invoke _lcreat
,
esi
,
0
;
// создать файл
push eax
;
// дескриптор
invoke _lwrite
,
eax
,
buff
,
[
fSize
]
;
// записать в него данные
pop eax
;
//
invoke _lclose
,
eax
;
// закрыть файл
ret
;
// выйти из процедуры
endp
;
//----------
section
'.idata'
import data readable
library msvcrt
,
'msvcrt.dll'
,
kernel32
,
'kernel32.dll'
import msvcrt
,
printf
,
'printf'
,
gets
,
'gets'
,
exit
,
'exit'
include
'api\kernel32.inc'
Из выхлопной трубы этого кода получим 4-файла с данными и сведениями о таблицах текущей мат.платы. На моём стационарном узле с Win7 и легаси-биосом на борту я получил следующий лог где видно, что область памяти
доступна на чтение и в сумме составляет 262.144 байт. Биосом реализовано всего восемь ACPI-таблиц, которые код сдампил в файл размером 9.939-байт:
https://forum.antichat.xyz/attachmen...3c2a6cc115.png
А вот, что возвращает этот-же код на моём ноуте Win-10 с UEFI..
Как видим, область памяти в диапазоне
уже не доступна, зато ACPI-таблиц вагон и целая тележка – аж 26 штук:
https://forum.antichat.xyz/attachmen...79ae5dd4a0.png
4. Чтение и разбор данных SMBIOS
Но вернёмся к базе-данных SMBIOS, и в следующем примере попытаемся вывести на консоль текстовые строки всех доступных структур. Можно было разбирать поля каждой из записи вплоть до каждого байта, но здесь я ставил перед собой иную задачу – продемонстрировать циклический обход всех записей по номерам их типов 00-127.
Суть в том, что мы берём первый байт очередной структуры (номер типа) и проверяем его на 127. Это значение является маркером последней записи, так-что встретив его сразу отправляемся на выход. Иначе, к адресу начала прибавляем второй байт структуры "StructLength" и оказываемся в начале текстовых строк, которые и выводим на консоль во-внутреннем цикле, пока не встретим пару терминальных нулей. Функция printf() возвращает в
количество распечатанных байт – внутри одной структуры их будем использовать для смещения к следующей строке текста. Вот наглядный пример алгоритма:
C-подобный:
Код:
format pe console
include
'win32ax.inc'
include
'equates\smbios.inc'
;
// подключаем инклуд SMBIOS (см.скрепку)
entry start
;
//----------
.
data
strucName dd type00
,
type01
,
type02
,
type03
,
type04
,
type05
,
type06
,
type07
;
// таблица указателей имён
dd type08
,
type09
,
type10
,
type11
,
type12
,
type13
,
type14
,
type15
dd type16
,
type17
,
type18
,
type19
,
type20
,
type21
,
type22
,
type23
dd type24
,
type25
,
type26
,
type27
,
type28
,
type29
,
type30
,
type31
dd type32
,
type33
,
type34
,
type35
,
type36
,
type37
,
type38
,
type39
dd type40
,
type41
,
type42
,
type33
,
type44
type00 db
10
,
10
,
'TYPE00 - BIOS Information:'
,
0
type01 db
10
,
10
,
'TYPE01 - System Info:'
,
0
type02 db
10
,
10
,
'TYPE02 - Baseboard:'
,
0
type03 db
10
,
10
,
'TYPE03 - Chassis:'
,
0
type04 db
10
,
10
,
'TYPE04 - Processor:'
,
0
type05 db
10
,
10
,
'TYPE05 - Memory Controller:'
,
0
type06 db
10
,
10
,
'TYPE06 - Memory Module:'
,
0
type07 db
10
,
10
,
'TYPE07 - Cache Information:'
,
0
type08 db
10
,
10
,
'TYPE08 - Port Connector:'
,
0
type09 db
10
,
10
,
'TYPE09 - System Slots:'
,
0
type10 db
10
,
10
,
'TYPE10 - On Board Devices:'
,
0
type11 db
10
,
10
,
'TYPE11 - OEM Strings:'
,
0
type12 db
10
,
10
,
'TYPE12 - System Configuration Options:'
,
0
type13 db
10
,
10
,
'TYPE13 - BIOS Language:'
,
0
type14 db
10
,
10
,
'TYPE14 - Group Associations:'
,
0
type15 db
10
,
10
,
'TYPE15 - System Event Log:'
,
0
type16 db
10
,
10
,
'TYPE16 - Physical Memory Array:'
,
0
type17 db
10
,
10
,
'TYPE17 - Memory Device:'
,
0
type18 db
10
,
10
,
'TYPE18 - 32-Bit Memory Error:'
,
0
type19 db
10
,
10
,
'TYPE19 - Memory Array Mapped Address:'
,
0
type20 db
10
,
10
,
'TYPE20 - Memory Device Mapped Address:'
,
0
type21 db
10
,
10
,
'TYPE21 - Built-in Pointing Device:'
,
0
type22 db
10
,
10
,
'TYPE22 - Portable Battery:'
,
0
type23 db
10
,
10
,
'TYPE23 - System Reset:'
,
0
type24 db
10
,
10
,
'TYPE24 - Hardware Security:'
,
0
type25 db
10
,
10
,
'TYPE25 - System Power Controls:'
,
0
type26 db
10
,
10
,
'TYPE26 - Voltage Probe:'
,
0
type27 db
10
,
10
,
'TYPE27 - Cooling Device:'
,
0
type28 db
10
,
10
,
'TYPE28 - Temperature Probe:'
,
0
type29 db
10
,
10
,
'TYPE29 - Electrical Current Probe:'
,
0
type30 db
10
,
10
,
'TYPE30 - Out-of-Band Remote Access:'
,
0
type31 db
10
,
10
,
'TYPE31 - Boot Integrity Services (BIS) Entry Point:'
,
0
type32 db
10
,
10
,
'TYPE32 - System Boot Information:'
,
0
type33 db
10
,
10
,
'TYPE33 - 64-Bit Memory Error:'
,
0
type34 db
10
,
10
,
'TYPE34 - Management Device:'
,
0
type35 db
10
,
10
,
'TYPE35 - Management Device Component:'
,
0
type36 db
10
,
10
,
'TYPE36 - Management Device Threshold Data:'
,
0
type37 db
10
,
10
,
'TYPE37 - Memory Channel:'
,
0
type38 db
10
,
10
,
'TYPE38 - IPMI Device Information:'
,
0
type39 db
10
,
10
,
'TYPE39 - System Power Supply:'
,
0
type40 db
10
,
10
,
'TYPE40 - Additional Information:'
,
0
type41 db
10
,
10
,
'TYPE41 - Onboard Devices Extended:'
,
0
type42 db
10
,
10
,
'TYPE42 - Management Controller Host Interface:'
,
0
type43 db
10
,
10
,
'TYPE43 - TPM Device:'
,
0
type44 db
10
,
10
,
'TYPE44 - Processor Additional Information:'
,
0
align
16
buff rb
256
*
1024
;
//----------
.
code
start
:
invoke SetConsoleTitle
,
cinvoke printf
,
;
//===== Читаем базу SMB в приёмный буфер
mov eax
,
'RSMB'
bswap eax
invoke GetSystemFirmwareTable
,
eax
,
0
,
buff
,
256
*
1024
;
//===== Выводим версию SMBIOS из структуры "RawSMBIOSData"
mov esi
,
buff
;
// адрес начала SMB-базы
movzx eax
,
[
esi
+
RawSMBIOSData
.
MjVer
]
;
// версия мажор
movzx ebx
,
[
esi
+
RawSMBIOSData
.
MnVer
]
;
// версия минор
mov ecx
,
[
esi
+
RawSMBIOSData
.
Length
]
;
// размер базы
cinvoke printf
,
,
eax
,
ebx
,
ecx
mov esi
,
buff
+
8
;
// смещаемся в буфере к первой структуре
@printSMBdata
:
;
//
movzx eax
,
byte
[
esi
]
;
// EAX = номер/тип очередной структуры
cmp eax
,
127
;
// проверить на последнюю структуру
je @exit
;
// если достигли конца..
shl eax
,
2
;
// иначе: делаем номер индексом в таблице имён
mov ebx
,
strucName
;
// EBX = начало таблицы имён
mov eax
,
[
ebx
+
eax
]
;
// вычислить смещение
push esi
cinvoke printf
,
,
eax
;
// описание типа на консоль
pop esi
movzx ebx
,
byte
[
esi
+
1
]
;
// EBX = второй байт из структуры (размер заголовка)
add esi
,
ebx
;
// получить адрес начала текстовых строк
@@
:
push esi
cinvoke printf
,
,
esi
;
// вывести очередную строку на консоль
pop esi
add esi
,
eax
;
// получить адрес следующей строки
dec esi
;
// коррекция указателя
cmp byte
[
esi
]
,
0
;
// проверить на терминальную пару нулей
jne @b
;
// продолжить, если нет
inc esi
;
// иначе: смещаем указатель на сл.структуру
jmp @printSMBdata
;
// прогнать цикл до типа(127)
@exit
:
cinvoke gets
,
buff
;
// конец кода!!!
cinvoke exit
,
0
;
//----------
section
'.idata'
import data readable
library msvcrt
,
'msvcrt.dll'
,
kernel32
,
'kernel32.dll'
import msvcrt
,
printf
,
'printf'
,
gets
,
'gets'
,
exit
,
'exit'
include
'api\kernel32.inc'
https://forum.antichat.xyz/attachmen...c75ed4fa5f.png
Обратите внимание, что заполнение полей в структурах полностью лежит на совести разработчика BIOS/UEFI. Судя по этим логам, AMI (на правом скрине) оставляет большинство из полей пустыми, не забывая отмечать сей факт маркерами "To Be Filled By OEM" (должно быть заполнено производителем). Зато инженеры "Lenovo" на моём буке с UEFI стараются не пренебрегать деталями, по возможности заполняя все теги соответствующей информацией.
Как было сказано выше, здесь я упустил парсинг заголовков структур, где в закодированном виде хранятся более специфичные характеристики – в реальном софте это является обязательным условием, по аналогии с программой "RW-Utility". Именно для таких случаев я создал инклуд с описанием структур и прикрепил его в скрепке. Там-же найдёте два представленных выше исполняемых файла, если вдруг возникнет желание протестировать их на своей машине.
5. Заключение.
Согласно отчётам группы DMTF, на данный момент 2 млрд. клиентских и серверных операционных систем реализуют SMBIOS на своём борту. С 2000-года Microsoft начала требовать, чтобы производители материнских плат и поставщики биос поддерживали SMBIOS, для получения их сертификата. Это говорит о том, что программный интерфейс SMB активно развивается и сбрасывать его со-счетов не нужно. Особый интерес в этом направлении представляют ACPI-таблицы, но это тема заслуживает отдельной статьи и мы к ней как-нибудь вернёмся. А пока ставлю здесь точку, удачи и до скорого.
|