• Курсы Академии Кодебай, стартующие в мае - июне, от команды The Codeby

    1. Цифровая криминалистика и реагирование на инциденты
    2. ОС Linux (DFIR) Старт: 16 мая
    3. Анализ фишинговых атак Старт: 16 мая Устройства для тестирования на проникновение Старт: 16 мая

    Скидки до 10%

    Полный список ближайших курсов ...

Статья Программирование HDD/SSD. Часть.2 – практика

Продолжим начатую тему и в этой части обсудим:

1. Стек драйверов
2. Функция DeviceIoControl()
3. Взаимодействие через ATA интерфейс

• Перечисление накопителей
• Идентификация устройств
• S.M.A.R.T – состояние IDE диска
4.. Заключение
---------------------------------------------



Стек драйверов

Прикладная программа общается с физ.устройством через его драйвер, т.к. запросы должны преодолеть запретную черту из юзера в кернел. Как-правило, одно устройство (в нашем случае диск) на разном уровне обслуживает сразу несколько связанных в цепочку драйверов – в литературе, эту цепочку назвали "стеком-драйверов". В то-же время, пользователь не может вызывать функции драйвера напрямую, поэтому система подключает посредников, в лице диспетчера ввода-вывода I/O Manager (по требованию юзера формирует пакеты запроса на ввод-вывод IRP), и диспетчера объектов Object Manager (прописывает символические ссылки на каждый из драйверов в системном пространстве имён).

На рисунке ниже, скрин программы "WinObj" для каталога GLOBAL?? Как видим имя PhysicalDrive0, которое мы передаём функции CreateFile() при открытии физ.диска, в действительности является ссылкой на объект \Device\Harddisk0\DR0 в пространстве имён диспетчера-объектов. Отметим, что любое имя в папке GLOBAL?? может использоваться в аргументах CreateFile(), т.к. эта папка специально создана для юзера и содержит исключительно ссылки на реальные объекты. На моей тестовой машине установлено два HDD и один USB-flash, соответственно в каталоге \Device видны три устройства DR0-DR2:

WinObj_1.png


Диспетчер Plug-and-Play в непрерывном режиме следит за состоянием системной шины PCI на предмет подключённых к ней контролёров физ.устройств. Как только таковой обнаруживается (причём это может быть горячее подключение типа USB/SATA/SAS), диспетчер PnP оповещает об этом драйвер шины pci.sys. Дальше, управление получает драйвер IDE-шины pciide.sys, который является первым драйвером в стеке – по сути он и выстраивает весь остальной стек. Здесь, этапы формирования пронумерованы как 0..4, а пользовательский запрос движется в обратном направлении 4-3-2:


Drv-Stack.png


Кол-во драйверов в стеке не ограничено, хотя на практике чаще используют от двух до пяти уровней (слишком большое их кол-во может снизить скорость ввода-вывода). Аппаратный драйвер pciidex.sys ничего не знает о файловой системе и занимается только внутренними делами накопителя, оперируя на уровне байтов. Драйвер среднего уровня atapi.sys уже более приближён к пользователю и организует блочный обмен данными. Дальше идут класс-драйверы cdRom и disk.sys, которые являются высоко-уровневыми, хотя и над ними имеется не показанный здесь драйвер файловой системы ntfs.sys. Драйверы класса должны обрабатывать все пользовательские запросы, и только если его не существует (бывает и такое для некоторых устройств), приложение может направить запрос напрямую драйверу порта.

Наш запрос оформляется в виде структуры IRP – I/O Request Packet, пакет запроса на в/в. Диспетчер ввода-вывода передаёт его всем драйверам в стеке, а обрабатывать запрос или нет, решает уже сам драйвер. Для этого, в хвосте структуры IRP имеются несколько вложенных структур IO_STACK_LOCATION (по одной для каждого драйвера в стеке), а кол-во таких структур в одном пакете указывается в поле "StackCount" по смещению 0х22. Вот как представляет это ядерный отладчик WinDbg:


IRP-example.png


Кроме того, поле по смещению 0х3С хранит указатель на буфер, который передаёт драйверу функция DeviceIoControl() (см.параметр LpInBuffer). Перед тем-как отправить запрос, диспетчер ввода-вывода помещает в структуру IO_STACK_LOCATION старший и младший код-операции, Major и Minor соответственно. Каждый из драйверов в стеке анализирует эти поля и если в нём не реализована процедура обработки данного мажор-кода, то просто игнорирует запрос. Всего системой определено 28 мажорных кодов, список которых представлен ниже. Названия кодов говорят сами за себя, а приоритетным для нас будет IRP_MJ_DEVICE_CONTROL (управление устройством) со-значением 0х0Е:

Код:
// Define the major function codes for IRPs. //
IRP_MJ_CREATE                   0x00
IRP_MJ_CREATE_NAMED_PIPE        0x01
IRP_MJ_CLOSE                    0x02
IRP_MJ_READ                     0x03
IRP_MJ_WRITE                    0x04
IRP_MJ_QUERY_INFORMATION        0x05
IRP_MJ_SET_INFORMATION          0x06
IRP_MJ_QUERY_EA                 0x07
IRP_MJ_SET_EA                   0x08
IRP_MJ_FLUSH_BUFFERS            0x09
IRP_MJ_QUERY_VOLUME_INFORMATION 0x0a
IRP_MJ_SET_VOLUME_INFORMATION   0x0b
IRP_MJ_DIRECTORY_CONTROL        0x0c
IRP_MJ_FILE_SYSTEM_CONTROL      0x0d
IRP_MJ_DEVICE_CONTROL           0x0e   ;// <--- наш клиент!
IRP_MJ_INTERNAL_DEVICE_CONTROL  0x0f
IRP_MJ_SHUTDOWN                 0x10
IRP_MJ_LOCK_CONTROL             0x11
IRP_MJ_CLEANUP                  0x12
IRP_MJ_CREATE_MAILSLOT          0x13
IRP_MJ_QUERY_SECURITY           0x14
IRP_MJ_SET_SECURITY             0x15
IRP_MJ_POWER                    0x16
IRP_MJ_SYSTEM_CONTROL           0x17
IRP_MJ_DEVICE_CHANGE            0x18
IRP_MJ_QUERY_QUOTA              0x19
IRP_MJ_SET_QUOTA                0x1a
IRP_MJ_PNP                      0x1b

Функция DeviceIoControl()

Будем считать, что с адресатом поверхностно разобрались – теперь об отправителе..
В составе системной библиотеки kernel32.dll есть интересная функция DeviceIoControl(). Прямо из пользовательского приложения она позволяет обращаться к драйверам буквально всех физических устройств в системе. Вот её прототип (при ошибке возвращает логический нуль):


