Техника защиты компакт-дисков от копирования

         

Доступ посредствомчерез SPTI


Одно из интереснейших архитектурных особенностей операционной системы Windows NT заключается в ее умении взаимодействовать с IDE-?устройствами посредствомчерез SCSI-интерфейса! К сожалению, данная технология чрезвычайно скудно документирована —– Platform SDK, MSDN, DDK содержат лишь обрывки информации, а имеющиеся примеры крайне ненаглядны и к тому же выполнены с большим количеством фактических ошибок, так что разобраться с ними под силу лишь профессионалу ну или очень настырному новичку.

Замечание

В общем-то это ситуация вполне логичнао —– ведь Microsoft не имеет к ATAPI/SCSI-интерфейсам ни малейшего отношения, и их стандартизацией занимаются совершенно иные комитеты. Однако в "приличных домах" так все-таки не поступают. Вместо того, чтобы оставить программиста со своими проблемами наедине, составители документации могли бы по крайней мере нарисовать общую картину взаимодействия. Попробуйте выкачать из Сети Интернет тысячи страниц технической документации (большей частью ненужной, но кто ж это знает заранее!) и, проштудировав ее всю, попытаться свести эту разрозненную картину воедино.

И, судя по сообщениям в телеконференциях, многим программистам осилить технику управления устройствами посредствомчерез SCSI-интерфейса так и не удалось, поэтому имеет смысл рассмотреть эту проблему поподробнее.

Для решения поставленной задачи нам понадобяиться:

5.     а) Описание SCSI--интерфейса (см. документ "SCSI Architecture Model –—  3", описывающий общие концепции SCSI-архитектуры и "SCSI Primary Commands –—  3", определяющий базовый набор команд для всех SCSI-устройств; черновые версии обоих документах доступны в электронном виде и лежат по адресамздесь http://www.t10.org/ftp/t10/drafts/sam3/sam3r08.pdf и здесь http://www.t10.org/ftp/t10/drafts/spc3/spc3r14.pdf

соответственно; в качестве пособия "быстрого старта" рекомендую "The Linux SCSI programming HOWTO", который можно найти по адресуздесь: http://www.ibiblio.org/pub/Linux/docs/HOWTO/other-formats/pdf/SCSI-Programming-HOWTO.pdf).;


6.     б) Описание SCSI-команд, специфичных для оптических накопителей (см. документ "Multimedia Commands –—  4", описывающий принципы программирования CD-ROM/R/ RW накопителей, электронную версию которого можно найти, в частности, по адресуздесь: http://www.t10.org/ftp/t10/drafts/mmc4/mmc4r02b.pdf).;

7.     в) Описание ATAPI-интерфейса для CD-ROM/DVD накопителей

(см. например, "ATA Packet Interface for CD-ROMs" и "Specification for ATAPI DVD Devices", причем, спецификации на DVD гораздо лучше и полнее описывают архитектуру CD-ROM, чем его родная документация; не самые свежие, но вполне подходящие ревизии можно найти по адресамздесь: www.stanford.edu/~csapuntz/specs/INF-8020.PDF

и ftp.seagate.com/sff/INF-8090.PDF; описания SCSI- и ATAPI- команд во многом дублируют друг друга, однако некоторые особо тонкие моменты лучше описываются то в одном, то в другом руководстве, поэтому профессиональные программисты должны иметь оба).;

8.     в) Описание форматов хранения данных на лазерных дисках (см. стандарт ECMA-130  "Data interchange on read-only 120 mm optical data disks", известный также под именем "Желтой Книги", которую можно найти по адресуздесь: http://www.ecma-international.org/publications/files/ecma-st/Ecma-130.pdf; это —– базовый стандарт для накопителей CD-?ROM накопителей;).;

9.     г) Помимо этого годиться любая литература, так или иначе затрагивающая вопросы программирования CD-ROM; нелишним будет почитать "ATAPI(IDE) CD. Информация к размышлению" от Константина Норватова и "Особенности программирования CD--ROM'а на Спектруме" от Влада Сотникова.

Итак, что же такое SCSI? Это —– стандартизованный, платформенно-независимый интерфейс, обеспечивающий согласованное взаимодействие различных устройств и высокоуровневых приложений. Собственно, аббревиатура SCSI именно так и расшифровывается —– Small Computer System Interface (системный интерфейс малых компьютеров).




Благодаря интерфейсу SCSI для низкоуровневого управления устройствами совершенно необязательно прибегать к написанию собственных драйверов (писать драйвер только для того, чтобы прорваться сквозь ограничения API –— чистейший маразм), ведьи эту задачу можно решить и на прикладном уровне, посылая устройству специальные CDB-блоки, содержащие стандартные или специфичные для данного устройства команды управления вместе со всеми необходимыми им параметрами. Собственно, "CDB" так и расшифровывается —– Command Descriptor Block. Пример одного из таких блоков приведен в таблице 1.4.2ниже.:

Таблица 2.1.4.22. Пример CDB блока, который будучи переданным SCSI-устройству, заставляет его прочитать 0x69-сектор

Смещение, байт

Содержимое

0x0

0x28

Код команды "read sector"

0x1

0x00

Зарезервировано

0x2

0x00

Номер сектора –— 0х69

0x3

0x00

0x4

0х00

0x5

0x69

0x6

0x00

Количество секторов

0x7

0x01

0x8

0x00

Зарезервировано

0x9

0x00

Зарезервировано

