Я произвёл реверс-инжиниринг модуля обновления прошивки своего HDD Toshiba, чтобы иметь возможность обновлять её под Linux. Приведённые ниже команды должны работать, но используйте их на свой страх и риск:
$ wget https://www.canvio.jp/en/support/download/hdd/ot_ihdd/fw/ISFw.dat
$ grep -C2 MODELNAME ISFw.dat
# ^
# |___ здесь определяем нужное имя файла
$ wget https://www.canvio.jp/en/support/download/hdd/ot_ihdd/fw/sk060202.ftd
# hdparm --fwdownload-mode3 sk060202.ftd /dev/sdX
Предыстория
Недавно я приобрёл для своего NAS диск Toshiba HDWG480 HDD. Вот вывод команды
hdparm -I /dev/XXX
:ATA device, with non-removable media
Model Number: TOSHIBA HDWG480
Serial Number: 3430A00RFR0H
Firmware Revision: 0601
Transport: Serial, ATA8-AST, SATA 1.0a, SATA II Extensions, SATA Rev 2.5, SATA Rev 2.6, SATA Rev 3.0
Standards:
Used: unknown (minor revision code 0x006d)
Supported: 10 9 8 7 6 5
Likely used: 10
[...]
Как обычно, я хотел проверить, есть ли для него доступные обновления прошивки. На сайте Toshiba для моей модели приводится версия
0602
.К сожалению, и вполне ожидаемо, для пользователей Linux возможности обновления нет. Производитель предоставляет лишь «Internal Storage Firmware Update Utility» для Windows.
Сами файлы обновления также отсутствуют.
▍ Цели
Итак, мои задачи:
- Понять, откуда модуль обновлений получает свежие файлы.
- Произвести реверс-инжиниринг процесса прошивки, чтобы переписать его под Linux.
Реверс-инжиниринг модуля обновления для Windows
▍ Начало
После успешного выполнения установщика1 с помощью Wine в каталоге
Program Files (x86)
появляются следующие файлы: 18312 ISFw.exe: PE32 executable (native) Intel 80386, for MS Windows, 4 sections
2434952 TosISFw.exe: PE32 executable (GUI) Intel 80386, for MS Windows, 5 sections
2172296 TosISFwSvc.exe: PE32 executable (GUI) Intel 80386, for MS Windows, 5 sections
2362248 TosISFwTray.exe: PE32 executable (GUI) Intel 80386, for MS Windows, 5 sections
Бегло просмотрев имена файлов и импорты, можно предположить следующее назначение перечисленных программ:
-
ISFW.exe
— это драйвер (экспортDriverEntry
), вероятно, отвечающий за фактический процесс прошивки. -
TosISFw.exe
— это GUI. -
TosISFwSvc.exe
— это пользовательский сервис (на что указывают связанные с сервисом импорты). -
TosISFwTray.exe
— по всей видимости, отвечает за иконку в трее.
▍ Поиск файлов обновлений
Очевидным первым шагом было грепнуть URL в установленных бинарниках. К сожалению, это ничего не дало, кроме URL, связанных с цифровыми подписями. А вот грепнув API
HttpOpenRequest
, часто используемый программами Windows для скачивания файлов, я получил два результата: TosISFw.exe
и TosISFwSvc.exe
.Давайте заглянем в более мелкий из них —
TosISFwSvc.exe
— поищем URL, проверив перекрёстные ссылки.Вызов
HttpOpenRequest
находится в функции по адресу 0x00401040
и выглядит так:v15 = HttpOpenRequestW(v14, L"GET", &v36[(_DWORD)lpBuffer], 0, (LPCWSTR)szReferrer, 0, 0x84000000, 0);
Очевидно, что эта функция участвует в «скачивании», на что указывают все вызовы API. Переименуем её в
dlfile
. На dlfile
есть всего две перекрёстных ссылки:if ( !RegOpenKeyExW(
HKEY_LOCAL_MACHINE,
L"SYSTEM\\CurrentControlSet\\Services\\TosISFwSvc",
0,
0x20019u,
&phkResult)
&& readregstring((LPBYTE)&String, &phkResult, L"FwURL")
&& lstrlenW(&String) )
{
sub_401000();
LOBYTE(v47) = 2;
if ( dlfile(&String, (int)v38) )
[...]
sub_4052E0(&lpValueName, L"%s%d", L"URL", phkResult);
v25 = 0;
if ( !RegOpenKeyExW(
HKEY_LOCAL_MACHINE,
L"SYSTEM\\CurrentControlSet\\Services\\TosISFwSvc",
0,
0x20019u,
&v25)
&& readregstring((LPBYTE)&String, &v25, lpValueName)
&& lstrlenW(&String)
&& dlfile(&String, (int)v36) )
Первая даёт нам искомый ответ: URL хранится в реестре. По факту он записывается установщиком InstallShield.
Вот его значение:
http://www.canvio.jp/en/support/download/hdd/ot_ihdd/fw/ISFw.dat
▍ Парсинг файла обновления
Это файл
ini
, который несложно прочесть и спарсить:[VERS]
VERSION="20240513"
[Firmware]
0000=qa060378.ftd
0000model="TOSHIBA HDWG21E"
0000rev="0603"
0000rev0000="0601"
0000native=0
0000option=0
0001=qa060378.ftd
0001model="TOSHIBA HDWG21C"
0001rev="0603"
0001rev0000="0601"
0001native=0
0001option=0
[...]
0008=sk060202.ftd
0008model="TOSHIBA HDWG480 "
0008rev="0602"
0008rev0000="0601"
0008native=0
0008option=0
[...]
; 905CBD24
В моём случае номер диска 8. Реальный же интерес здесь вызывает контрольная сумма в конце. Это CRC32 файла минус последние 10 байтов, которые можно легко проверить инструментами
slice
и crc32
из моего универсального набора для хакинга rsbkb:$ slice -- ISFw.dat 0 -10 | crc32
905cbd24
Теперь, естественно, попробуем скачать нужный файл:
$ wget https://www.canvio.jp/en/support/download/hdd/ot_ihdd/fw/sk060202.ftd
Resolving www.canvio.jp (www.canvio.jp)... 23.72.248.205, 23.72.248.202
Connecting to www.canvio.jp (www.canvio.jp)|23.72.248.205|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1171456 (1.1M)
[...]
Чисто из любопытства можно проверить, сможет ли
cpu_rec_rs
определить какой-либо код в этом бинарнике:$ ~/tools/cpu_rec_rs/cpu_rec_rs sk060202.ftd
Loading corpus from "/home/trou/tools/cpu_rec_rs/cpu_rec_corpus/*.corpus"
-------------------------------------------------
File | Range | Detected Architecture
-------------------------------------------------
sk060202.ftd | Whole file | ARMhf
-------------------------------------------------
Похоже, что прошивка выполняется на SoC ARM (так и есть).
▍ Разбор процесса обновления
Теперь надо понять, как этот файл отправляется на диск для выполнения обновления. Напомню, что у нас есть четыре бинарника, и мы видели, что драйвером является
ISFW.exe
.Функция
DriverEntry
до боли проста:NTSTATUS __stdcall DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
int v2; // eax
readregistry();
v2 = flashfirmware();
sub_1001812(v2 % 100 == 0, 1, v2);
return NtTerminateProcess((HANDLE)0xFFFFFFFF, 0);
}
Я уже переименовал
readregistry
и flashfirmware
, так как эти функции определить легко:char readregistry()
{
[...]
RtlInitUnicodeString(&DestinationString, L"\\REGISTRY\\MACHINE\\SYSTEM\\CurrentControlSet\\Services\\TosISFwSvc");
[...]
if ( NtOpenKey(&KeyHandle, 0x20019u, &ObjectAttributes) >= 0 )
{
RtlInitUnicodeString(&ValueName, L"FW_Serial");
if ( NtQueryValueKey(KeyHandle, &ValueName, KeyValuePartialInformation, KeyValueInformation, 0x800u, &ResultLength) >= 0 )
{
memcpy(&fwserial, &KeyValueInformation[3], KeyValueInformation[2]);
[...]
RtlInitUnicodeString(&ValueName, L"FW_CurRev");
memcpy(&fw_cur, &KeyValueInformation[3], KeyValueInformation[2]);
[...]
RtlInitUnicodeString(&ValueName, L"FW_NewRev");
memcpy(fw_new, &KeyValueInformation[3], KeyValueInformation[2]);
[...]
RtlInitUnicodeString(&ValueName, L"FW_Model");
memcpy(fw_model, &KeyValueInformation[3], KeyValueInformation[2]);
[...]
RtlInitUnicodeString(&ValueName, L"FW_FWFile");
wmemcpy(path, L"\\??\\", 4);
memcpy(&path + 4, &KeyValueInformation[3], KeyValueInformation[2]);
[...]
}
Значения параметров реестра (устанавливаемые
TosISFwSvc.exe
) считываются и копируются в глобальные переменные, которые я переименовал в соответствии с названиями этих параметров.Вот начало
flashfirmware
:int flashfirmware()
{
[...]
Handle = 0;
fwdata = 0;
fwsize = 0;
memset(&drivedata, 0, sizeof(drivedata));
printf(L"%s Firmware: %s -> %s\n", fw_model, &fw_cur, fw_new);
printf(L"DO NOT TURN OFF THE PC WHILE ANY FIRMWARE UPDATE IS RUNNING.\n");
printf(
L"Your device may become unusable if you do this and Toshiba is not \n"
"responsible for any damage, including any necessary replacement of \n"
"the unit, caused by your doing so.\n");
HeapHandle = RtlCreateHeap(2u, 0, 0, 0, 0, 0);
if ( HeapHandle )
{
status = readfile(&path, &fwdata, &fwsize);
if ( !(status % 100) )
{
Handle = verifydisk(&fwserial, &fw_cur, fw_model, &drivedata);
[...]
При своей относительной простоте функция
verifydisk
очень важна (здесь уже всё переименовано):HANDLE __stdcall verifydisk(PCWSTR serial, PCWSTR cur, WCHAR *model, IDENTIFY_DEVICE_DATA *devdata)
{
HANDLE hdl; // edi
UNICODE_STRING cur_; // [esp+10h] [ebp-104h] BYREF
struct _UNICODE_STRING serial_; // [esp+18h] [ebp-FCh] BYREF
UNICODE_STRING model_from_drive_u; // [esp+20h] [ebp-F4h] BYREF
UNICODE_STRING serial_from_drive_u; // [esp+28h] [ebp-ECh] BYREF
UNICODE_STRING model_; // [esp+30h] [ebp-E4h] BYREF
UNICODE_STRING fwrev_from_drive_u; // [esp+38h] [ebp-DCh] BYREF
DWORD *drivenumber; // [esp+40h] [ebp-D4h]
HANDLE hdl_; // [esp+44h] [ebp-D0h]
char v14; // [esp+4Bh] [ebp-C9h] BYREF
WCHAR model_from_drive[50]; // [esp+4Ch] [ebp-C8h] BYREF
WCHAR serial_from_drive[30]; // [esp+B0h] [ebp-64h] BYREF
WCHAR fwrev_from_drive[18]; // [esp+ECh] [ebp-28h] BYREF
[...]
for ( drivenumber = 0; (unsigned int)drivenumber < 0x20; drivenumber = (DWORD *)((char *)drivenumber + 1) )
{
[...]
hdl = opendrive((char)drivenumber);
if ( !hdl )
break;
if ( !getdevprop(hdl, &bustype) || bustype == BusTypeUsb ) {
NtClose(hdl);
} else {
if ( get_IDENTIFY_DEVICE_DATA(hdl_, devdata, 0x200u) ) {
get_drive_serial(devdata, serial_from_drive, 30, 1);
get_drive_fw_rev(devdata, fwrev_from_drive, 18, 1);
get_drive_model(devdata, model_from_drive, 50, 1);
RtlInitUnicodeString(&serial_from_drive_u, serial_from_drive);
RtlInitUnicodeString(&fwrev_from_drive_u, fwrev_from_drive);
RtlInitUnicodeString(&model_from_drive_u, model_from_drive);
if ( RtlEqualUnicodeString(&serial_, &serial_from_drive_u, 0) )
{
if ( RtlEqualUnicodeString(&cur_, &fwrev_from_drive_u, 0)
&& RtlEqualUnicodeString(&model_, &model_from_drive_u, 0) )
{
return hdl_;
}
}
}
NtClose(hdl_);
}
}
return 0;
}
▍ Взаимодействие с диском
▍ Проверка типа диска
Заглянем в функции
opendrive
и getdevprop
:HANDLE __stdcall opendrive(char Args)
{
[...]
HANDLE FileHandle; // [esp+30h] [ebp-88h] BYREF
WCHAR SourceString[64]; // [esp+34h] [ebp-84h] BYREF
DestinationString.Length = 0;
*(_DWORD *)&DestinationString.MaximumLength = 0;
HIWORD(DestinationString.Buffer) = 0;
memset(SourceString, 0, sizeof(SourceString));
FileHandle = 0;
wsprintf(SourceString, 64, (wchar_t *)L"\\??\\PhysicalDrive%u", Args);
RtlInitUnicodeString(&DestinationString, SourceString);
[...]
NtOpenFile(&FileHandle, 0x100003u, &ObjectAttributes, &IoStatusBlock, 3u, 0x20u);
return FileHandle;
}
char __stdcall getdevprop(HANDLE hdl, char *bustype)
{
char tmp; // al
struct _IO_STATUS_BLOCK IoStatusBlock; // [esp+Ch] [ebp-1018h] BYREF
char *bustype_; // [esp+14h] [ebp-1010h]
HANDLE FileHandle; // [esp+18h] [ebp-100Ch]
char retvalue; // [esp+1Fh] [ebp-1005h]
STORAGE_DEVICE_DESCRIPTOR InputBuffer; // [esp+20h] [ebp-1004h] BYREF
FileHandle = hdl;
bustype_ = bustype;
IoStatusBlock.Status = 0;
IoStatusBlock.Information = 0;
retvalue = 0;
memset(&InputBuffer, 0, 0x1000u);
if ( NtDeviceIoControlFile( hdl, 0, 0, 0, &IoStatusBlock,
IOCTL_STORAGE_QUERY_PROPERTY,
&InputBuffer, 0x1000u, &InputBuffer, 0x1000u) < 0 ) {
tmp = 0;
} else {
tmp = InputBuffer.BusType;
retvalue = 1;
}
if ( bustype_ )
*bustype_ = tmp;
return retvalue;
}
opendrive
возвращает для указанного PhysicalDrive
дескриптор, который затем использует функция NtDeviceIoControlFile
, являющаяся частью getdevprop
. С помощью «standard enums» из IDA (Interactive Disassembler) я переотобразил значение 0x2D1400
в читабельное определение: IOCTL_STORAGE_QUERY_PROPERTY
.Так как
InputBuffer
перед вызовом устанавливается на 0
, в качестве данных возвращается структура STORAGE_DEVICE_DESCRIPTOR
, которую verifydisk
использует для проверки, не подключён ли диск по USB (BusTypeUsb
), и прекращает процесс, если это так.▍ Проверка модели диска
После этого
verifydisk
вызывает get_IDENTIFY_DEVICE_DATA
:char __stdcall get_IDENTIFY_DEVICE_DATA(HANDLE hdl, void *buff, size_t Size)
{
struct _IO_STATUS_BLOCK IoStatusBlock; // [esp+Ch] [ebp-3Ch] BYREF
HANDLE FileHandle; // [esp+14h] [ebp-34h]
char v6; // [esp+1Bh] [ebp-2Dh]
ATA_PASS_THROUGH_DIRECT InputBuffer; // [esp+1Ch] [ebp-2Ch] BYREF
FileHandle = hdl;
IoStatusBlock.Status = 0;
v6 = 0;
IoStatusBlock.Information = 0;
memset(buff, 0, Size);
memset(&InputBuffer, 0, sizeof(InputBuffer));
InputBuffer.Length = 0x28;
InputBuffer.AtaFlags = ATA_FLAGS_DRDY_REQUIRED|ATA_FLAGS_DATA_IN|ATA_FLAGS_NO_MULTIPLE;
InputBuffer.DataTransferLength = Size;
InputBuffer.TimeOutValue = 10;
InputBuffer.DataBuffer = buff;
InputBuffer.CurrentTaskFile[reg_Command] = 0xEC;
if ( NtDeviceIoControlFile(hdl, 0, 0, 0, &IoStatusBlock,
IOCTL_ATA_PASS_THROUGH_DIRECT,
&InputBuffer, 0x28u, &InputBuffer, 0x28u) >= 0
&& (InputBuffer.CurrentTaskFile[reg_Status] & 9) == 0 )
{
return 1;
}
return v6;
}
Теперь
NtDeviceIoControlFile
используется со структурой IOCTL_ATA_PASS_THROUGH_DIRECT
, которая, как и предполагает её имя, отправляет диску голую команду ATA. Разобраться здесь сложновато, так как ATA_PASS_THROUGH_DIRECT
посредством поля CurrentTaskFile
устанавливает оба буфера данных и «регистры».CurrentTaskFile
— это массив, используемый для индексирования 8 регистров, входных и выходных. Опираясь на документацию, можно создать два перечисления для использования в IDA:enum ATA_INPUT_REGISTERS : __int32
{
reg_Features = 0x0,
reg_Sector_Count_in = 0x1,
reg_Sector_Number_in = 0x2,
reg_Cylinder_Low_in = 0x3,
reg_Cylinder_High_in = 0x4,
reg_Device_Head_in = 0x5,
reg_Command = 0x6,
reg_Reserved = 0x7,
};
enum ATA_OUTPUT_REGISTERS : __int32
{
reg_Error = 0x0,
reg_Sector_Count_out = 0x1,
reg_Sector_Number_out = 0x2,
reg_Cylinder_Low_out = 0x3,
reg_Cylinder_High_out = 0x4,
reg_Device_Head_out = 0x5,
reg_Status = 0x6,
reg_Reserved_out = 0x7,
};
Итак, командой здесь выступает
0xEC
, почитать о которой можно в документации. В спецификации набора команд ATA/ATAPI описывается IDENTIFY DEVICE — ECh, PIO Data-In
, которая возвращает много данных. К счастью, Microsoft предоставляет нам структуру IDENTIFY_DEVICE_DATA
, где есть всё.Далее приведённый ниже код проверяет, «правильный» ли у нас диск, сравнивая серию, номер и версию прошивки из возвращённых данных с сохранёнными в реестре.
int __stdcall get_drive_serial(IDENTIFY_DEVICE_DATA *drivedata, wchar_t *dest, int destlen, char stripflag)
{
return (int)getdrive_data_string( drivedata, dest, destlen,
offsetof(IDENTIFY_DEVICE_DATA, SerialNumber), 20,
stripflag);
}
[...]
get_drive_serial(devdata, serial_from_drive, 30, 1);
[...]
RtlInitUnicodeString(&serial_from_drive_u, serial_from_drive);
[...]
if ( RtlEqualUnicodeString(&serial_, &serial_from_drive_u, 0) )
{
if ( RtlEqualUnicodeString(&cur_, &fwrev_from_drive_u, 0)
&& RtlEqualUnicodeString(&model_, &model_from_drive_u, 0) )
▍ Отправка файла прошивки
После того, как драйвер определил диск и убедился, что его можно прошить, он приступает к самому процессу:
[...]
MaxBlocksPerDownloadMicrocodeMode03 = drivedata.MaxBlocksPerDownloadMicrocodeMode03;
if ( !drivedata.MaxBlocksPerDownloadMicrocodeMode03 || drivedata.MaxBlocksPerDownloadMicrocodeMode03 == 0xFFFF ) {
MaxBlocksPerDownloadMicrocodeMode03 = 128;
} else if ( drivedata.MaxBlocksPerDownloadMicrocodeMode03 >= 0x80u ) {
MaxBlocksPerDownloadMicrocodeMode03 = 128;
}
if ( MaxBlocksPerDownloadMicrocodeMode03 >= drivedata.MinBlocksPerDownloadMicrocodeMode03
&& MaxBlocksPerDownloadMicrocodeMode03 ) {
fwblocks = fwsize >> 9;
fwblocks2 = fwsize >> 9;
v1 = 60;
do {
printprogress();
wait((LARGE_INTEGER)500LL);
--v1;
} while ( v1 );
for ( fwsize = 0; (int)fwsize < 30; ++fwsize ) {
currentblock = 0;
status = 6000;
if ( fwblocks ) {
fwdata1 = fwdata;
MaxBytesPerDL = MaxBlocksPerDownloadMicrocodeMode03 << 9;
while ( 1 ) {
printprogress();
blocks_to_flash = fwblocks2 - currentblock;
if ( MaxBlocksPerDownloadMicrocodeMode03 < fwblocks2 - currentblock )
blocks_to_flash = MaxBlocksPerDownloadMicrocodeMode03;
if ( !ATA_CMD_DOWNLOAD_MICRO(Handle, currentblock, blocks_to_flash, fwdata1) )
break;
currentblock += MaxBlocksPerDownloadMicrocodeMode03;
fwdata1 += MaxBytesPerDL;
if ( currentblock >= fwblocks2 )
goto LABEL_25;
}
status = 6009;
LABEL_25:
fwblocks = fwblocks2;
}
if ( !(status % 100) )
break;
v5 = 2;
do {
printprogress();
wait((LARGE_INTEGER)500LL);
--v5;
} while ( v5 );
}
if ( !(status % 100) )
{
if ( get_IDENTIFY_DEVICE_DATA(Handle, &drivedata, 0x200u) ) {
get_drive_fw_rev(&drivedata, newfwrev, 18, 1);
if ( wcsncmp(fw_new, newfwrev, wcslen(fw_new)) )
status = 6011;
} else {
status = 6010;
}
}
} else {
LABEL_35:
status = 6006;
}
[...]
if ( status % 100 )
printf(L"Update Failed. \n");
else
printf(L"Update Succeeded. \n");
Здесь мы видим, что модуль обновления проверяет интересное поле из данных о диске:
MaxBlocksPerDownloadMicrocodeMode03
. Посмотрим, что оно означает.▍ Отправка команд ATA для обновления прошивки
▍ Документация
Приведённая ниже выдержка из спецификации набора команд ATA отражает смысл этого поля:
A.11.5.3.4 Поле DM MAXIMUM TRANSFER SIZE
Если:
a) значение поля DM MAXIMUM TRANSFER SIZE (см. таблицу A.30) больше нуля;
b) значение поля DM MAXIMUM TRANSFER SIZE меньше FFFFh;
c) на один установлен бит DOWNLOAD MICROCODE SUPPORTED (см. A.11.5.2.20) или бит DOWNLOAD MICROCODE DMA SUPPORTED (см. A.11.5.2.6); и
d) на один установлен бит DM OFFSETS DEFERRED SUPPORTED (см. A.11.5.3.1) или бит DM OFFSETS IMMEDIATE SUPPORTED (см. A.11.5.3.3), тогда поле DM MAXIMUM TRANSFER SIZE указывает максимальное число блоков данных размером 512 байт, допускаемое командой DOWNLOAD MICROCODE (см. 7.7) или командой DOWNLOAD MICROCODE DMA (см. 7.8), которая устанавливает подкоманду:
a) Скачивания со смещениями и сохранения микрокода для использования непосредственно после загрузки или в будущем (то есть 03h); или
b) Скачивания со смещениями и сохранения микрокода для будущего использования (то есть 0Eh).
В противном случае максимум не указывается (то есть нет предела количества блоков данных размером 512 байт).
Данные IDENTIFY DEVICE содержат копию поля DM MAXIMUM TRANSFER SIZE (см. данные IDENTIFY DEVICE, слово 235 в таблице 45).
Естественно, я хочу проверить эту команду
DOWNLOAD MICROCODE
:Команда DOWNLOAD MICROCODE позволяет хосту изменять микрокод устройства. Данные, передаваемые с помощью команды DOWNLOAD MICROCODE и команды DOWNLOAD MICROCODE DMA в зависимости от поставщика отличаются.
[...]
Скачивание и активация микрокода включает следующие шаги:
1) скачивание: хост передаёт обновлённый микрокод на устройство в одной ли более команд DOWNLOAD
MICROCODE или DOWNLOAD MICROCODE DMA;
2) сохранение: когда все обновлённые данные микрокода получены, устройство сохраняет эти обновлённые данные в энергонезависимом хранилище, если это установлено режимом скачивания микрокода;
3) активация: устройство начинает использовать сохранённые данные или откладывает их использование до события, установленного режимом скачивания микрокода, после чего они становятся активными.
Поле BLOCK COUNT указывает количество блоков данных размером 512 байт, которые будут переданы. Поле BLOCK COUNT устанавливается в полях COUNT и LBA (см. таблицу 37).
Подкоманды
DOWNLOAD
по факту определяют поведение обновления:▍ Фактический код
char ATA_CMD_DOWNLOAD_MICRO(HANDLE FileHandle, __int16 currentblock, int blocks_to_flash, void *fwdata)
{
struct _IO_STATUS_BLOCK IoStatusBlock; // [esp+Ch] [ebp-38h] BYREF
char v6; // [esp+17h] [ebp-2Dh]
ATA_PASS_THROUGH_DIRECT InputBuffer; // [esp+18h] [ebp-2Ch] BYREF
IoStatusBlock.Status = 0;
IoStatusBlock.Information = 0;
memset(&InputBuffer, 0, sizeof(InputBuffer));
InputBuffer.Length = 0x28;
InputBuffer.AtaFlags = ATA_FLAGS_DRDY_REQUIRED|ATA_FLAGS_DATA_OUT|ATA_FLAGS_NO_MULTIPLE;
*(_WORD *)&InputBuffer.CurrentTaskFile[reg_Sector_Count_in] = blocks_to_flash;// BLOCK COUNT
*(_WORD *)&InputBuffer.CurrentTaskFile[reg_Cylinder_Low_in] = currentblock;// BUFFER OFFSET
v6 = 0;
InputBuffer.DataTransferLength = blocks_to_flash << 9;
InputBuffer.TimeOutValue = 70;
InputBuffer.DataBuffer = fwdata;
InputBuffer.CurrentTaskFile[reg_Features] = 3;// mode 3
InputBuffer.CurrentTaskFile[reg_Device_Head_in] = 0xE0;// OBSOLETE7|N/A|OBSOLETE5
InputBuffer.CurrentTaskFile[reg_Command] = IDE_COMMAND_DOWNLOAD_MICROCODE;
if ( NtDeviceIoControlFile(FileHandle, 0, 0, 0, &IoStatusBlock,
IOCTL_ATA_PASS_THROUGH_DIRECT,
&InputBuffer, 0x28u, &InputBuffer, 0x28u) >= 0
&& (InputBuffer.CurrentTaskFile[6] & 9) == 0 )// status
{
return 1;
}
return v6;
}
Как видите, структура
ATA_CMD_DOWNLOAD_MICRO
просто следует спецификации. Единственным странным моментом здесь является регистр Device
, который, по сути, является устаревшим, но тут установлен на 0xE0
. Для пущей уверенности я заглянул в исходный код hdparm
, чтобы увидеть значение, установленное в этой команде. И действительно, там оно тоже установлено на 0xE0
, значит это наверняка какой-то легаси-мусор:enum {
ATA_USING_LBA = (1 << 6),
ATA_STAT_DRQ = (1 << 3),
ATA_STAT_ERR = (1 << 0),
};
[...]
r->lob.dev = 0xa0 | ATA_USING_LBA;
Заключение
Итак, по сути, модуль обновления делает следующее:
- скачивает список обновлений,
- проверяет соответствие диска, устанавливает значения реестра,
- передаёт эстафету драйверу, который:
- проверяет, не подключён ли диск по USB,
- с помощью команды
IDENTIFY DEVICE
убеждается, что это тот самый диск, который указан в реестре, - циклически отправляет обновление прошивки по 128 фрагментов размером 512 байт за раз, используя команду
DOWNLOAD MICROCODE
, - с помощью команды
IDENTIFY DEVICE
убеждается, что диск был обновлён.
▍ Наконец, обновление
Была не была — я решился обновить прошивку на своём основном диске NAS:
# hdparm -I /dev/sdb | grep Firmware
Firmware Revision: 0601
# hdparm --fwdownload-mode3 sk060202.ftd --yes-i-know-what-i-am-doing --please-destroy-my-drive /dev/sdb
/dev/sdb:
fwdownload: xfer_mode=3 min=1 max=4224 size=512
...............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
. Done.
# hdparm -I /dev/sdb | grep Firmware
Firmware Revision: 0602
▍ Сноска
1. Версия 1.20.0410, MD5: 7cc7dc301f7b8a45cc56ee25e5707cc2, дата: 2023-12-27 ↩
Telegram-канал со скидками, розыгрышами призов и новостями IT ?
ajijiadduh
если кому-то неинтересно так трахаться ради маленькой операции, можно просто взять и обновить всё что нужно с загрузочного диска с виндой.
но ради искусства конечно норм
nochkin
Особенно если учесть, что обновление решают проблему, которая не касается конфигурации у автора. Но тут важен процесс, есть такое.
Как-то давно я реверсил полумёртвую плату для обновления прошивки мониторов. Отреверсил и даже написал прогу под Linux, которая прошивает по VGA/HDMI безо всяких дополнительных железных модулей. А потом ради прикола починил эту плату и мониторы "в проде" прошивали с ней. Не догнал, но согрелся.