C-подобный:
BOOL DeviceIoControl (
   HANDLE hDevice              ;// имя или дескриптор устройства
   DWORD dwIoControlCode       ;// IOCTL-код операции для выполнения
   LPVOID lpInBuffer           ;// указатель на буфер, для передачи доп.данных драйверу
   DWORD nInBufferSize         ;//   ..размер этого буфера
   LPVOID lpOutBuffer          ;// указатель на буфер, для приёма ответа от драйвера
   DWORD nOutBufferSize        ;//   ..размер этого буфера
   LPDWORD lpBytesReturned     ;// указатель на переменную, куда вернётся кол-во принятых байт
   LPOVERLAPPED lpOverlapped   ;// структура для асинхронной операции (Null)
);

Сложность использования этой функции в том, что ей можно передавать огромное количество IOCTL-кодов, от которых зависят остальные параметры и соответствующие им информационные структуры. Код выбирает одну из функций в драйвере устройства, и передаёт ей различные аргументы через третий свой параметр lpInBuffer. Соответственно содержимое передаваемого буфера не может быть статичным, и его определяет код-операции. С достаточно объёмным списком поддерживаемых семёркой IOCTL-кодов и их констант можно .

Под управляющим сообщением подразумевается IOCTL, который позволяет обращаться к драйверу диска с запросами, отличающимися от стандартных операций чтения/записи в устройство типа ReadFile() и прочие. Помимо запрашиваемой операции, внутри IOCTL имеются биты с определением "прав пользователя" и метода передачи lpInBuffer с дополнительной информацией:


IOCTL-code.png


В качестве примера, на схеме выше я привёл несколько кодов с их константами в hex и bin. Флаги и метод передачи буфера нам не интересны. Для пользователя с правами обычного юзера проблему представляют биты контроля [15:14], в результате чего DeviceIoControl() может возвратить ошибку "Не достаточно прав". Это ограничение не распространяется на админа, поскольку ему всегда открыт доступ на запись (см.левую табличку).

Старшая половина кода с битами [30:16] отведена под "тип устройства", к которому обращается функция. Выделив эту составляющую, диспетчер ввода-вывода делает вывод, какому именно девайсу адресован запрос. В файле Ntddk.h можно найти определения этих типов, а в демо-примерах ниже, клиентами наших запросов будут только типы устройств: 0x04 =Controller, 0x07 =Disk и 0x2d =MassStorage (устройства хранения данных):

C-подобный:
// Define the various device type values.  Note that values used by Microsoft
// Corporation are in the range 0-32767, and 32768-65535 are reserved for use
// by customers.
//
#define FILE_DEVICE_BEEP                0x00000001
#define FILE_DEVICE_CD_ROM              0x00000002
#define FILE_DEVICE_CD_ROM_FILE_SYSTEM  0x00000003
#define FILE_DEVICE_CONTROLLER          0x00000004
#define FILE_DEVICE_DATALINK            0x00000005
#define FILE_DEVICE_DFS                 0x00000006
#define FILE_DEVICE_DISK                0x00000007
#define FILE_DEVICE_DISK_FILE_SYSTEM    0x00000008
#define FILE_DEVICE_FILE_SYSTEM         0x00000009
#define FILE_DEVICE_INPORT_PORT         0x0000000a
#define FILE_DEVICE_KEYBOARD            0x0000000b
#define FILE_DEVICE_MAILSLOT            0x0000000c
#define FILE_DEVICE_MIDI_IN             0x0000000d
#define FILE_DEVICE_MIDI_OUT            0x0000000e
#define FILE_DEVICE_MOUSE               0x0000000f
#define FILE_DEVICE_MULTI_UNC_PROVIDER  0x00000010
#define FILE_DEVICE_NAMED_PIPE          0x00000011
#define FILE_DEVICE_NETWORK             0x00000012
#define FILE_DEVICE_NETWORK_BROWSER     0x00000013
#define FILE_DEVICE_NETWORK_FILE_SYSTEM 0x00000014
#define FILE_DEVICE_NULL                0x00000015
#define FILE_DEVICE_PARALLEL_PORT       0x00000016
#define FILE_DEVICE_PHYSICAL_NETCARD    0x00000017
#define FILE_DEVICE_PRINTER             0x00000018
#define FILE_DEVICE_SCANNER             0x00000019
#define FILE_DEVICE_SERIAL_MOUSE_PORT   0x0000001a
#define FILE_DEVICE_SERIAL_PORT         0x0000001b
#define FILE_DEVICE_SCREEN              0x0000001c
#define FILE_DEVICE_SOUND               0x0000001d
#define FILE_DEVICE_STREAMS             0x0000001e
#define FILE_DEVICE_TAPE                0x0000001f
#define FILE_DEVICE_TAPE_FILE_SYSTEM    0x00000020
#define FILE_DEVICE_TRANSPORT           0x00000021
#define FILE_DEVICE_UNKNOWN             0x00000022
#define FILE_DEVICE_VIDEO               0x00000023
#define FILE_DEVICE_VIRTUAL_DISK        0x00000024
#define FILE_DEVICE_WAVE_IN             0x00000025
#define FILE_DEVICE_WAVE_OUT            0x00000026
#define FILE_DEVICE_8042_PORT           0x00000027
#define FILE_DEVICE_NETWORK_REDIRECTOR  0x00000028
#define FILE_DEVICE_BATTERY             0x00000029
#define FILE_DEVICE_BUS_EXTENDER        0x0000002a
#define FILE_DEVICE_MODEM               0x0000002b
#define FILE_DEVICE_VDM                 0x0000002c
#define FILE_DEVICE_MASS_STORAGE        0x0000002d
#define FILE_DEVICE_SMB                 0x0000002e
#define FILE_DEVICE_KS                  0x0000002f
#define FILE_DEVICE_CHANGER             0x00000030
#define FILE_DEVICE_SMARTCARD           0x00000031
#define FILE_DEVICE_ACPI                0x00000032
#define FILE_DEVICE_DVD                 0x00000033
#define FILE_DEVICE_FULLSCREEN_VIDEO    0x00000034
#define FILE_DEVICE_DFS_FILE_SYSTEM     0x00000035
#define FILE_DEVICE_DFS_VOLUME          0x00000036
#define FILE_DEVICE_SERENUM             0x00000037
#define FILE_DEVICE_TERMSRV             0x00000038
#define FILE_DEVICE_KSEC                0x00000039
#define FILE_DEVICE_FIPS                0x0000003A
#define FILE_DEVICE_INFINIBAND          0x0000003B
#define FILE_DEVICE_VMBUS               0x0000003E
#define FILE_DEVICE_CRYPT_PROVIDER      0x0000003F
#define FILE_DEVICE_WPD                 0x00000040
#define FILE_DEVICE_BLUETOOTH           0x00000041
#define FILE_DEVICE_MT_COMPOSITE        0x00000042
#define FILE_DEVICE_MT_TRANSPORT        0x00000043
#define FILE_DEVICE_BIOMETRIC           0x00000044
#define FILE_DEVICE_PMI                 0x00000045
#define FILE_DEVICE_EHSTOR              0x00000046
#define FILE_DEVICE_DEVAPI              0x00000047
#define FILE_DEVICE_GPIO                0x00000048
#define FILE_DEVICE_USBEX               0x00000049
#define FILE_DEVICE_CONSOLE             0x00000050
#define FILE_DEVICE_NFP                 0x00000051
#define FILE_DEVICE_SYSENV              0x00000052
#define FILE_DEVICE_VIRTUAL_BLOCK       0x00000053
#define FILE_DEVICE_POINT_OF_SERVICE    0x00000054
#define FILE_DEVICE_STORAGE_REPLICATION 0x00000055
#define FILE_DEVICE_TRUST_ENV           0x00000056
#define FILE_DEVICE_UCM                 0x00000057
#define FILE_DEVICE_UCMTCPCI            0x00000058