0xA

0x00

Зарезервировано

Первый байт блока представляет собой команду операции (в нашем случае: 0x28 —– чтение одного или нескольких секторов), а все остальные байты блока —– параметры данной команды. Причем, обратите внимание на тот факт, что младший байт слова располагается по большему адресу, –— то естьли все происходит не так, как в привычном нам IBM PC! Поэтому, если передать в качестве номера первого сектора последовательность 0x69 0x00 0x00 0х00, то почитается 0x6900000 сектор, а вовсе не 0x00000069, как можно было того ожидать!

Краткое описание стандартных SCSI-команд можно найти в том же "The Linux SCSI programming HOWTO", однако для наших целей их вряд ли окажется достаточно, и команды, специфичные для дисков CD-ROM дисков, мы рассмотрим отдельно. Однако это произойдет не раньше, чем мы разберемся как CDB-блоки упаковываются в SRB-конверт (SCSI Request Block), без которого операционная система просто не поймет, что же мы хотим сделать (как известно, компьютернаямашинная программа выполняет то, что ей приказали сделать, иногда это совпадает с тем, что от нее хотели, иногда нет).



Структура SRB-блока подробно описана в NT DDK, поэтому не будем подробно на ней останавливаться и пробежимся по основным полям лишь вкратце (листинг 1.4.6).

Листинг 2.1.4.6. КратноеКраткое описание структуры SCSI_REQUEST_BLOCK

typedef struct _SCSI_REQUEST_BLOCK {

    USHORT Length;        // длина структуры SCSI_REQUEST_BLOCK

 

    UCHAR Function;       // функция (обычно SRB_FUNCTION_EXECUTE_SCSI == 0, т.е.

                          // отправить устройству команду на выполнение)

 

    UCHAR SrbStatus;      // здесь устройство отображает прогресс выполнения

                          // команды, наиболее часто встречаются значения:

                          // SRB_STATUS_SUCCESS == 0x1 – команда завершена успешно

                          // SRB_STATUS_PENDING == 0x0 – команда еще выполняется

                          // SRB_STATUS_ERROR   == 0x4 – произошла ошибка

                          // также возможны и другие значения, перечисленные в DDK

 

    UCHAR ScsiStatus;     // здесь устройство возвращает статус завершения команды

                          // и, если не SUCCESS, значит, произошел ERROR

 

    UCHAR PathId          // SCSI-порт, на котором сидит контроллер устройства

                          // для "виртуальных" SCSI устройств всегда 0

 

    UCHAR TargetId;       // контроллер устройства на шине

                          // для IDE устройств обычно 0 – primary, 1 – secondary

 

    UCHAR Lun;            // логический номер устройства внутри контроллера

                          // для IDE устройств обычно 0 – master, 1 – slayer

 

    CHAR QueueTag;        // обычно не используется и должно быть равно нулю

    CHAR QueueAction;     // обычно не используется и должно быть равно нулю

 

    CHAR CdbLength;       // длина CDB-блока, для ATAPI-устройств всегда 12 (0Ch)

    CHAR SenseInfoBufferLength;      // длина SENSE-буфера (о нем ниже)

 

    LONG SrbFlags;        // флаги.


обычно принимают два значения


                          // SRB_FLAGS_DATA_IN == 0x40 – перемещение данных от

                          //                   устройства к компьютеру (чтение)

                          // SRB_FLAGS_DATA_OUT == 0x80 – перемещение данных от

                          //                   компьютера к устройству (запись)

 

    ULONG DataTransferLength;        // длина блока читаемых/записываемых данных

 

    LONG TimeOutValue;     // время вылета по тайм-ауту в секундах

 

    PVOID DataBuffer;      // указатель на буфер c читаемыми/записываемыми данными

 

    PVOID SenseInfoBuffer; // указатель на SENSE буфер (о нем – ниже)

 

    struct _SCSI_REQUEST_BLOCK *NextSrb; // указатель на след. SRB. Обычно не исп.

 

    PVOID OriginalRequest; // указатель на IRP. Практически не используется

 

    PVOID SrbExtension;    // обычно не используется и должно быть равно нулю

 

    UCHAR Cdb[16];         // собственно, сам CDB-блок

} SCSI_REQUEST_BLOCK, *PSCSI_REQUEST_BLOCK;

Заполнив поля структуры SCSI_REQUEST_BLOCK подобающим образом, мы можем передать SRB-блок выбранному нами устройству посредством функции DeviceIoControl, просто задав соответствующий код IOCTL. Вот, собственно, и все! Заглотнув наживку, операционная система передаст CDB-блок соответствующему устройству, и оно выполнит (или не выполнит) содержащуюся в нем (СDB-блоке) команду. Обратите внимание: CDB-блок обрабатывается не драйвером устройства, ано самим устройством, иа потому мы имеем практически неограниченные возможности по управлению последним. И все это —– с прикладного уровня!

Теперь о грустном. Процедура управлениями устройствами довольно капризна и одно-единственное неправильно заполненное поле может обернуться категорическим нежеланием устройства выполнять передаваемые ему команды. Вместо этого будет возвращаться код ошибки или вовсе не возвратится ничего. К тому же малейшая неаккуратность может запросто испортить данные на всех жестких дисках, а потому с выбором значений TargetID и lun вы должны быть особенно внимательными! (Для автоматического определения физического адреса CD-ROM'а можно использовать SCSI-команду SCSI_INQUIRY —– см.


демонстрационный пример \NTDDK\src\win_me\block\wnaspi32 из DDK). Однако довольно говорить об опасностях (без них жизнь была бы слишком скучной), переходим к самому интересному —– поиску того самого IOCTL-кодаа, который этот SRB-блок собственно и передает.