Взаимодействие через ATA интерфейс
1) Перечисление подключённых накопителей


Начнём с обычного обхода всех дисковых устройств..
Для этого можно воспользоваться переданным в DeviceIoControl() кодом IOCTL_STORAGE_QUERY_PROPERTY. Согласно мелкомягких, к этому коду мы должны приложить буфер с дополнительной информацией STORAGE_PROPERTY_QUERY, структура которого выглядит так:


C-подобный:
struct STORAGE_PROPERTY_QUERY
   PropertyId             dd  0     ;// идентификатор запроса (смотри "STORAGE_PROPERTY_ID" ниже)
   QueryType              dd  0     ;// нуль = стандартный запрос
   AdditionalParameters   rd  30    ;// доп.параметры (нам не нужны)
ends

Здесь, наиболее важным является первый дворд PropertyId – это расширение основного IOCTL-кода, и оно может принимать одно из 33-х значений в диапазоне 0..32. В своём примере я использовал нулевой уточняющий код, что означает StorageDeviceProperty (свойства девайса хранения данных). В списке ниже перечислены другие возможные варианты, которые можете самостоятельно испробовать на досуге:

Код:
;/////////////
;// Константы "STORAGE_PROPERTY_ID" =========================
;// Перечисляет возможные значения члена PropertyId структуры "STORAGE_PROPERTY_QUERY",
;// переданного в качестве входных данных в запрос "IOCTL_STORAGE_QUERY_PROPERTY"
;// для получения свойств устройства хранения или адаптера.
;//==========================================================
   StorageDeviceProperty                    =  0
   StorageAdapterProperty                   =  1
   StorageDeviceIdProperty                  =  2
   StorageDeviceUniqueIdProperty            =  3
   StorageDeviceWriteCacheProperty          =  4
   StorageMiniportProperty                  =  5
   StorageAccessAlignmentProperty           =  6
   StorageDeviceSeekPenaltyProperty         =  7
   StorageDeviceTrimProperty                =  8
   StorageDeviceWriteAggregationProperty    =  9
   StorageDeviceDeviceTelemetryProperty     =  10
   StorageDeviceLBProvisioningProperty      =  11
   StorageDevicePowerProperty               =  12
   StorageDeviceCopyOffloadProperty         =  13
   StorageDeviceResiliencyProperty          =  14
   StorageDeviceMediumProductType           =  15
   StorageAdapterRpmbProperty               =  16
   StorageAdapterCryptoProperty             =  17
   StorageDeviceIoCapabilityProperty        =  18
   StorageAdapterProtocolSpecificProperty   =  19
   StorageDeviceProtocolSpecificProperty    =  20
   StorageAdapterTemperatureProperty        =  21
   StorageDeviceTemperatureProperty         =  22
   StorageAdapterPhysicalTopologyProperty   =  23
   StorageDevicePhysicalTopologyProperty    =  24
   StorageDeviceAttributesProperty          =  25
   StorageDeviceManagementStatus            =  26
   StorageAdapterSerialNumberProperty       =  27
   StorageDeviceLocationProperty            =  28
   StorageDeviceNumaProperty                =  29
   StorageDeviceZonedDeviceProperty         =  30
   StorageDeviceUnsafeShutdownCount         =  31
   StorageDeviceEnduranceProperty           =  32

Если функция DeviceIoControl() отработает без ошибки (должна вернуть EAX=1), на выходе получим буфер с запрошенной информацией, который в красках описывает структура STORAGE_DEVICE_DESCRIPTOR и нам остаётся только выводить на консоль интересные на наш взгляд поля:

C-подобный:
struct STORAGE_DEVICE_DESCRIPTOR
   Version                dd  0    ;// размер этой структуры
   Size                   dd  0    ;// общий размер возвращённых данных
   DeviceType             db  0    ;// тип устройства
   DeviceTypeModifier     db  0    ;// модификатор
   RemovableMedia         db  0    ;// 1 = съёмный носитель
   CommandQueueing        db  0    ;// 1 = поддерживает NCQ (очередь команд SATA-2, SCSI)
   VendorIdOffset         dd  0    ;// смещение к строке с именем поставщика
   ProductIdOffset        dd  0    ;// смещение к имени устройства
   ProductRevisionOffset  dd  0    ;// смещение к ревизии
   SerialNumberOffset     dd  0    ;// смещение к серийнику лиска
   BusType                dd  0    ;// тип шины
   RawPropertiesLength    dd  0    ;// кол-во специфичных для вендора байт
   RawDeviceProperties    rb  512  ;// специфичные данные
ends

Из достоинств этого IOCTL-кода можно выделить только то, что он перечисляет буквально все накопители в системе, включая USB-брелки и накопители на лазерных дисках ATAPI. Однако на этом достоинства и заканчиваются. К примеру, программа ниже на моей машине с Win-7 возвращает какой-то левый серийный номер диска, не имеющий ничего общего с реальным s/n. Так-что доверять ему полностью нельзя, хотя на Win-10 серийник получаю валидный. Значит дело тут не в коде, а в драйверах, к которым идёт обращение с соответствующим запросом. Запушим этот факт в черепную коробку..

В своей демке, я в цикле открываю все диски по их именам в пространстве имён диспетчера-объектов, пока функция CreateFile() не вернёт ошибку. На каждой итерации цикла, достаточно увеличивать только номер диска типа PhysicalDrive0, дальше 1 и т.д. После того-как с дисками будет покончено, переходим к сидюкам, которые перечисляются аналогичным образом CdRom0, CdRom1 и т.д. Вот пример реализации задуманного..


C-подобный:
format    pe console
include  'win32ax.inc'
include  'equates\storage.inc'
entry     start
;//---------
.data
title     db  'STORAGE_QUERY - ver.0.1',0
dNameHdd  db  '\\.\'
devHdd    db  'PhysicalDrive0',0       ;// имена дисков в пространстве имён
dNameCd   db  '\\.\'
devCd     db  'CdRom0',0               ;// имена DVD-ROM'ов

inBuff    STORAGE_PROPERTY_QUERY       ;// структура, которую передаём драйверу вместе с IOCTL-кодом
outBuff   STORAGE_DEVICE_DESCRIPTOR    ;// структура, которую принимаем от драйвера