Оказывается, напрямую это сделать не так-то просто, точнее —– легальными средствами невозможно вообще! Создатели Windows по ряду соображений решили предоставить полный доступ к полям структуры SCSI_REQUEST_BLOCK только писателям драйверов, а прикладных программистов оставили наедине со структурами SCSI_PASS_THROUGH и SCSI_PASS_THROUGH_DIRECT, —– схожими по назначению с SRB, но несколько ограниченными в своей функциональности. К счастью, на содержимое CDB-блоков не было наложено никаких ограничений, а потому возможность низкоуровневого управления железом у нас все-таки осталаись. Подробнее обо всем этом можно прочитать в разделе "9.2 SCSI Port I/O Control Codes" из NT DDK, а также из исходного текста демонстрационного примера "\NTDDK\src\storage\class\spti" из того же DDK (обратите внимание на файл spti.htm, лежащий в этом же каталоге, который достаточно подробно описывает суть управления устройством через SСSI-интерфейс).

Согласно наименованию каталога с демонстрационным примером, данный способ взаимодействия с устройством носит название SPTI и расшифровывается как SCSI Pass Through IOCTLs —– т. е. SCSI, проходящий через IOCTL. Кратко перечислим основные особенности и ограничения интерфейса SPTI-интерфейса.

q      Во-первых, для передачи CDB-блоков устройству вы должны обладать привилегиями администратора, что не всегда удобно (зато безопасно!).

q      Во-вторых, использование многоцелевых команд запрещено (т. е. мы не можем отдать команду копирования данных с устройства А на устройство Б в обход процессора, хотя такие команды у современных приводов есть, и было бы очень здорово копировать лазерные диски совершенно не загружая процессор).



q      В-третьих, реверсивное ( то бишь двунаправленное) перемещенниеперемещение данных не поддерживается, и в каждый момент времени данные могут перемещаться либо от устройства к компьютеру, либо от компьютера к устройству, но не то и другое одновременно!).

q      В-четвертых, при установленном class-драйвере для целевого устройства, мы должны направлять CDB-блоки именно class-драйверу, ано не самому SCSI-устройству. То есть, для управления CD-ROM'ом вы должны взаимодействовать с ним через устройство \\.\X:, где X —– буква привода, попытка же обращения к "\\.\Scsi0:" возвратит ошибку (и это, как показывает практика, основной камень преткновения неопытных программистов, начинающих программировать раньше, чем читать документацию).

Замечание

Как вариант –— можно обращаться к устройству "\\.\CdRom0" или "\\.\CdRom1" без знака двоеточия на конце, где 0 и 1 –— порядковый номер привода CD-ROM привода в системе. Вопреки распространенному заблуждению, гласящему, что устройство "\\.\CdRom0" расположено на более низком уровне, чем "\\.\X:", с точки зрения операционной системы это синонимы и, чтобы убедиться в этом, достаточно заглянуть в содержимое таблицы объектов (objdir "\DosDevice"), доказывающее, что "\\.\X:" представляет собой ни что иное, как символическую ссылку на \\.\CdRomN.

q      В-пятых, на максимальный размер пересылаемых данных (MaximumTransferLength) наложены жесткие ограничения, диктуемые спецификой используемого оборудования и обслуживающего его драйвера мини- порта. Ограничения касаются как предельно допустимого размера блока данных, так и количества занятых им физических страниц. Для определения конкретных характеристик следует послать устройству команду IOCTL_SCSI_GET_CAPABILITIES, которая возвратит структуру IO_SCSI_CAPABILITIES (ищите ее определение в NTDDSCSI.h), содержащую среди всего прочего значения[Y101]  MaximumTransferLength и MaximumPhysicalPages_in_bytes.


Максимальный размер пересылаемых данных вычисляется по следующей формуле: largest transfer = min (MaximumTransferLength, MaximumPhysicalPages_in_bytes).

Как вариант можно ограничиться блоками по 64 Ккилобайтовыми блоками, гарантированно поддерживаемых всеми устройствами. Буфер так же должен быть выровнен на величину кратную величине AlignmentMask[Y102] , возвращаемую в структуре IO_SCSI_CAPABILITIES. Степень выравнивания, обеспечиваемая функцией malloc, для этих целей оказывается вполне достаточнойа и при ее использовании никаких проблем не возникает. Другое дело, если выделение памяти осуществляется конструкцией "char buf[BUF_SIZE]", –— в этом случае работоспособность вашей программы уже не гарантируется.

q      В-шестых, сама структура SCSI_PASS_THROUGH_DIRECT (листинг 1.4.7) содержит значительно меньше полей, причем значения полей PathId, TargetId и Lun просто игнорируются! Физический адрес устройства на шине определяется непосредственно самой операционной системой по символьному имени дескриптора устройства, которому, собственно, и посылается SCSI_PASS_THROUGH_DIRECT- запрос. Структура SCSI_PASS_THROUGH во всем похожа на структуру SCSI_PASS_THROUGH_DIRECT, но не обеспечивает передачу данных посредством DMA (Direct Memory Access).

Листинг 2.1.4.7. Формат структуры SCSI_PASS_THROUGH_DIRECT (структура SCSI_PASS_THROUGH во всем похожа на нее, но не обеспечивает передачу данных через DMA).

typedef struct _SCSI_PASS_THROUGH_DIRECT {

    USHORT Length;                // размер структуры SCSI_PASS_THROUGH_DIRECT

    UCHAR ScsiStatus;             // статус выполнения SCSI-команды устройством

    UCHAR PathId;                 // игнорируется

    UCHAR TargetId;               // игнорируется

    UCHAR Lun;                    // игнорируется

    UCHAR CdbLength;              // длина CDB-пакета, посылаемая устройству, байты

    UCHAR SenseInfoLength;        // длина SENSE-буфера для возращения ошибки



    UCHAR *DataIn;                // направление передачи данных

    ULONG DataTransferLength;     // размер буфера для обмена данными в байтах

    ULONG TimeOutValue;           // время вылета по тайм-ауту

    PVOID DataBuffer;             // указатель на буфер для обмена данными

    ULONG SenseInfoOffset;        // указатель на SENSE-буфер с информацией о error

    UCHAR Cdb[16];                // буфер с CDB-пакетом (16 байт максимум)

}SCSI_PASS_THROUGH_DIRECT, *PSCSI_PASS_THROUGH_DIRECT;

К счастью, "цензура", в основном, коснулась тех полей, которые в реальной жизни все равно практически не используются, так что мы ровным счетом ничего не потеряли. Заполняем оставшиеся поля, и наша структура готова!

Естественно, прежде чем передать ее устройству, нам необходимо получить дескриптор, использующийся для управления этого самого устройства. Это можно сделать так как показано в листинге 1.4.8.:

Листинг 2.1.4.8. Открытие привода для получения дескриптора, использующегося для его уприавления

HANDLE hCD = CreateFile ("\\\\.\\X:", GENERIC_WRITE | GENERIC_READ,

                   FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);

Убедившись, что hCD не равно INVALID_HANDLE_VALUE, передаем полученный дескриптор вместе с самой структурой IOCTL_SCSI_PASS_THROUGHT_DIRECT функции DeviceIoControl, вызывая ее как это показано в листинге 1.4.9.следующим образом:

Листинг 2.1.4.9. Передача структуры IOCTL_SCSI_PASS_THROUGH

DeviceIoControl(hCD, 0x4D014h /* IOCTL_SCSI_PASS_THROUGH_DIRECT

*/
, &srb,

            sizeof(SCSI_PASS_THROUGH_DIRECT), sense_buf, SENSE_SIZE, &returned, 0);

Где srb и есть заполненный экземпляр структуры IOCTRL_SCSI_PASS_THROUGHT_DIRECT, а returned –— переменная, в которую будет записано количество байт, возращенных устройством. В свою очередь, sense_buf –— это тот самый буфер, в котором заполненный нами экземпляр структуры IOCTL_SCSI_PASS_THROUGHT_DIRECT возвращается назад, да не один, а вместе с Sense Infoинфой, –— кодом ошибки завершения операции.


Если же операция завершилась без ошибок, то Sense Info не возвращается и буфер sense_buf содержит только структуру IOCTL_SCSI_PASS_THROUGHT. Позиция размещения Sense Info в буфере определяется содержимым поля SenseInfoOffset, значение которого должно быть подобрано так, чтобы не "наступать на пятки" структуре IOCTRL_SCSI_PASS_THROUGHT, т. е., попросту говоря, минимально возможное смещение Sense Info равно: srb.SenseInfoOffset = sizeof(SCSI_PASS_THROUGH_DIRECT). Обратите внимание, SenseInfoOffset это не указатель на Sense Info, но индекс первого байта Sense Info в возвращаемом буфере!

Для определения факта наличия ошибки, необходимо проанализировать количество байт, возращенных функцией DeviceIoControl в переменной returned. Если оно превышает размер структуры IOCTL_SCSI_PASS_THROUGHT, то в буфере находится Sense Info, а раз есть Sense Info, то есть и ошибка! Формат кода ошибки Sense Info приведен на рисунке 1.4.1 .ниже:

Рис. 2.1.4.11. 0x063 [Y103] Формат кода Sense Info, возвращаемогой устройством в случае возникновения ошибки

Первый байт указывает на тип ошибки и обычно принимает значение 70h (текущая ошибка –— current error) или 71h (отсроченная ошибка –— deferred error). Коды ошибок с 72h по 7Eh зарезервированы, причем ошибки с кодом 7Eh указывают на нестандартный (vendor-specific) формат sense-info формат. Коды ошибок с 00h по 6Fh в спецификации CD-ROM ATAPI неопределенны, и потому их использование нежелательно (данное предостережение, разумеется, адресовано не программистам, а разработчикам аппаратуры).

Описание ошибки кодируется тройкой чисел: Sense Key, Additional Sense Code (дополнительный смысловой код, сокращенно ASC) и Additional Sense Code Qualifier (ASCQ). Вершину этой иерархической пирамиды возглавляет Sense Key, содержащий общую категорию ошибки (genetic categories), затем идет дополнительный смысловой код, более детально описывающий ошибку и, наконец, в самом низу иерархии находится квалификатор дополнительного смыслового кода, уточняющий непосредственно сам дополнительный смысловой код.


Если ошибка исчерпывающе описывается одним лишь кодом Sense Key и ASC, то ASCQ в таком случае отсутствует (точнее –— находится в неопределенном состоянии).