hndl      dd  0       ;// под дескриптор устройства
retSize   dd  0       ;// будет кол-во возвращённых байт
reserve   db  0       ;// в хозяйстве пригодится..
;//---------

.code
start:  invoke  SetConsoleTitle,title

;//==== Заполняем передаваемый буфер ====================
        mov     [inBuff.PropertyId],StorageDeviceProperty
        mov     [inBuff.QueryType],PropertyStandardQuery
        mov     [inBuff.AdditionalParameters],0

;//==== Открываем устройство по имени (сначала диски) ============
@hdd:   invoke  CreateFile,dNameHdd,GENERIC_READ,FILE_SHARE_READ,\
                                    0,OPEN_EXISTING,0,0
        mov     [hndl],eax
        cmp     eax,-1
        je      @cd                                 ;// если ошибка..
       cinvoke  printf,<10,10,'Dev: %s',0>,devHdd   ;//
        call    GetDriveInfo                        ;// зовём процедуру с DeviceIoControl()

        inc     byte[devHdd+13]    ;// имя устройства +1
        jmp     @hdd               ;// повторить, пока CreateFile() не вернёт ошибку..

;//==== Открываем CD-ROM'ы по имени =============================
@cd:    invoke  CreateFile,dNameCd,GENERIC_READ,FILE_SHARE_READ,\
                                   0,OPEN_EXISTING,0,0
        mov     [hndl],eax
        cmp     eax,-1
        je      @exit
       cinvoke  printf,<10,10,'Dev: %s',0>,devCd
        call    GetDriveInfo

        inc     byte[devCd+6]
        jmp     @cd

@exit: cinvoke  gets,reserve  ;// ждём клаву..
       cinvoke  exit,0        ;// на выход!!!

;//---------
;//==== Функция DeviceIoControl() =====================================
;//==== передаём доп.информацию в буфере "inBuff", получаем в "outBuff"
proc    GetDriveInfo
        invoke  DeviceIoControl,[hndl],IOCTL_STORAGE_QUERY_PROPERTY,\
                                inBuff,3*4,outBuff,1024,retSize,0

       cinvoke  printf,<10,'     Vendor....: ',0>
        mov     eax,[outBuff.VendorIdOffset]      ;// указатель на строку вендора
        or      eax,eax                           ;// проверить на нуль
        je      @f                                ;// пропустить, если нет указателя..
        add     eax,outBuff                       ;// иначе: добавить смещение от начала буфера
       cinvoke  printf,<'%s',0>,eax               ;// выводим на консоль!

@@:    cinvoke  printf,<10,'     Product...: ',0>
        mov     eax,[outBuff.ProductIdOffset]
        or      eax,eax
        je      @f
        add     eax,outBuff
       cinvoke  printf,<'%s',0>,eax

@@:    cinvoke  printf,<10,'     Revision..: ',0>
        mov     eax,[outBuff.ProductRevisionOffset]
        or      eax,eax
        je      @f
        add     eax,outBuff
       cinvoke  printf,<'%s',0>,eax

@@:    cinvoke  printf,<10,'     Serial....: ',0>
        mov     eax,[outBuff.SerialNumberOffset]
        or      eax,eax
        je      @f
        add     eax,outBuff
       cinvoke  printf,<'%s',0>,eax

@@:     mov     eax,[outBuff.BusType]          ;// EAX = тип шины
        shl     eax,2                          ;// умножить на 4, чтобы получить смещение в таблице
        mov     esi,typeTbl                    ;// ESI = таблица возможных вариантов
        add     esi,eax                        ;// ESI = указатель на мессагу с типом шины
        mov     eax,[esi]                      ;// EAX = адрес сообщения.
       cinvoke  printf,<10,'     BusType...: %s',0>,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'

;//**********************************
;//===== Таблица с типом шин ===========================================
section '.rdata' data readable
typeTbl   dd   unk,scsi,atapi,ata,b1394,ssa,fibre,usb,raid,iscsi,sas,sata
unk       db  'Unknown',0
scsi      db  'SCSI',0
atapi     db  'ATAPI',0
ata       db  'ATA',0
b1394     db  '1394',0
ssa       db  'SSA',0
fibre     db  'FibreChanel',0
usb       db  'USB',0
raid      db  'RAID',0
iscsi     db  'iSCSI',0
sas       db  'SAS/NVM',0
sata      db  'SATA',0

Query_Property.png


Все необходимые константы и структуры для этого кода можно найти в инклудах, которые я прикрепил в скрепке. Там-же имеется программа WinObj для знакомства с пространством имён и типами системных объектов (двойной клин на строке раскрывает свойства).

Как видно из примера выше, зарытая в структуру STORAGE_DEVICE_DESCRIPTOR информация очень скудная, зато на запрос отзываются все накопители системы. Более детальный отчёт возвращает следующий IOCTL-код, в котором мы обращаемся уже не к драйверу диска, а напрямую к его контролёру, посредством низкоуровневых АТА-команд.



2) Идентификация IDE устройств

Из общего пространства накопителя, производитель выделяет одну сторону одного из дисков под свои сервисные данные – эта область не доступна пользователю и поддерживает работу только технической составляющей. Среди данных можно выделить две "таблицы дефектных секторов" – Primary (основная, заполняется на этапе тестирования продукта после схода его с конвейера), и Grow – растущая, для скрытия бэд-секторов в процессе эксплуатации устройства. Кроме того имеются служебные секторы для хранения логов S.M.A.R.T., а так-же 512-байтный сектор с исчерпывающим паспортом данного диска.

В своей массе, производитель не регламентирует координаты сервисных секторов, в результате чего мы не можем читать их посредством адресации CHS (Cylinder-Head-Sector) или последовательной LBA (Logical-Block-Address). В таких случаях приходится обращаться с запросами к контролёру диска, отправив ему только АТА-команду, не указывая при этом адрес конкретного сектора. Ясно, что интересы юзверов здесь вообще не учитываются – это нужно в первую очередь системному BIOS, чтобы он по паспорту смог корректно идентифицировать принятое на борт устройство.

Большинство из IOCTL-кодов для передачи АТА-команд имеют кредит доверия в высшем обществе, требуя админских прав. Это следует воспринимать как часть политики Win. Обычно, программисты передают АТА-команды через сквозной интерфейс PASS_THROUGH, который реализуется очень громоздко из-за присущей ему пакетной передачи данных. Однако имеется и более простой вариант, в лице управляющего кода IOCTL_SMART_RCV_DRIVE_DATA – рассмотрим его подробней..

К этому IOCTL привязывается структура SEND-CMD-IN-PARAM, а она (в свою очередь) имеет вложенную структуру IDE-REGS. По названию последней не трудно догадаться, что это бокс для физических регистров контролёра IDE. Соответственно чтобы передать АТА-команду, нам нужно всего-то указать её код в командном регистре 0x1F7 контролёра (см.предыдущую часть статьи), о чём свидетельствует таблица из спецификации
(стр.139):