Расшифровка основных кодов ошибок описывается в двух таблицах (табл. 1.4.3 и 1.4.4)., приведенных ниже. Стоит сказать, что для анализа ошибки значение Sense Key в общем-то некритично, т. к. гарантируется, что каждый код ASC принадлежит только одному Sense Key; напротив, один и тот же код ASCQ может принадлежать нескольким различным кодам ASC, и потому в отрыве от последнего он бессмыслен.

Таблица 2.1.4.33. Основные Sense Key (категории ошибок) и их описания

Sense  Key

Описание

00h

NO SENSE. Нет дополнительного кода ошибкий Sense Info. Операция выполнена успешно.

01h

RECOVERED ERROR (восстановленная ошибка). Операция выполнена успешно, но в процессе ее выполнения возникли некоторые проблемы, устраненные непосредственно самим приводом. За дополнительной информацией обращайтесь к ключам ASC и ASCQ.

02h

NOT READY (не готов). Устройство не готово.

03h

MEDIUM ERROR (ошибка носителя). В процессе выполнения операции произошла неустранимая ошибка, вызванная, по всей видимости, дефектами носителя или ошибкой записи данных. Данный Sense  Key может возвращаться и в тех случаях, когда привод оказывается не в состоянии отличить дефект носителя от аппаратного сбоя самого привода.

04h

HARDWARE ERROR (аппаратная ошибка). Неустранимая аппаратная ошибка (например, отказ контроллера).

05h

ILLEGAL REQEST (неверный запрос). Неверные параметры, переданные приводу в CDB-пакете (например, начальный адрес больше конечного).

06h

UNIT ATTENTION (модуль требуетмого внимания) Носитель заменен или выполнен сброс контроллера привода.

07h

DATA PROTECT (защищенные данные) Попытка чтения защищенных данных.

8h –—0Ah

Зарезервировано.

0Bh

ABORTED COMMAND (команда прервана). По тем или иным причинам выполнение команды было прервано.

0Eh

MISCOMPARE (ошибка сравнения) Исходные данные не соответствуют данным, прочитанным с носителя.

0Fh

Зарезервировано.

<


Таблица 2.1.4.44. Основные ASC- и ASCQ- коды.

ASC

ASCQ

DROM

Описание

00

00

DROM

NO ADDITIONAL SENSE INFORMATION

00

11

R

PLAY OPERATION IN PROGRESS

00

12

R

PLAY OPERATION PAUSED

00

13

R

PLAY OPERATION SUCCESSFULLY COMPLETED

00

14

R

PLAY OPERATION STOPPED DUE TO ERROR

00

15

R

NO CURRENT AUDIO STATUS TO RETURN

01

00

R

MECHANICAL POSITIONING OR CHANGER ERROR

02

00

DROM

NO SEEK COMPLETE

04

00

DROM

LOGICAL DRIVE NOT READY - CAUSE NOT REPORTABLE

04

01

DROM

LOGICAL DRIVE NOT READY - IN PROGRESS OF BECOMING READY

04

02

DROM

LOGICAL DRIVE NOT READY - INITIALIZING COMMAND REQUIRED

04

03

DROM

LOGICAL DRIVE NOT READY - MANUAL INTERVENTION REQUIRED

05

01

DROM

MEDIA LOAD - EJECT FAILED

06

00

DROM

NO REFERENCE POSITION FOUND

09

00

DRO

TRACK FOLLOWING ERROR

09

01

RO

TRACKING SERVO FAILURE

09

02

RO

FOCUS SERVO FAILURE

09

03

RO

SPINDLE SERVO FAILURE

11

00

DRO

UNRECOVERED READ ERROR

11

06

RO

CIRC UNRECOVERED ERROR

15

00

DROM

RANDOM POSITIONING ERROR

15

01

DROM

MECHANICAL POSITIONING OR CHANGER ERROR

15

02

DRO

POSITIONING ERROR DETECTED BY READ OF MEDIUM

17

00

DRO

RECOVERED DATA WITH NO ERROR CORRECTION APPLIED

17

01

DRO

RECOVERED DATA WITH RETRIES

17

02

DRO

RECOVERED DATA WITH POSITIVE HEAD OFFSET

17

03

DRO

RECOVERED DATA WITH NEGATIVE HEAD OFFSET

17

04

RO

RECOVERED DATA WITH RETRIES AND/OR CIRC APPLIED

17

05

DRO

RECOVERED DATA USING PREVIOUS SECTOR ID

18

00

DRO

RECOVERED DATA WITH ERROR CORRECTION APPLIED

18

01

DRO

RECOVERED DATA WITH ERROR CORRECTION & RETRIES APPLIED

18

02

DRO

RECOVERED DATA - THE DATA WAS AUTO-REALLOCATED

18

03

R

RECOVERED DATA WITH CIRC

18

04

R

RECOVERED DATA WITH L-EC

1A

00

DROM

PARAMETER LIST LENGTH ERROR

20

00

DROM

INVALID COMMAND OPERATION CODE

21

00

DROM

LOGICAL BLOCK ADDRESS OUT OF RANGE

24

00

DROM

INVALID FIELD IN COMMAND PACKET

26

00

DROM

INVALID FIELD IN PARAMETER LIST

26

01

DROM

PARAMETER NOT SUPPORTED

26

02

DROM

PARAMETER VALUE INVALID

28

00

ROM