Identify_command.png


Здесь, в столбце "Field" представлены регистры и как видим значимым является только последний CommandReg, а остальные ходят под флагом N/A (не используется). Контролёр их тупо игнорирует, поэтому можно пихать туда любые значения, но лучше придерживаясь этики, оставить по-нулям. АТА-команда IDENTIFY_DEVICE кодируется значением 0хЕС, которое определёно константой внутри инклуда в скрепке:


C-подобный:
;//
;// входные параметры для SMART_RCV_DRIVE_DATA, SMART_SEND_DRIVE_COMMAND
;//
struct SENDCMDINPARAMS
    cBufferSize          dd  sizeof.SENDCMDINPARAMS  ;// размер этой структуры
    irDriveRegs          IDEREGS     ;// вложенная структура с регистрами контролёра
    bDriveNumber         db  0       ;// номер диска нам не нужен, т.к. мы открываем его по имени
    bReserved            rb  19      ;// резерв..
ends

struct IDEREGS
    FeaturesReg         db  0   ;// регистр 0x1F1 – аргумент команды
    SectorCountReg      db  0   ;// регистр 0x1F2 – счётчик секторов IDE
    LBA_Low             db  0   ;// мл.байт сектора LBA
    LBA_Middle          db  0   ;// средний байт ^^^
    LBA_High            db  0   ;// старший байт ^^^
    DriveHeadReg        db  0   ;// регистр 0x1F6 – номер головки/устройства
    CommandReg          db  0   ;// <<<=== АТА-команда ===== регистр 0x1F7
    Reserved            db  0   ;// (байт выравнивания на 8)
ends

В конкретно данном случае получается, что если мы хотим отправить контролёру АТА-команду IDENTIFY_DEVICE, то в эти две структуры должны записать всего-лишь один код команды =0хЕС, и посредством запроса SMART_RCV_DRIVE_DATA передать его функции DeviceIoControl(). Если она отработает удачно, в приёмном буфере получим сначала 16-байтную структуру SENDCMDOUTPARAMS (с состоянием драйвера), и следом за ней сразу 512-байтный сектор данных – это и будет полезная нагрузка в виде паспорта диска.

В спецификации ATA-Command-Set можно найти подробное описание каждого байта паспорта, а я собрал их все в структуру IDENTIFY_SECTOR. Чтобы не утонуть в полезном коде, в демонстрационном примере вывожу на консоль только самые важные на мой взгляд данные. Натощак покурив ACS, вы можете добавить свои..

Имеется ещё один, связанный с паспортом нюанс.. По непонятной причине, в текстовых строках производители меняют два соседних байта местами. Зачем это нужно, так и осталось для меня загадкой. Так-что до вывода строк на консоль нужно приводить их в человеческий вид – я озадачил этим макрос SwapByte, которому передаю три аргумента: куда сбрасывать готовую строку, от куда её брать, и длину в байтах. Вот пример:


C-подобный:
format   pe console
include 'win32ax.inc'
include 'equates\storage.inc'       ;// см.инклуд в скрепке
entry    start
;//---------
.data
macro   SwapByte [Buff,Offset,Len]  ;// макрос меняет байты строки местами
{       mov     esi,Offset
        mov     edi,Buff
        mov     ecx,Len
        shr     ecx,1
@@:     lodsw
        xchg    ah,al
        stosw
        loop    @b
        mov     byte[edi],0
}

title     db  'IOCTL_SMART_RCV_DRIVE_DATA --> IDENTIFY_DEVICE',0
dName     db  '\\.\PhysicalDrive0',0
drive     dd  -1

ata       db  'ATA',0
sata      db  'SATA',0

retSize   dd  0
fpuBuff   dd  0,0
inBuff    SENDCMDINPARAMS     ;// структура для передачи драйверу
outBuff   SENDCMDOUTPARAMS    ;// заголовок приёмного буфера
idSec     IDENTIFY_SECTOR     ;// структура с описанием полей паспорта
prnBuff   db  0               ;// до конца секции-данных, под приведённые в порядок строки
;//---------
.code
start:  invoke  SetConsoleTitle,title

;//==== Открываем очередной диск ===============================
@@:     invoke  CreateFile,dName,GENERIC_READ or GENERIC_WRITE,\  ;// для АТА-команд нужны права на R/W
                           FILE_SHARE_READ,0,OPEN_EXISTING,0,0
        cmp     eax,-1
        jz      @exit               ;// ошибка!
        call    ReadIdentifySector  ;// иначе: зовём DeviceIoControl()
        inc     byte[dName+17]      ;// меняем номер диска на следующий
        jmp     @b                  ;// повторить до ошибки..

@exit: cinvoke  gets,prnBuff   ;//
       cinvoke  exit,0         ;// на выход!
;//---------

proc    ReadIdentifySector
;//==== Заполняем регистры контролёра диска ==================
        mov     [inBuff.irDriveRegs.FeaturesReg],0               ;// нет под-команды
        mov     [inBuff.irDriveRegs.CommandReg],IDENTIFY_DEVICE  ;// команда 0хЕС

;//==== Передаём буфер и зовём функцию =======================
        invoke   DeviceIoControl,eax,IOCTL_SMART_RCV_DRIVE_DATA,\
                   inBuff,sizeof.SENDCMDINPARAMS,\
                  outBuff,sizeof.SENDCMDOUTPARAMS + sizeof.IDENTIFY_SECTOR, retSize,0

        or       eax,eax      ;// проверить на ошибку нуль
        jne      @f           ;// вниз, если OK..

        add      esp,4        ;// иначе: убрать адрес-возврата из стека
       сinvoke   printf,<10,' :( Contact the admin',0>    ;// нет админских прав!
        jmp      @exit        ;// на выход!

;//==== Данные идентификации лежат в приёмном буфере =========
@@:     inc      [drive]
       cinvoke   printf,<10,10,' DRIVE %d ',\
                            10,' ************',0>,[drive]  ;// номер драйва
        mov      ebx,ata
        cmp      word[idSec.wSATAcapabilites],0
        je       @f
        mov      ebx,sata
@@:    cinvoke   printf,<10,'   Interface....: %s',0>,ebx      ;// ATA или SATA ???

       SwapByte  prnBuff,idSec.sModelNumber,40                 ;// восстановить порядок байт в строке
       cinvoke   printf,<10,'   Model........: %s',0>,prnBuff

       SwapByte  prnBuff,idSec.sSerialNumber,20
       cinvoke   printf,<10,'   Serial.......: %s',0>,prnBuff

       SwapByte  prnBuff,idSec.sFirmwareRev,8
       cinvoke   printf,<10,'   Revision.....: %s',0>,prnBuff