NOT READY TO READY TRANSITION, MEDIUM MAY HAVE CHANGED

29

00

ROM

POWER ON, RESET OR BUS DEVICE RESET OCCURRED

2A

00

ROM

PARAMETERS CHANGED

2A

01

ROM

MODE PARAMETERS CHANGED

30

00

ROM

INCOMPATIBLE MEDIUM INSTALLED

30

01

RO

CANNOT READ MEDIUM - UNKNOWN FORMAT

30

02

RO

CANNOT READ MEDIUM - INCOMPATIBLE FORMAT

39

00

ROM

SAVING PARAMETERS NOT SUPPORTED

3A

00

ROM

MEDIUM NOT PRESENT

3F

00

ROM

ATAPI CD-ROM DRIVE OPERATING CONDITIONS HAVE CHANGED

3F

01

ROM

MICROCODE HAS BEEN CHANGED

40

NN

ROM

DIAGNOSTIC FAILURE ON COMPONENT NN (80H-FFH)

44

00

ROM

INTERNAL ATAPI CD-ROM DRIVE FAILURE

4E

00

ROM

OVERLAPPED COMMANDS ATTEMPTED

53

00

ROM

MEDIA LOAD OR EJECT FAILED

53

02

ROM

MEDIUM REMOVAL PREVENTED

57

00

R

UNABLE TO RECOVER TABLE OF CONTENTS

5A

00

DROM

OPERATOR REQUEST OR STATE CHANGE INPUT (UNSPECIFIED)

5A

01

DROM

OPERATOR MEDIUM REMOVAL REQUEST

63

00

R

END OF USER AREA ENCOUNTERED ON THIS TRACK

64

00

R

ILLEGAL MODE FOR THIS TRACK

B9

00

R

PLAY OPERATION OBORTED

BF

00

R

LOSS OF STREAMING

<


Как видите, —– все просто! Единственное, с чем мы еще не разобрались, –— это ATAPI. Поскольку мы не собираемся взаимодействовать с ATAPI-интерфейсом напрямую (этой возможности "благодаря" архитекторам Windows мы, увы, лишены) "промчимся галопом" лишь по ключевым аспектам и особенностям. Как пишет Михаил Гук в своей книге "Интерфейсы персональных компьютеров": "Для устройств, логически отличающихся от жестких дисков —– оптических, магнитооптических, ленточных и любых других —– в 1996 г. была принята спецификация ATAPI. Это пакетное расширение интерфейса, которое позволяет передавать по шине ATA устройству блоки командной информации, структура которых была позаимствована из SCSI". Теперь, по крайней мере, становится понятно, почему Windows так лихо "превращает" ATAPI-устройства в SCSI. Если отбросить аппаратные различия интерфейсов, которые с программного уровня все равно не видны, то ATAPI-интерфейс будет очень напоминать SCSI. Во всяком случае, управление ATAPI-устройствами осуществляется посредством тех самых CDB-блоков, которые мы уже рассматривалирассмотрели ранеевыше.

Естественно, чтобы управлять устройством, необходимо знать, какими именно командами оно управляется. Для получения этой информации нам понадобится документ "ATAPI Packet Commands for CD-ROM devices". Откройте его на описании команды READ CD command

(код BEh) и вы обнаружите таблицу формата этой команды (рис. 1.4.2).следующего содержания:

Рис. 2.1.4.22. ФорматОписание команды READ CD

Попробуем в ней разобраться. Первый байт (точнее байт 0), представляющий собой код выполняемой команды, никаких вопросов не вызывает, но вот дальше мы сталкиваемся с полем Expected Sector Type, задающим тип требуемого сектора. Перевернув несколько страниц вперед, мы найдем коды, соответствующие всем существующим типам секторов: CDDA, Mode 1, Mode 2, Mode 2 Form 1 и Mode 2 Form 2.


Если же тип сектора заранее неизвестен, передавайте с этим полем 0x0, что обозначает "нас устроит любой тип сектора".

Следующие четыре байта занимает адрес первого читаемого сектора (Starting Logical Block Address), заданный в формате LBA (т. е. Logical Block Address). За этой "страшной" аббревиатурой скрывается элегантный способ сквозной нумерации секторов. Если вы когда-то программировали "древние" жесткие диски, то наверняка помните, какие громоздкие расчеты приходилось выполнять, чтобы определить к какой головке, цилиндру, сектору каждый байт прилежит. Теперь же можно обойтись безо всех этих заморочек. Первый сектор имеет номер 0, затем идет 1, 2, 3… и так до последнего сектора диска. Только помните, что порядок байт в этом двойном слове обратный, –— т. е. старший байт старшего слова идет первым.

Байты с шестого по восьмой "оккупировал" параметр, задающий количество читаемых секторов (Transfer Length in Blocks). Вот какая несправедливость —– для адреса сектора выделяется четыре байта, а для количества читаемых секторов только три. Шутка! Вы же ведь не собираетесь читать весь диск за раз?! Порядок байт здесь также обратный, так что не ошибитесь, иначе при попытке считать один-единственный сектор вы запросите добрую половину диска целиком!

Девятый байт наиболее интересен, ибо он хранит флаги, определяющие, какие части сектора мы хотим прочитать (Flag Bits). Помимо пользовательских данных, мы можем запросить синхробайты (Synch Field), заголовок (Header(s) code), EDC/ECC коды (EDC & ECC) и даже флаги ошибок чтения (Error Flag(s)) (для взлома некоторых защит это самое то! —– правда, эту возможность поддерживают не все приводы).