;// Берём макс.номер сектора LBA 48-бит, и получаем размер диска в гектарах.
;// Для расчётов воспользуемся FPU, умножив LBA на 512 (размер сектора):
        mov      eax,dword[idSec.ulTotalSectors48]
        mov      ebx,dword[idSec.ulTotalSectors48+4]
        push     512 ebx eax
        fild     qword[esp]
        fimul    word[esp+8]
        push     1024*1024*1024     ;// байты в Гигабайты
        fidiv    dword[esp]
        fstp     qword[fpuBuff]
        add      esp,4*4            ;// выровнить стек от пушей
       cinvoke   printf,<10,'   Capacity.....: %0.1f Gb',0>,dword[fpuBuff],dword[fpuBuff+4]

;// Смотри "ATA_Command_Set" стр.144
        movzx    eax,word[idSec.wSATAcapabilites]
        shr      eax,8
        and      eax,1
       cinvoke   printf,<10,'   Support NCQ..: %d',0>,eax

        movzx    eax,word[idSec.wAtaMajorVersion]
        shl      ax,8
        mov      ebx,7
        mov      ecx,3
@@:     shl      ax,1
        jc       @f
        dec      bl
        loop     @b
@@:    cinvoke   printf,<10,'   ATA/ATAPI....: %d',0>,ebx

        movzx    eax,word[idSec.wUltraDMAmode]
        shl      ax,9
        mov      ebx,6
        mov      ecx,7
@@:     shl      ax,1
        jc       @f
        dec      bl
        loop     @b
@@:     mov      eax,133
        cmp      bl,6
        je       @dma
        mov      eax,100
        cmp      bl,5
        je       @dma
        mov      eax,66
@dma:  cinvoke   printf,<10,'   Ultra DMA....: %d = %d Mb/sec',0>,ebx,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'

Identify_Device.png


Отметим, что это единственный способ получить заложенный вендором реальный серийник диска (именно диска, а не раздела или тома), поскольку мы берём его прямо из служебной зоны накопителя. Нужно учитывать, что производитель заполняет поля паспорта на своё усмотрение, хотя и придерживается общепринятых правил. Поэтому на низкопробных девайсах от наших друзей из страны восходящего солнца может получиться так, что некоторые поля будут пустыми, со-значением нуль. И ещё.. Только устройства ATA/SATA/ATAPI имеют сектор идентификации, и на USB-брелках его искать бесполезно.


3) S.M.A.R.T – состояние накопителей

Современные жёсткие диски HDD/SSD настолько развитый вид оборудования, что могут предсказывать свою смерть. Эта технология заложена в них на генном уровне и известна как SMART – Self Monitoring Analysis and Reporting Technology (самоконтроль, анализ и отчётность для оценки состояния жёсткого диска). В отличии от 512-байтного сектора с паспортом идентификации, в сервисной зоне накопителя данные SMART занимают целый цилиндр, а это 63 сектора или 32 Кб. Контроль за состоянием диска поддерживают огромное кол-во таблиц, куда специальная микро/программа прошивки сбрасывает 16 логов – вот их перечень:

SMART_Log_Table.png


На основе этих логов, по известному только производителю алгоритму вычисляется общее состояние диска. Нам, как рядовым пользователям детали реализации не интересны – нам подавай только готовый результат, для чего имеется основная (под)команда для работы со SMART – READ_DATA с соответствующим ей кодом 0хD0. Эта команда возвращает привычную нам таблицу, которую мы можем наблюдать в окне утилит по сбору информации о жёстких дисках типа Victoria, HD-Tune и прочие.

Чтобы прочитать SMART диска, в командный регистр 0x1F7 контролёра мы должны передать основную АТА-команду SMART_EXECUTE_CMD (константа 0хВ0), и дальше в регистре Feature 0x1F1 указать одну из следующих (под)команд, причём начальный сектор LBA производителями оговаривается и всегда будет равен 0xC24F00. Полный список того-чем можно оперировать в контексте SMART со-значением всех регистров, представлен в таблице ниже:

Smart_command.png


Здесь нужно уточнить некоторые детали.. Первое – это значение регистров 0x1F6 "DriveHead" и 0x1F2 "SectorCount". Современные контролёры уже давно перешли на LBA-адресацию секторов, поэтому поле с номером головки Head 0xA0 они тупо игнорируют и его можно не указывать. Если мы посылаем АТА-команды, то счётчик секторов так-же уходит на скамейку запасных, т.к. по коду основной команды контролёр на автомате вычисляет кол-во требуемых секторов. А вот если мы запрашиваем SMART_READ_LOG =0xD5, то в регистре адреса LBA-Low должны указать, какой именно из 16-ти логов нам нужен (см.первую таблицу).

Для прикладных программистов, интерес представляют обычно первые две команды. Следуя логике разработчиков, SMART_READ_DATA =0xD0 должна возвращать готовые к употреблению данные, однако на практике самого главного из всех значений почему-то среди этих данных нет – это пороговое значение атрибута, относительно которого пляшут все остальные. Вот здесь-то и приходит на помощь вторая команда SMART_READ_THRESHOLDS =0xD1. Рассмотрим, какие бывают атрибуты, и по какому принципу рассчитывается здоровье диска.

Всего технология SMART поддерживает 30 различных атрибутов, а вот их идентификаторов аж 255. Дело в том, что у накопителей HDD свой диапазон ID, а у дисков SSD свой. Поэтому общее кол-во приближается к отметке 255. В
можно найти перечень всех атрибутов с их идентификаторами, где попавшие под раздачу SSD выделены своей меткой. Под каждый атрибут отводится 12-байт с таким форматом:

SMART_Attributes.png


Значит в первом байте зарыт идентификатор (номер) атрибута, дальше идёт слово с флагами и если бит нуль в нём взведён, то показатель жизненно-важен. Потом следуют 2-байта – текущее значение атрибута и наихудшее. 4-байтное сырое поле RAW во многих случаях несёт в себе наиболее ценную информацию. В новом накопителе RAW практически всех атрибутов содержат нули, и при первом-же включении начнут меняться, ..например счётчики запусков, времени наработки и т.п..

Однако предсмертное состояние накопителя вычисляется на основании порогового значения атрибута Threshold. Пригодным к эксплуатации считается диск, у которого ни один из текущих атрибутов Current не ниже порогового значения! При его достижении, в ответ на команду SMART_RETURN_STATUS =0xDA контролёр будет возвращать статус BAD.

Это хорошо, только среди этих 12-ти байт значение Threshold всегда равно нулю (не возвращается командой READ_DATA), и его нужно запрашивать явно, второй отдельной командой SMART_READ_THRESHOLDS. В своём инклуде, я определил эти две таблицы в виде таких структур:


C-подобный:
struct SMART_ATTRIB
  AttributeId   db  0
  Flags         dw  0
  Current       db  0
  Worst         db  0
  RawData       dd  0
  AttrSpecific  dw  0
  Thresholds    db  0   ;// ----------------+
Ends                                        |
                                            |
struct SMART_ATTRIB_THRESHOLDS  ;//<--------+
  AttributeId   db  0
  Thresholds    db  0
  Reserved      rb  10
ends

Наконец-то пешком (как Ломоносов) мы подобралась к практической составляющей SMART..
Значит как и в предыдущем примере с паспортом диска, будем терзать функцию DeviceIoControl() запросом IOCTL_SMART_RCV_DRIVE_DATA, только в качестве основной команды передадим SMART_EXECUTE_CMD =0xB0, прицепив к ней (под)команду SMART_READ_DATA для чтения значений атрибутов, и следом SMART_READ_THRESHOLDS, чтобы выудить пороговые значения. После того-как получим от функции ответ, в цикле длинною в 30 итераций обойдём все атрибуты, по-ходу сравнивая текущие их значения с порогом. Если текущее окажется меньше, то включаем ревун, иначе – "в Багдаде всё спокойно". Вот пример и его выхлоп:


C-подобный:
format   pe console
include 'win32ax.inc'
include 'equates\storage.inc'
entry    start
;//---------
.data
title    db  'S.M.A.R.T. ver 0.1',0
dName    db  '\\.\PhysicalDrive'
name     db  '0',0
good     db  'Ok ',0
bad      db  'Bad',0
capt     db  10,'        Attribute ID                Life  Max Thresh Min   Raw Values'
         db  10,70 dup('='),0

retSize  dd  0
hndl     dd  0
attrId   dd  0

inBuff   dd  0,0,0
vendor   STORAGE_DEVICE_DESCRIPTOR

scip     SENDCMDINPARAMS
buff_1   rb  18           ;// приёмный буфер для атрибутов SMART
smartId  rb  512
buff_2   rb  18           ;// приёмный буфер для пороговых значений
smartTh  rb  512
;//---------

.code
start:  invoke  SetConsoleTitle,title

;//==== Заполняем регистры контролёра ======================================
        mov     [scip.irDriveRegs.LBA_Middle],SMART_LBA_MIDDLE_REG  ;// 0x4F
        mov     [scip.irDriveRegs.LBA_High],  SMART_LBA_HIGH_REG    ;// 0xC2
        mov     [scip.irDriveRegs.CommandReg],SMART_EXECUTE_CMD     ;// 0xB0 (основная команда)

;//==== Открываем очередной диск (нужны права админа) ======================
@@:     invoke  CreateFile,dName,GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ,\
                                 0,OPEN_EXISTING,0,0
        cmp     eax,-1
        jz      @exit         ;// выйти, если ошибка
        mov     [hndl],eax

;//==== Запрашиваем модель диска ===========================================
        invoke  DeviceIoControl,eax,IOCTL_STORAGE_QUERY_PROPERTY,\
                                inBuff,3*4,\
                                vendor,sizeof.STORAGE_DEVICE_DESCRIPTOR,\
                                retSize,0
        mov     eax,[vendor.ProductIdOffset]
        add     eax,vendor
       cinvoke  printf,<10,10,10,'Disk:   %s %s',0>,eax,capt

        call    GetDiskSMART
        inc     [name]        ;// сл.диск
        jmp     @b            ;// повторить..

@exit: cinvoke  gets,buff_1
       cinvoke  exit,0        ;// на выход!

;//---------
proc    GetDiskSMART
        mov     [scip.irDriveRegs.FeaturesReg],SMART_READ_DATA        ;// читаем SMART (доп.команда)
        invoke  DeviceIoControl,[hndl],IOCTL_SMART_RCV_DRIVE_DATA,\
                                scip,sizeof.SENDCMDINPARAMS,\
                                buff_1,512+16,retSize,0
        or      eax,eax
        jnz     @ok
        ret

@ok:    mov     [scip.irDriveRegs.FeaturesReg],SMART_READ_THRESHOLDS  ;// читаем пороговые значения
        invoke  DeviceIoControl,[hndl],IOCTL_SMART_RCV_DRIVE_DATA,\
                                scip,sizeof.SENDCMDINPARAMS,\
                                buff_2,512+16,retSize,0

;//==== Теперь данные SMART в двух буферах ========================
        mov     edi,smartId          ;// EDI = указатель на атрибуты
        mov     ebp,smartTh          ;// EBP = их пороговые значения
        mov     ecx,30               ;// всего атрибутов (цикл)
@@:     push    ebp edi ecx          ;// запомнить для сл.итерации

        movzx   eax,byte[edi+SMART_ATTRIB.AttributeId]  ;// ID очередного атрибута..
        mov     [attrId],eax         ;// запомнить для вывода на консоль
        or      al,al                ;// пропустить,
        je      @unk                 ;//   ..если нуль (вендор не определил)

        mov     ecx,[tableSize]      ;// иначе: ищем его имя в таблице (ECX = всего)
        mov     ebx,eax              ;// EBX = ID атрибута
        mov     esi,smartTable       ;// ESI = адрес таблицы
@find:  lodsd                        ;// EAX = ID из таблицы
        cmp     eax,ebx              ;// сравнить
        je      @stop                ;// если совпало..
        add     esi,4                ;// иначе: следующий в таблице
        loop    @find                ;// промотать ECX-раз..
        jmp     @unk                 ;// прокол :(

;//==== Имя нашли, и берём остальные данные из обеих таблиц ======
@stop:  mov     eax,[esi]
        movzx   ebx,byte[edi+SMART_ATTRIB.Current]
        movzx   ecx,byte[edi+SMART_ATTRIB.Worst]
        mov     edx,dword[edi+SMART_ATTRIB.RawData]
        movzx   esi,byte[ebp+SMART_ATTRIB_THRESHOLDS.Thresholds]
        mov     edi,good
        cmp     ebx,esi          ;// сравнить Current и Threshold
        jae     @good            ;// больше/равно = Гуд
        mov     edi,bad
@good: cinvoke  printf,<10,'%03d %s  %s  %03d   %02d   %03d   %u',0>,\
                                  [attrId],eax,edi,ebx,esi,ecx,edx

;//==== Один атрибут распарсили, переходим к следующему ===========
@unk:   pop     ecx edi ebp      ;// восстановить длину и указатели
        add     edi,12           ;// перейти к следующим..
        add     ebp,12           ;// ^^^
        dec     ecx              ;// всего атрибутов -1
        jnz     @b               ;// повторить, если не нуль
        ret
endp