Десятый байитт (Sub-Channel Data Selection Bits) отвечает за извлечение данных изх подканалов, однако поскольку эти же самые данные уже содержатся в заголовке, то без них можно, в принципе, и обойтись.

Наконец, последний, одиннадцатый, считая от нуля, байт, никак не используется и зарезервирован на будущее, а потому для гарантии совместимости с новыми моделями приводов, он должен быть равен нулю.



Естественно, в зависимости от рода и количества запрашиваемых данных, длина возращенного сектора может варьироваться в очень широких пределах (табл. 1.4.5). Вот, смотрите:

Таблица 2.1.4.5 . 0х035 Взаимосвязь [Y104] рода запрошенных данных и длины возвращаемого сектора, старндаррые режимы выдлены серым

Data to be transferred

Flag Bits

CD-DA

Mode 1

Mode 2

non XA

Mode 2

Form 1

Mode 2

Form 2

User Data

10h

2352

2048

2336

2048

2338

User Data + EDC/ECC

18h

(10h)

2336

(10h)

2336

(10h)

Header Only

20h

(10h)

4

4

4

4

Header Only + EDC/ECC

28h

(10h)

Illegal

Illegal

Illegal

Illegal

Header & User Data

30h

(10h)

2052

2340

Illegal

Illegal

Header & User Data + EDC/ECC

38h

(10h)

2344

(30h)

Illegal

Illegal

Sub Header Only

40h

(10h)

8

8

8

8

Sub Header Only + EDC/ECC

48h

(10h)

Illegal

Illegal

Illegal

Illegal

Sub Header & User Data

50h

(10h)

(10h)

(10h)

2056

2336

Sub Header & User Data + EDC/ECC

58h

(10h)

(10h)

(10h)

2344

(50h)

All Header Only

60h

(10h)

12

12

12

12

All Header Only + EDC/ECC

68h

(10h)

Illegal

Illegal

Illegal

Illegal

All Header & User Data

70h

(10h)

(30h)

(30h)

2060

2340

All Header & User Data + EDC/ECC

78h

(10h)

(30h)

(30h)

2340

2340

Sync & User Data

90h

(10h)

Illegal

Illegal

Illegal

Illegal

Sync & User Data + EDC/ECC

98h

(10h)

Illegal

Illegal

Illegal

Illegal

Sync & Header Only

A0h

(10h)

16

16

16

16

Sync & Header Only + EDC/ECC

A8h

(10h)

Illegal

Illegal

Illegal

Illegal

Sync & Header & User Data

B0h

(10h)

2064

2352

Illegal

Illegal

Sync & Header & User Data + EDC/ECC

B8h

(10h)

2344

(30h)

Illegal

Illegal

Sync & Sub Header Only

C0h

(10h)

Illegal

Illegal

Illegal

Illegal

Sync & Sub Header Only + EDC/ECC

C8h

(10h)

Illegal

Illegal

Illegal

Illegal

Sync & Sub Header & User Data

D0h

(10h)

(10h)

(10h)

Illegal

Illegal

Sync & Sub Header & User Data + EDC/ECC

D8h

(10h)

(10h)

(10h)

Illegal

Illegal

Sync & All Headers Only

E0h

(10h)

24

24

24

24

Sync & All Headers Only + EDC/ECC

E8h

(10h)

Illegal

Illegal

Illegal

Illegal

Sync & All Headers & User Data

F0h

(10h)

2064

2352

2072

2352

Sync & All Headers & User Data + EDC/ECC

F8h

(10h)

2352

(F0h)

2352

(F0h)

Repeat All Above and Add Error Flags

02h

294

294

294

294

294

Repeat All Above and Add Block & Error Flags

04h

296

296

296

296

296

<


Рис. 2.3. 0х035 взаимосвязь рода запрошенных данных и длины возвращаемого сектора

Рис. 2.43. 0х033 Внутренний мир Windows NT.

IDE-устройства с прикладного уровня видятся как SCSI. Разумеется, на физическом уровне с приводом не происходит никаких изменений, и привод CD-ROM привод с IDE-–интерфейсом так IDE-приводом и остается, со всеми присущими ему достоинствами и недостатками. Однако IRP-запросы (I/O Request Packet) к этому драйверу, проходя через Storage class driver, транслируются в блок SRB (SCSI Request Block). Затем SRB-запросы попадают в Storage port driver (т. е. непосредственно в сам драйвер привода), где они заново транслируются в конкретные физические команды данного устройства (см. рис. 1.4.30х033). Подробности этого увлекательного процессора можно почерпнуть из набора NT DDK (см. "1.4.1 Storage Driver Architecture"), здесь же достаточно указать на тот немаловажный факт, что, кроме команд семейства IRP_MJ_ххх, мы также можем посылать устройству и SRB-запросы, которые обладают значительно большей свободной и гибкостью. Однако, такое взаимодействие невозможно осуществить непосредственного с прикладного уровня, поскольку, IRP-команды относятся к числу приватных

команд, в то время как API-функция DeviceIoControl передает лишь публичные команды, явно обрабатываемые драйвером в диспетчере IRP_MJ_DEVICE_CONTROL.

Рис. 1.4.3. 0х033 Внутренний мир Windows NT

Давайте теперь, в порядке закрепления всего вышесказанного, попытаемся создать программу, которая бы читала сектора с лазерных дисков в "сыром" виде. Ее ключевой фрагмент (вместе со всеми необходимыми комментариями) приведен в листинге 1.4.10.ниже:

Листинг 2.1.4.10. [/SPTI.raw.sector.read.c] Функция, читающая сектора в "сыром" виде посредствомчерез SPTI

#define RAW_READ_CMD            0xBE // ATAPI RAW READ

#define WHATS_READ              0xF8 // Sync & All Headers & User Data + EDC/ECC



#define PACKET_LEN              2352 // длина одного сектора

//#define WHATS_READ            0x10 // User Data

//#define PACKET_LEN            2048 // длина одного сектора

//-[SPTI_RAW_SECTOR_READ]------------------------------------------------------

//       функция читает один или несколько секторов с CDROM в сыром (RAW) виде,

//      согласно переданным флагам

//

//      ARG:

//           CD            - что открывать (типа "\\\\.\\X:" или "\\\\.\\CdRom0")

//           buf           - буфер куда читать

//           buf_len       - размер буфера в байтах

//           StartSec      - с какого сектора читать, считая от нуля

//           N_SECTOR      - сколько секторов читать

//           flags         - что читать (см. спецификацию на SCSI/ATAPI)

//

//      RET:

//           !=0           - функция завершилась успешно

//           ==0           - функция завершилась с ошибкой

//

//      NOTE:

//           - работает только под NT/W2K/XP и требует прав администратора

//

//           - 64 Кб данных за раз максимум

//-----------------------------------------------------------------------------

SPTI_RAW_SECTOR_READ(char *CD,char *buf,int buf_len,int StartSec,int N_SEC,char flags)

{

      HANDLE                        hCD;

      SCSI_PASS_THROUGH_DIRECT      srb;

      DWORD                         returned, length, status;

     

      // ОТКРЫВАЕМ УСТРОЙСТВО

      hCD = CreateFile (      driver, GENERIC_WRITE|GENERIC_READ,

      FILE_SHARE_READ|FILE_SHARE_WRITE,0,OPEN_EXISTING,0,0);

      if (hCD == INVALID_HANDLE_VALUE) { printf("-ERR: open CD\n"); return 0;}

     

      // ФОРМИРУЕМ SRB

      memset(&srb,0,sizeof(SCSI_PASS_THROUGH_DIRECT));      // инициализация

     

      srb.Length              = sizeof(SCSI_PASS_THROUGH_DIRECT);

      srb.PathId              = 0;                    // SCSI controller ID

      srb.TargetId            = 6;                    // игнорируется

      srb.Lun                 = 9;                    // игнорируется



      srb.CdbLength           = 12;                   // длина CDB пакета

      srb.SenseInfoLength     = 0;                    // нам не нужна SenseInfo

      srb.DataIn              = SCSI_IOCTL_DATA_IN;   // мы будем читать

      srb.DataTransferLength  = PACKET_LEN*N_SECTOR;  // сколько мы будем читать

      srb.TimeOutValue        = 200;                  // время выхода по TimeOut

      srb.DataBufferOffset    = buf;                  // указатель на буфер

      srb.SenseInfoOffset     = 0;                    // SenseInfo на не нужна

     

      // CDB-пакет, содержащий команды ATAPI

      srb.Cdb[0]              = RAW_READ_CMD;         // читать сырой сектор

      srb.Cdb[1]              = 0x0;                  // формат диска - любой

     

      // номер первого сектора для чтения, причем сначала передается старший

      // байт старшего слова, а потом младший байт младшего слова

      srb.Cdb[2]              = HIBYTE(HIWORD(StartSector));

      srb.Cdb[3]              = LOBYTE(HIWORD(StartSector));

      srb.Cdb[4]              = HIBYTE(LOWORD(StartSector));

      srb.Cdb[5]              = LOBYTE(LOWORD(StartSector));

     

      // количество секторов для чтения

      srb.Cdb[6]              = LOBYTE(HIWORD(N_SECTOR));

      srb.Cdb[7]              = HIBYTE(LOWORD(N_SECTOR));

      srb.Cdb[8]              = LOBYTE(LOWORD(N_SECTOR));

     

      srb.Cdb[9]              = flags;                // что читать

      srb.Cdb[10]             = 0;                    // Sub-Channel Data Bits

      srb.Cdb[11]             = 0;                    // reserverd

     

      // ОТПРАВЛЯЕМ SRB-блок ATAPI-устройству

      status = DeviceIoControl(hCD, IOCTL_SCSI_PASS_THROUGH_DIRECT,

                &srb, sizeof(SCSI_PASS_THROUGH_DIRECT), &srb, 0, &returned, 0);

     

      return 1;

}

Остается отметить, что защитные механизмы, взаимодействующие с диском посредствомчерез интерфейса SPTI, элементарно ломаются установкой точки останова на функциии CreateFile/DeviceIoControl.Для предотвращения "лишних" всплытий отладчика фильтр точки останова должен реагировать только на те вызовы функции CreateFile, чей первый слева аргумент равен "\\.\X:" или "\\.\CdRomN". Автоматически, второй слева аргумент функции DeviceIoControl должен представлять собой либо структуру IOCTL_SCSI_PASS_THROUGHT, либо IOCTL_SCSI_PASS_THROUGHT_DIRECT, шестнадцатеричные значения кодов которых равны 0x4D004 и 0x4D014 соответственно.


Содержание раздела