;//**********************************
;// Таблица идентификаторов и указателей на их строки
section '.smart' data readable
smartTable  dd   0x01,@01,0x02,@02,0x03,@03,0x04,@04,0x05,@05,0x06,@06
            dd   0x07,@07,0x08,@08,0x09,@09,0x0A,@0A,0x0B,@0B,0x0C,@0C
            dd   0x0D,@0D,0x64,@64,0x67,@67,0xAA,@AA,0xAB,@AB,0xAC,@AC
            dd   0xAD,@AD,0xAE,@AE,0xAF,@AF,0xB0,@B0,0xB1,@B1,0xB2,@B2
            dd   0xB3,@B3,0xB4,@B4,0xB5,@B5,0xB6,@B6,0xB7,@B7,0xB8,@B8
            dd   0xBB,@BB,0xBC,@BC,0xBD,@BD,0xBE,@BE,0xBF,@BF,0xC0,@C0
            dd   0xC1,@C1,0xC2,@C2,0xC3,@C3,0xC4,@C4,0xC5,@C5,0xC6,@C6
            dd   0xC7,@C7,0xC8,@C8,0xC9,@C9,0xCA,@CA,0xCB,@CB,0xCC,@CC
            dd   0xCD,@CD,0xCE,@CE,0xCF,@CF,0xD1,@D1,0xDC,@DC,0xDD,@DD
            dd   0xDE,@DE,0xDF,@DF,0xE0,@E0,0xE1,@E1,0xE2,@E2,0xE3,@E3
            dd   0xE4,@E4,0xE6,@E6,0xE7,@E7,0xEA,@EA,0xF0,@F0,0xF1,@F1
            dd   0xF2,@F2,0xF7,@F7,0xF8,@F8,0xFA,@FA,0xFE,@FE
tableSize   dd  ($ - smartTable) /8

;// https://ru.wikipedia.org/wiki/S.M.A.R.T.
;// Наиболее важные атрибуты: 05,07,0A,C4,C5,C6,C8
;// ==================================================
@01        db  ' Raw Read Error Rate           ',0
@02        db  ' Throughput Performance        ',0
@03        db  ' Spin-Up Time                  ',0
@04        db  ' Start/Stop Count              ',0
@05        db  ' Realocation Sector Count      ',0
@06        db  ' Read Channel Margin           ',0
@07        db  ' Seek Error Rate               ',0
@08        db  ' Seek Time Performance         ',0
@09        db  ' Power-On Hours Count          ',0
@0A        db  ' Spin Retry Count              ',0
@0B        db  ' Recalibration Retries         ',0
@0C        db  ' Power Cycle Count             ',0
@0D        db  ' Soft-Read Error Rate          ',0
@64        db  ' SSD Erase/Program Cycles      ',0
@67        db  ' SSD Translation Table Rebuild ',0
@AA        db  ' SSD Reserved Block Count      ',0
@AB        db  ' SSD Program Fail Count        ',0
@AC        db  ' SSD Erase Fail Count          ',0
@AD        db  ' Wear Leveller Worst Case Erase',0
@AE        db  ' SSD Unexpected Power Loss     ',0
@AF        db  ' SSD Program Fail Count        ',0
@B0        db  ' SSD Erase Fail Count          ',0
@B1        db  ' SSD Wear Leveling Count       ',0
@B2        db  ' SSD Used Reserved Block Count ',0
@B3        db  ' SSD Used Reserved Block Count ',0
@B4        db  ' SSD Unused Reserved Block     ',0
@B5        db  ' SSD Program Fail Count        ',0
@B6        db  ' SSD Erase Fail Count          ',0
@B7        db  ' SSD SATA Down-shifts          ',0
@B8        db  ' End-to-End Error              ',0
@BB        db  ' Reported UNC Errors           ',0
@BC        db  ' Command Timeout               ',0
@BD        db  ' High Fly Writes               ',0
@BE        db  ' Airflow Temperature (WDC)     ',0
@BF        db  ' G-Sense Error Rate (Shock)    ',0
@C0        db  ' Power-off Retract Count       ',0
@C1        db  ' Load/Unload Cycle Count       ',0
@C2        db  ' Temperature *C                ',0
@C3        db  ' Hardware ECC Recovered        ',0
@C4        db  ' Realocation Event Count       ',0
@C5        db  ' Current Pending Sector Count  ',0
@C6        db  ' Uncorrectable Sector Count    ',0
@C7        db  ' Ultra-DMA CRC Error Count     ',0
@C8        db  ' Write Error Rate              ',0
@C9        db  ' Soft Read Error Rate          ',0
@CA        db  ' Data Address Mark Errors      ',0
@CB        db  ' Run Out Cancel                ',0
@CC        db  ' Soft ECC Correction           ',0
@CD        db  ' Thermal Asperity Rate (TAR)   ',0
@CE        db  ' Flying Height                 ',0
@CF        db  ' Spin High Current             ',0
@D1        db  ' Offline Seek Performance      ',0
@DC        db  ' Disk Shift                    ',0
@DD        db  ' G-Sense Error Rate (Shock)    ',0
@DE        db  ' Loaded Hours                  ',0
@DF        db  ' Load/Unload Retry Count       ',0
@E0        db  ' Load Friction                 ',0
@E1        db  ' Load Cycle Count              ',0
@E2        db  ' Load in Time                  ',0
@E3        db  ' Torgue Amplification          ',0
@E4        db  ' Power Off Retract Count       ',0
@E6        db  ' GMR Head Amplitude            ',0
@E7        db  ' SSD Percent Lifetime Remaining',0
@EA        db  ' Not Correctable ECC Error     ',0
@F0        db  ' Head Flying Hours             ',0
@F1        db  ' Total LBAs Written from Host  ',0
@F2        db  ' Total LBAs Read from Host     ',0
@F7        db  ' Host Program Page Count       ',0
@F8        db  ' Background Program Page Count ',0
@FA        db  ' Read Error Retry Rate         ',0
@FE        db  ' Free Fall Protection          ',0

;//**********************************
section '.idata' import data readable
library  msvcrt,'msvcrt.dll',kernel32,'kernel32.dll'
import   msvcrt, printf,'printf',scanf,'scanf',gets,'gets',exit,'exit'
include 'api\kernel32.inc'

SMART_READ_DATA.png



Заключение

На этой ноте пора уже поставить точку, хотя в масштабах общих возможностей IOCTL не было сказано почти ничего. За бортом осталась работа с разделами диска на уровне файловой системы с кодами FSCTL и многое другое. В процессе экспериментов главное помнить, что в этой войне пленных не берут и при малейших ошибках чтения/записи секторов, можно запросто потерять все данные на диске.

Помимо рассмотренных прикладных методов, с дисками можно общаться и по внутреннему интерфейсу i2c, перемычками или джампером переключив контролёр в сервисный режим. Эта скрытая кладезь позволяет вторгнуться в святая-святых накопителей – их прошивку. Правда при этом Win32-API остаётся уже не у дел, и нужно передавать команды по терминалу. Если освоить данную технику, то можно пустить это дело на коммерческие рельсы, воскрешая мёртвые диски из небытия и восстанавливая с них информацию.

В скрепке цепляю обещанный инклуд, с программами HD-Tune (можно сравнить SMART), и WinObj для просмотра системного пространства имён. До скорого!
 

Вложения

  • Storage FASM.zip
    315,9 КБ · Просмотры: 593
Последнее редактирование:
Мы в соцсетях:

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