Адресация секторов
Адресация сектора произошла от аудиодисков и записывается в формате Time—– mm:ss:ff (минуты:секунды:доли, где доля в секунде равна от 0 до 74). Отсчет начинается с начала программной области, т. е. адреса секторов вводной области отрицательные.
Для перевода MSF[Y37] [n2k38] адреса в LBA можно воспользоваться следующей формулой: Logical Sector Address = (((Minute*60)+Seconds)*75) –- 150
Alcohol 120%
Программа Alcohol 120% в зависимости от настроек может обращаться к диску тремя путями: посредствомчерез собственногоый драйвера (по умолчанию), посредствомчерез интерфейса ASPI/SPTI интерфейс и посредствомчерез ASPI Layer. Начнем с "собственного драйвера". Установка точки останова на функции CreateFileA показывает, что программа Alcohol 120% Алкоголь открывает устройство "\\.\SCSI2:" (естественно, на других компьютерах номер может быть и другим), и дальнейшая проверка подтверждает, что функция DeviceIoControl получает тот же самый дескриптор, что возвратился при открытии устройства SCSI! Следовательно, под "собственным" драйвером программа Alcohol 120% Алкоголик понимает тот самый драйвер мини- порта, которой он и установил в систему при своей установке.
Теперь изменим настройки программы Alcohol 120% Алкоголика так, чтобы она работала посредствомчерез интерфейса SPTI/ASPI интерфейс. После перезапуска программы (а при смене метода доступа программа Alcohol 120% Алкоголь требует обязательного перезапуска), мы снова "словим" открытие устройства "\\.\SCSI2", а затем произойдет открытие диска "\\.\G:" (естественно, на других компьютерах буква может быть и другой). Собственно, при взаимодействии с устройством посредствомчерез интерфейса SPTI интерфейс именно так все и происходит. Точнее должно происходить. Программа Alcohol 120% Алкоголь открывает диск "\\.\G:" многократно, что указывает на "корявость" его архитектуры. Это существенно усложняет нашу задачу, поскольку мы вынуждены следить за всеми дескрипторами одновременно, и если упустить хотя бы один из них, реконструированный алгоритм работы программы окажется неверным (разве не интересно узнать, как именно Alcohol 120% Алкоголь осуществляет копирование защищенных дисков?).
Наконец, переключив программу Alcohol 120% Алкоголь на последний оставшийся способ взаимодействия с диском, мы получим следующий результат: "\\.\\SCSI2", "\\.\MbMmDp32", "\\.\G:". Устройство с именем "MbMmDp32" и есть уже знакомый нам ASPI-драйвер. Правда, не совсем понятно, зачем программа Alcohol 120% Алкоголь явно открывает диск "\\.\G:", ведь ASPI-интерфейс этого не требует.
Аудио, перекрываемое данными
Рассмотрим многосессионный диск, состоящий из двух сессий —– аудио-сессии и сессии с данными. При просмотре такого диска средствами операционный системы Windows мы увидим лишь том с данными, в то время как обыкновенные аудио-проигрыватели будут исправно воспроизводить сессию с аудио треками, ничего не подозревая о существовании сессии данных (для совместимости с аудио проигрывателями сессия с аудио треками всегда должна располагаться первой). Теоретически аудио треки могут быть прослушаны путем "ручного" запуска CD-плеераplayer'а, но практически же это обычно не удается, т. к. защита использует "дополнительные рубежи обороны" (например, кастрированный урезанная область Lead-inLead-In, внесение неисправимых CIRC-ошибок и т. д.), препятствующие такому простому способу "взлома".
Визуально такие компакт-дискиы распознаются по характерной области Lead-outLead-Out области, расположенной вблизи внешнего края диска (см. рис. 0x022). Это и есть тот барьер, что отделяет аудио сессию от сессии данных. Само по себе наличие "посторонней" области Lead-outLead-Out области еще не свидетельствует о наличии защиты, она присутствует и на, так называемых, дисках CD-Enhanced дисках, – т. е. дисках смешанного типа (аудио плюс данные), вполне успешно поддерживаемых современными приводами или /операционными системами, однако, в любом случае —– это весьма тревожный симптом и без особой нужны такой диск лучше не покупать (а, впрочем, чего уж там, покупайте —– все равно взломаем!). Блестящее колечко области Lead-Out, расположенной недалеко от внешнего края лазерного диска и хорошо различимое в отраженном свете, еще не свидетельствует о наличии защиты от воспроизведения, но вероятность ее наличия чрезвычайно велика. В частности, изображенный на фотографии диск (рис. 7.1) защищен Cactus Shield 2.00, — наиболее популярной защитой от цифрового копирования аудидисков на сегодняшний день.
![](image/index-image057.jpg)
Рис. 7.1. унок 21 0х022 Блестящее колечко Lead- out области, расположенной недалеко от внешнего края лазерного диска и хорошо различимое в отраженном свете, еще не свидетельствует о наличии защиты от воспроизведения, но вероятность ее наличия чрезвычайно велика. В частности, изображенный на фотографии Диск защищен Cactus Shield 2.00, —– наиболее популярной защитой от цифрового копирования аудидисков на сегодняшний день .
Сессия данных может включать в себя все, что угодно, вплоть до того, чтобы быть совсем пустой. Но обычно здесь находится сильно сжатое аудио в формате MP3. Причем это аудио mp3 чаще всего записывается не в виде отдельных файлов, которые можно скопировать с диска и выложить в Интернет, а "закатывается" в исполняемый файл-оболочку, запускающийся только с оригинального CD! Естественно, файл-оболочка работает только в среде Windows и пользователям UNIX/Mac остается лишь "облизываться" или… ломать защитный механизм на корню! Ну, если процедура восстановления некорректно записанного компакта, осуществляемая за собственный счет, уже называется взломом, то…
Если никаких дополнительных защитных уровней не предусмотрено, то содержимое аудиосесссии может быть элементарно "сграблено" в MP3/WMA. Большинство штатных программ для "прожига" CD-R/RW позволяют просматривать содержимое всех сессий диска и первой сессии в том числе. Вы можете использовать Roxio Easy CD Creator, Stomp Record Now! или любую другую аналогичную программу по своему выбору. В Roxio Easy CD Creator'e просто выберите в меню "CD" пункт "CD Information" и выделите один или несколько аудио-трекови первой сессии, а затем нажмите "Convert Audio" и… наслаждайтесь высококачественной музыкой, сграбленной в вашем любимом формате (та версия, Roxio Easy CD Creator'a, что поставляется вместе с рекордером PHILPS не поддерживает формат MP3 и предлагает на выбор всего два варианта: WMA и WAV).
Как вариант можете создать исправленную копию защищенного диска, удалив из него лишнюю сессию с данными. Программа Clone CDCloneCD способна справиться с этим и автоматически. Просто найдите в окне Profile parameters ("Параметрыах профиля)" взакладку Audio read parameters ("Параметры чтения аудио)", а в ней —– флажокпункт Read first session only ("Чтение только первой сессии)". Установить егоВзведите напротив него галочку и нажмите кнопку "ОК" для подтверждения. Копирование диска будет протекать в обычном режиме, но в результате него, вы получите обыкновенныйх аудио диск безо всякого постороннего "мусора".
Копировщик Alcohol 120% не поддерживает такой возможности, но позволяет добиться аналогичного результата вручную. Осуществляется это следующим образом: на первом этапе мы должны получить образ диска, записанный в формате Clone CDCloneCD (Alcohol 120%Алкоголик это позволяет). Затем, отредактируем CCD-файл так, чтобы вычеркнуть из него все упоминания о сессии (сессиях) с данными. Прежде всего, мы должны уменьшить значение поля "Sessions" с двух до одного. Затем, удалить из файла все Entry, в которых значение полячей "Session" больше единицы. Теперь скорректируем значение поля "TocEntries", сократив его на величину удаленного количества Entry. Остается удалить лишь один или несколько последних треков с данными. Сквозная нумерация треков несколько осложняет эту, казалось бы с виду простую задачу, поскольку мы не можем быстро установить какой трек к какой сессии принадлежит. Поэтому, приходится либо подсчитывать количество треков вручную (поля Point со значением больше нуля, но меньше 0x64), либо удалять все треки, чей Mode не равен нулю. Конкретный пример работы с CCD-файлом приведен в листинге 7.1ниже.
Листинг 7.1. Коррекция CCD-файла для взлома диска
[CloneCD] [CloneCD]
Version=3 Version=3
[Disc] [Disc]
TocEntries=24 TocEntries=20
Sessions=2 Sessions=1
DataTracksScrambled=0 DataTracksScrambled=0
CDTextLength=0 CDTextLength=0
[Session 1] CDTextLength=0
PreGapMode=0 PreGapMode=0
PreGapSubC=0 PreGapSubC=0
[Session 2] [Session 2]
PreGapMode=2 PreGapMode=2
PreGapSubC=0 PreGapSubC=0
[Entry 0] [Entry 0]
Session=1 Session=1
Point=0xa0 Point=0xa0
ADR=0x01 ADR=0x01
Control=0x00 Control=0x00
TrackNo=0 TrackNo=0
AMin=97 AMin=97
ASec=26 ASec=26
AFrame=66 AFrame=66
ALBA=-11634 ALBA=-11634
Zero=0 Zero=0
PMin=1 PMin=1
PSec=32 PSec=32
PFrame=0 PFrame=0
PLBA=6750 PLBA=6750
…
[Entry 20] [Entry 20]
Session=2 Session=2
Point=0xa0 Point=0xa0
ADR=0x01 ADR=0x01
Control=0x04 Control=0x04
TrackNo=0 TrackNo=0
AMin=72 AMin=72
ASec=22 ASec=22
AFrame=38 AFrame=38
ALBA=325538 ALBA=325538
Zero=0 Zero=0
PMin=16 PMin=16
PSec=32 PSec=32
PFrame=0 PFrame=0
PLBA=74250 PLBA=74250
…
[Entry 23] [Entry 23]
Session=2 Session=2
Point=0x10 Point=0x10
ADR=0x01 ADR=0x01
Control=0x04 Control=0x04
TrackNo=0 TrackNo=0
AMin=72 AMin=72
ASec=23 ASec=23
AFrame=17 AFrame=17
ALBA=325592 ALBA=325592
Zero=0 Zero=0
PMin=73 PMin=73
PSec=54 PSec=54
PFrame=38 PFrame=38
PLBA=332438 PLBA=332438
[TRACK 1] [TRACK 1]
MODE=0 MODE=0
FLAGS= DCP FLAGS= DCP
INDEX 1=0 INDEX 1=0
[TRACK 2] [TRACK 2]
MODE=0 MODE=0
FLAGS= DCP FLAGS= DCP
INDEX 1=19173 INDEX 1=19173
…
[TRACK 16] [TRACK 16]
MODE=2 MODE=2
INDEX 1=0 INDEX 1=0
Листинг 68 коррекция CCD-файла для взлома диска
Некоторые диски содержат "зловредную" программу, автоматически запускающуюся при загрузке диска и скрытно внедряющуюся в оперативную память компьютера. Ее основная задача —– охрана аудио треков от цифрового грабежа. Конкретные реализации такого "сторожа" довольно различны. Можно, например, периодически сканировать список окон верхнего уровня (см. описание функции FindWindows или EnumWindows в Platform SDK) на предмет поиска заголовков наиболее популярных "грабилок". Хороших программ для работы с цифровым аудио существует не так уж и много, поэтому вполне реально научиться опознавать их всех. Коль скоро окно программы найдено, защита может вытворять с ним все, что угодно. В частности, для принудительного завершения приложения ему достаточно послать сообщение WM_DESTROY.
Автоматическое копирование и обсуждение его результатов
Горячо любимая мной программа StompRecord Now! при попытке скопировать диск с искаженным стартовым адресом первого трека выводит сообщение "Invalid disk" и отказывается начинать операцию. В общем-то, это и не удивительно. Что можно взять с "юзерского" копировщика?
Гораздо интереснее протестировать поведение Ahead Nero CD Speed из ее же комплекта –— популярнейшего профессионального копировщика программ. Проверка показывает, что независимо от состоянияе флажка Ignore Illegal TOC Type, находящегося во вкладке Read options
и положения остальных опций, скопировать защищенный диск никак не получается. "Нюра" (программа Ahead Nero CD Speed) из ее же комплекта выдает сообщение "Invalid track mode"
и даже не пытается начать чтение! Служебная утилита Scan Disk из программыиз Ahead Nero CD Speed из ее же комплекта так же работает некорректно и выполняет сканирование отнюдь не первого трека, но той области, в которой расположен искаженный стартовый адрес (см. рис. 6.30x064). На рисунке видно, что утилита "согласилась" на сканирование диска с искаженным стартовым адресом, однако, "залезла" совсем "не в ту степь", принявшись сканировать область диска, с адресом первого трека, указанную в TOC. Второй же трек здесь и вовсе не виден!
![](image/avtomaticheskoe-kopirovanie-i-obsuzhdenie-ego_1.gif)
Рис. 6.унок 23. 0x064 Сканирование служебной утилитой Scan Disk программы Ahead Nero CD Speed à Scan Disk согласился на сканирование диска с искаженным стартовым адресом, однако, залез совсем не в ту степь, принявшись сканировать область диска, с адресом первого трека, указанную в TOC'e
Теперь перейдем к копировщикам защищенных дисков, одним из которых является Clone CDCloneCD, создатели которого утверждают, что он может справиться с любой, существующей ныне защитой.
В какой бы привод защищенный диск ни был вставлен, программа Clone CDCloneCD выдает неизменно постоянный результат, не имеющий ничего общего с реальной действительностью.
Обратите внимание (листинг 6.6), что она распознала на диске лишь одну сессию из двух (первую), да и то неправильно. По еего скромному мнению, эта сессия имеетдиск содержит всего одну сессию с общуюей протяженностью в 4,6 Ммегабайт, но зато размер единственного трека последней сессии составляет ни много ни мало –— 3,9 Ттерабайт!
Листинг 6.6. Таким видит защищенный диск копировщик CloneCD
ИНФОРМАЦИЯ О CD В ДИСКОВОДЕ:
Число сессий: 1
Занято на диске: 4726 Кбайт
Секторов: 2058
Время: 00:27:33 (мин:сек:кадр)
ИНФОРМАЦИЯ О СЕССИИ 1:
Размер сессии: 4726 Кбайт
Число треков: 1
Pregap: Данные Mode 1, размер: 103359 Кбайт
Track 1: Data, размер: 4294868664 Кбайт
Листинг 6 таким видит защищенный диск копировщик Clone CD. Обратите внимание, что он распознал лишь одну сессию из двух (первую), да и то неправильно
Еще до завершения процесса копирования нас начинают одолевать стойкие сомнения или, я бы даже сказал, непоколебимая уверенность, в том, что диск будет скопирован неправильно. И действительно, чего мы опасались, то мы и получили! Давайте создадим образ скопированного диска в плане сравнения копии TOC с оригиналом (листинг 6.7). Здесь несоответствующие поля выделены полужирным шрифтом.
Листинг 6.7. Образ защищенного диска, снятый программой CloneCD
[CloneCD] ; данные о копировщике
Version=3 ; версия Clone CDCloneCD
[Disc] ; данные о диске
TocEntries=7 ; кол-во элементов TOC'a == 7 (в оригинале было 12)
Sessions=1 ; кол-во сессий == 1 (в оригинале было 2)
DataTracksScrambled=0 ; поле DVD
CDTextLength=0 ; CD-Text'a в полях подкода Lead-inLead-In области нету
[Session 1] ; данные сессии 1
PreGapMode=1 ; тип трека == Mode 1
PreGapSubC=0 ; данных подканала – нет
[Entry 0] ; данные элемента TOC'a №0
Session=1 ; элемент сессии 1
Point=0xa0 ; номер первого трека сессии 1 в PMin/тип диска в PSec
ADR=0x01 ; q-Mode == 1
Control=0x04 ; диск с данными, запрещенный ;-) для копирования
TrackNo=0 ; трек, который мы сейчас читаем – это Lead-inLead-In трек (т.е. TOC)
AMin=0 ; \
ASec=0 ; + - абсолютный адрес текущего трека
AFrame=0 ; /
ALBA=-150 ; LBA-адрес текущего трека
Zero=0 ; это поле должно быть равно нулю, как оно и есть
PMin=1 ; номер первого трека сессии 1
PSec=0 ; тип диска CD-DA и CD-ROM диск в Mode 1
PFrame=0 ; не несет никакой полезной информации
PLBA=4350 ; номер трека представленный CloneCD как LBA-адрес, т.е. чушь
[Entry 1] ; данные элемента TOC'a №1
Session=1 ; элемент сессии 1
Point=0xa1 ; номер последнего трека сессии 1 в PMin
ADR=0x01 ; q-Mode == 1
Control=0x04 ; диск с данными, запрещенный ;-) для копирования
TrackNo=0 ; трек, который мы сейчас читаем – это Lead-inLead-In трек (т.е. TOC)
AMin=0 ; \
ASec=0 ; + - абсолютный адрес текущего трека
AFrame=0 ; /
ALBA=-150 ; LBA-адрес текущего трека
Zero=0 ; это поле должно быть равно нулю, как оно и есть
PMin=1 ; номер последнего трека сессии 1 (в сессии только один трек)
PSec=0 ; не несет никакой полезной информации
PFrame=0 ; не несет никакой полезной информации
PLBA=4350 ; номер трека представленный CloneCD как LBA-адрес, т.е. чушь
[Entry 2] ; данные элемента TOC'a №2
Session=1 ; элемент сессии 1
Point=0xa2 ; положение Lead-out области в PMin:PSec:PFrame
ADR=0x01 ; q-Mode == 1
Control=0x04 ; диск с данными, запрещенный ;-) для копирования
TrackNo=0 ; трек, который мы сейчас читаем – это Lead-inLead-In трек (т.е. TOC)
AMin=0 ; \
ASec=0 ; + - абсолютный адрес текущего трека
AFrame=0 ; /
ALBA=-150 ; LBA-адрес текущего трека
Zero=0 ; это поле должно быть равно нулю, как оно и есть
PMin=0 ; \
PSec=29 ; + - абсолютный адрес Lead-out области сессии 1
PFrame=33 ; /
PLBA=2058 ; LBA-адрес Lead-out области сессии 1
[Entry 3] ; данные элемента TOC'a №3
Session=1 ; элемент сессии 1
Point=0x01 ; данные трека 1 сессии 1
ADR=0x01 ; q-Mode == 1
Control=0x04 ; диск с данными, запрещенный ;-) для копирования
TrackNo=0 ; трек, который мы сейчас читаем – это Lead-inLead-In трек (т.е. TOC)
AMin=0 ; \
ASec=0 ; + - абсолютный адрес текущего трека
AFrame=0 ; /
ALBA=-150 ; LBA-адрес текущего трека
Zero=0 ; это поле должно быть равно нулю, как оно и есть
PMin=10 ; \
PSec=2 ; + - абсолютный адрес начала трека 1 сессии 1
PFrame=0 ; /
PLBA=45000 ; LBA-адрес начала трека 1 сессии 1
[Entry 4] ; данные элемента TOC'a №4
Session=1 ; элемент сессии 1
Point=0xb0 ; позиция следующий записываемой области в AMin:ASec:AFrame
ADR=0x05 ; q-Mode == 1
Control=0x04 ; диск с данными, запрещенный ;-) для копирования
TrackNo=0 ; трек, который мы сейчас читаем – это Lead-inLead-In трек (т.е. TOC)
AMin=2 ; \
ASec=59 ; + - абсолютный адрес следующей записываемой области
AFrame=33 ; /
ALBA=13308 ; LBA-адрес следующей записываемой области
Zero=3 ; кол-во pointer'ов в Mode 5
PMin=22 ; \
PSec=14 ; + - абсолютный адрес максимальной записываемой области
PFrame=34 ; /
PLBA=99934 ; LBA-адрес максимальной записываемой области
[Entry 5] ; данные элемента TOC'a №5
Session=1 ; элемент сессии 1
Point=0xc0 ; стартовый адрес Lead-inLead-In области Hybrid диска (если он есть)
ADR=0x05 ; Mode 5 (Оранжевая книга)
Control=0x04 ; диск с данными, запрещенный ;-) для копирования
TrackNo=0 ; трек, который мы сейчас читаем – это Lead-inLead-In трек (т.е. TOC)
AMin=162 ; рекомендуемая мощность лазера для
ASec=200 ; Application code (в оригинале здесь было 128)
AFrame=224 ; в оригинале здесь было 140
ALBA=294074 ; LBA-"адрес" трех предыдущих полей
Zero=0 ; зарезервировано
PMin=97 ; \
PSec=27 ; + - абсолютный адрес Lead-inLead-In области Hybrid диска
PFrame=21 ; / ( адрес лежит за пределами диска, т.е. Hybrid-диска нет)
PLBA=-11604 ; LBA-адрес Lead-inLead-In области Hybrid'a(вычислен с переполнением)
[Entry 6] ; данные элемента TOC'a №6
Session=1 ; элемент сессии 1
Point=0xc1 ; копия ATIP-информации
ADR=0x05 ; -+
Control=0x04 ; -+
TrackNo=0 ; -+
AMin=4 ; -+
ASec=192 ; -+
AFrame=150 ; -+- ATIP (изменена!)
ALBA=32400 ; -+
Zero=0 ; -+
PMin=0 ; -+
PSec=0 ; -+
PFrame=0 ; -+
PLBA=-150
[TRACK 1]
MODE=0
INDEX 1=45000
Листинг 7 образ защищенного диска, снятый программой Clone CD (несоответствующие поля выделены жирным шрифтом)
Сокращение сессий с двух до одной очень сильно смущает. Куда девалась вторая –— неискаженная(!) –— сессия вообще непонятно. И, хотя искаженные данные первого трека сохранились, оказались неожиданно измененными поля Application Code и ATIP (и это несмотря на то, что запись производилась на туже самую болванку CD-RW, что и раньше, хотя в ее "прожиг" осуществлялся различными приводами). Самое удивительное –— вместо действительного адреса выводной области программа Clone CDCloneCD указала какую-то "муть". По ее мнению абсолютный Lead-outLead-Out адрес равен 00:29:33, в то время как Lead-outLead-Out оригинального диска располагался в позиции 03:24:23, а стартовый адрес первого трека скопированного диска –— 10:02:00. Да! Адрес выводной области оказался расположенным до начала стартового адреса первого трека! Вот так копировщик –— не справился с "родной" защитой диска, но "навесил" на него свою собственную. Между прочим, диски си искаженным адресом выводной области способны выводить [Y164] механику приводов из строя на чисто физическом уровне. (подробнее см[Y165] . )
Как следствие: скопированный диск оказывается работоспособен не на всех приводах (ASUS, NEC и TEAC его прочитают, хотя увидят лишь первую сессию, а вот PHILIPS –— откажется использовать такой диск вообще), к тому же защите ничего не стоит прочитать текущий TOC и сравнить его с эталонным. Благодаря тому обстоятельству, что TOC скопированного диска оказался чудовищно искажен, становится легко отличить оригинал от его "пиратского" дубликата (конкретный пример привязки см. разд. "Пример реализации защиты на программном уровне" этой главы).
Короче говоря, "факир был пьян и фокус не удался". Что ж, попробуем обратиться за помощью к программе Alcohol 120% –— уж она-то должна наверняка с этим справиться! Действительно, Alcohol 120% видит обе сессии: как искаженную, так и неискаженную, однако по малопонятным причинам сохраняет в образ лишь вторую из них (Clone CDCloneCD сохранял первую). Ну что это за "зоопарк" такой, а? Кажется, что содержимое TOC скопированного диска можно даже и не сравнивать –— там будет далеко не то, что защита собирается ожидать. Тем не менее, вопреки всем пессимистическим предчувствиям, содержимое TOC, снятое программой Alcohol 120% практически полностью соответствует оригиналу. Единственно, в чем ошибся Alcohol 120% –— определил тип Pre-gap обоих треков не как Mode 1, но и как Mode 2 (рис. 6.4). Впрочем, в силу отсутствия в образе первой сессии (рис. 6.5), полученная с его помощью копия диска все равно оказывается неработоспособной.
![](image/index-image037.jpg)
Рис.унок 6. 34. 0x061 Alcohol 120% видит обе сессии защищенного диска, но…
![](image/index-image038.jpg)
Рис. 6.5унок 4. 0x62 Alcohol 120% копирует лишь вторую сессию, а первую нагло пропускает
А ведь заявлялось, что копировщики Clone CDCloneCD/Alcohol 120% способны копировать любые существующие на сегодняшний момент защищенные диски и вдруг на проверку оказывается, что даже такую простую защиту, которую может создать "на кончике пенька" любой программист (даже начинающий!) они преодолеть ни вместе, ни по раздельности не в состоянии! Причем, аппаратура, на которой все эти эксперименты и осуществлялись, возможность корректного копирования искаженного диска гарантированно поддерживает (сам проверял!) и потому отмахнуться физическими ограничениями приводов разработчикам обоих копировщиков уже не удастся!
Копировщик Alcohol 120% сообщает (рис. 6.6): "Размер образа НЕ соответствует Lead-Out, записанному в TOC!" (что есть прямое следствие ошибочного снятия образа копировщиком из-за искажения стартового адреса первого трека). "Адрес Lead-Out в TOC: 03:22:23.
Размер образа: 12:54:65. Для продолжения выберите одну из опций ниже!" Предлагаемые опции:
q Сохранить TOC как на исходном CD, записать до конца файла-образа;
q Изменить адрес Lead-out по длине файла-образа;
q Сохранить TOC как на исходном CD записать до адреса Lead-out.
![](image/avtomaticheskoe-kopirovanie-i-obsuzhdenie-ego_2.gif)
Рис. 6.6унок 5. 0x065. Сообщение выводимое программой Alcohol 120% Алкоголь говорит: "Размер образа НЕ соответствует Lead-Out, записанному в TOC! [что есть прямое следствие ошибочного снятия образа самим Алкоголем из-за искажения стартового адреса первого трека –КК]. Адрес Lead-Out в TOC: 03:22:23. Размер образа: 12:54:65. Для продолжения выберите одну из опций ниже: Сохранить TOC как на исходном CD, записать до конца файла-образа; Изменить адрес Lead-Out по длине файла-образа; Сохранить TOC как на исходном CD записать до адреса Lead-Out"
Выбираем "сохранить TOC как на исходном CD, записать до конца файла-образа" и получаем:
08:46:48 [Y166] (G:) TEAC CD-W552E (1:1): Во время записи произошла ошибка!
08:46:48 Ошибка: [05/26/00] - Invalid Field In Parameter List
08:46:48 (G:) TEAC CD-W552E (1:1): Во время записи произошла ошибка!
08:46:48 Загрузка файла-образа отменена!
08:46:51 Во время записи произошли какие-то неполадки! Просмотрите файл с отчётом и сообщите обо всех ошибках в службу техподдержки.
Даже не вериться, что такой простой прием "ослепляет" лучшие копировщики защищенных дисков! Неужели и вправду, создания некопируемых дисков вполне осуществимо на обыкновенном бытовом оборудовании?! Да! Именно так! Конечно, не стоит путать некопируемость диска автоматическими копировщиками с принципиальной невозможностью получения его идентичной копии. В ручном режиме копирование таких дисков вполне осуществимо (правда, при условии, что ваш пишущий привод поддерживает режим RAW DAO, а читающий –— читает сектора из обоих секций) и сейчас мы продемонстрируем как.
>>>>> Блокирование/разблокирование кнопки Eject
Если приложение, взаимодействующие с CD, выполняет операцию, которая не должна быть ни при каких обстоятельствах прервана, можно воспользоваться ICTL-командой блокировки лотка–— IOCTL_CDROM_MEDIA_REMOVAL (а вот и ее непосредственное значение: 0x24804). При попытке выполнитьсделать возврат диска —у "Eject" — при заблокированном лотке, мой накопитель PHILIPS CDRW начинает "злобно" моргать" красным огоньком, показывая, что диск находится внутри (""IN"), но он заблокирован ("is locked"). Вплоть до момента разблокирования лотка извлечь диск можно разве что при помощи булавкиой или перезагрузивкой операционнуюой системуы.
Уже одно это создает богатое поле для всевозможных "пакостей" со стороны многочисленных злоумышленниковзлоумышленников, да и просто некорректно работающих программ, успевающих "умереть" от критической ошибки прежде, чем разблокировать лоток. Как с этим бороться? Да очень просто –— разблокировать лоток самостоятельно!
Дело в том, что система не требует, чтобы разблокирование выполнялось в контексте того процесса, который выполнил блокирование. Она просто ведет счет количества блокировок, и если тот равен нулю, –— лоток свободен. Соответственно, если счет блокировок равен, например, шести –— мы должны шесть раз вызывать команду разблокирования, прежде чем лазерный диск удастся извлечь на свет божий.
Утилита, исходный текст которой приведен в листинге 2.5.4ниже, позволяет манипулировать счетчиком блокировок диска по вашему собственному усмотрению. Аргумент командной строки "+" увеличивает значение счетчика на единицу, а "–" –— уменьшает на единицу. При достижении счетчиком нуля дальнейшие попытки его уменьшения не возымеют никакого действия.
Как это можно использовать? Ну, например, для преждевременного извлечения диска из записывающей программы, что полезно для экспериментов. Другое применение: отлучаясь от своего компьютера на несколько минут, вы можете заблокировать диск, чтобы быть уверенными, что окружающие коллеги его не упрут.
>>>>> Что читать (врезка)
Несмотря на то, что данный раздел является вполне самодостаточным и весь минимально необходимый математический аппарат излагает самостоятельно без отсылок к сторонней литературе, желание углубить свои знания вполне естественно и его можно только приветствовать. А потому будет лучше, если вы не ограничитесь одной этой книгой, но "перевернете целые горы" специализированной литературы, с каждым разом все больше и больше ужасаясь глубине той пропасти, что отделяет ваши поверхностные представления от действительно настоящих знаний. Теория помехоустойчивого кодирования столь обширна, что для ее изучения потребуется как минимум целая жизнь.
Итак, с чего начать?
q Blahut Richard "Theory and Practice of Error Control Codes", Mass.: Addison-Wesley, 1983.
Очень хорошая книжка из категории "must have"; по слухам есть в электронном виде в сети, однако, к сожалению самой книжки я так и не нашел, но тучи ссылок на нее убедительно свидетельствуют о высоком качестве последней. Так же имеется ее русскоязычный перевод, выпущенный издательством "Мир" (см. далеениже).;
q Блейхут Р. "Теория и практика кодов, контролирующих ошибки" М.: Мир, 1986. 576 с.
Технически грамотный и добротный перевод уже упомянутой ранеевыше книги Блейхута (Blahut Richard) (ах, какие в издательстве Мир были переводчики!), электронной копии в сети, к сожалению, нет.;
q James Plank "A tutorial on Reed-Solomon Coding for fault-tolerance in RAID-like systems".
Неплохое руководство по использованию кодов Рида-Соломона для построения отказоустойчивых RAID-подобных систем, ориентированное на математически неподготовленных системных программистов и доходчиво объясняющее суть помехоустойчивого кодирования с примерами исходных текстов на языке Си. Электронная копия руководства доступна по адресу: http://www.cs.utk.edu/~plank/plank/papers/CS-96-332.pdf. Настоятельно рекомендую прочитать, даже если вы и не собираетесь заниматься сборкой RAID.;
q Joel Sylvester "Reed Solomon Codes".
Предельно кратное описание принципов работы кодов Рида-Соломона с блок-схемами вместо исходных текстов. На практическое руководство не тянет, но общую картину все- таки дает, почитайте. Руководство доступно по адресу: http://www.elektrobit.co.uk/pdf/reedsolomon.pdf.;
q Tom Moore "REED-SOLOMON PACKAGE" (old tutorial)
Роскошный сборник разнообразных руководств по кодам Рида-Соломона, наверное, лучший из всех, что я видел. Включает в себя краткное описание основ теории полей Галуа, базовые принципы построения кодеров/декодеров Рида-Соломона и законченные примеры реализации самих кодеров/декодеров на языке Си (правда, недостаточно добросовестно прокомментированные). Сей материал (stuff) неоднократно промелькивал в сети ФИДО и последний раз был замеченпостился 28 декабря 1994 года в конференции comp.compression. Его легко найти в "Гугле[Y62] [n2k63] " по ключевым словам "Reed-Solomon+main+ECC". Настоятельно рекомендую.
q Ross N.Williams "A painless guide to CRC error detection algorithms".
Подробное руководство по кодам CRC полезное достаточно внятным и доступным описанием полиномиальной арифметики, без которой работа с кодами Рида-Соломона просто не мыслима. Доступно в электронной форме по следующему адресу: ftp://www.internode.net.au/clients/rocksoft/papers/crc_v3.txt. Так же имеется его неплохой перевод на русский язык, легко отыскивающийся в сети по запросу "Элементарное руководство по CRC алгоритмам обнаружения ошибок". Настоятельно рекомендую.
q ftape (драйвер ленточного накопителя из дистрибуьютива Linux).
Ну какая же запись на магнитную ленту обходится без корректирующих кодов? Представить себе такое прямо-таки скажем довольно затруднительно. Поэтому, анализ исходных текстов драйверов ленточных накопителей дает довольно-таки богатую пищу для размышлений (при условии, конечно, если исследуемый драйвер действительно использует коды Рида-Соломона, а не что- ни будь другое).Линуховый Драйвер ftape, из дистрибутива Linux, как раз и является тем драйвером, что вам нужен, а непосредственно сам код, ответственный за кодирование/декодирование кодов Рида-Соломона вынесен в файл ftape-ECC.c/ftape-ECC.h. Это достаточно аккуратный, хорошо структурированный и даже местами слегка комментируемый код, так же рекомендую.
q James S. Plank GFLIB "C Procedures for Galois Field Arithmetic and Reed-Solomon Coding".
Библиотечка для работы с кодами Рида-Соломона. Содержит в себе полные исходные тексты всех необходимых функций и распространяется по лицензии GPL (General [GNU] Public License). Найти ее можно на любом GNU'том сайте GNU, например, по адресуздесь: http://www.cs.utk.edu/~plank/plank/gflib/gflib.tar.
CloneCD
Точка останова, установленная на функцию CreateFileA показывает, что программа Clone CD общается с диском посредствомчерез своегой собственногоый драйвера –— \\.\ELBYCDIO, причем по не совсем понятным причинам его открытие происходит в цикле, так что дескриптор драйвера возвращается многократно.
переводчику
сухой user [level, data]
Декодер (decoder)
Декодирование кодов Рида-Соломона представляет собой довольно сложную задачу, решение которой выливается в громоздкий, запутанный и чрезвычайно ненаглядный программный код, требующий от разработчика обширных знаний во многих областях высшей математики. Типовая схема декодирования, получившая название авторегрессионого спектрального метода декодирования, состоит из следующих шагов:
4. Вычисления синдрома ошибки (синдромный декодер).
5. Построения полинома ошибки, осуществляемое либо посредством высокоэффективного, но сложно реализуемого алгоритма Берлекэмпа-Месси, либо посредством простого, но тормозного Евклидового алгоритма.
6. Нахождения корней данного полинома, обычно решающееся лобовым перебором (алгоритм Ченя).
7. Определения характера ошибки, сводящееся к построению битовой маски, вычисляемой на основе обращения алгоритма Форни или любого другого алгоритма обращения матрицы.
8. Наконец, исправления ошибочных символов, путем наложения битовой маски на информационное слово и последовательного инвертирования всех искаженных битов посредствомчерез операциию XOR (исключающее ИЛИ).
Следует отметить, что данная схема (рис. 2.3) декодирования не единственная и вероятно, даже не самая лучшаяей, но зато универсальная. Всего же существует около десятка различных схем декодирования абсолютно непохожих на друг друга и выбираемых в зависимости от того какая часть декодера реализуется программно, а какая аппаратно.
Рис. 21.33. 0x337 Схема авторегрессионого спектрального декодера корректирующих кодов Рида-Соломона
Деление в полях Галуа
Деление в полях Галуа осуществляется практически точно так, как и умножение с той лишь разницей, что индексы не прибавляются, а вычитаются друг из друга. В самом деле: a/b== 2i/2j == 2(i –- j). Для перевода из полиномиальной в индексную форму и наоборот может использоваться уже приводимая выше таблица look-up таблица.
Естественно, не забывайте о том, что какими бы "извращенными" поля Галуа ни были, а на нуль даже в абстрактной арифметике делить нельзя и функция деления должна быть снабжена соответствующей проверкой.
Листинг 21.16. Функция быстрого табличного деления в полиномов в полях Галуа
// функция возвращает результат деления
// двух полиномов a на b в полях Галуа
// при попытке деления на ноль функция
// возвращает -1
int gf_div(int a, int b)
{
int diff;
if (a == 0) return 0; // немного оптимизации не повредит
if (b == 0) return -1; // на ноль делить нельзя!
diff = alpha_of[a] – alpha_of[b]; // вычисляем разность индексов
if (diff < 0) diff += GF-1; // приводим разность к модулю GF
return index_of[diff]; // переводим результат в полиномиальную…
// …форму и возвращаем результат
}
Диск читается с ошибками
Если не смотря на все ухищрения типа снижения скорости или очистки поверхности диск все равно читается с ошибками и сбойные сектора приходится как раз на область, занятную ценнейшими файлами –— "дело труба". Но все же, шансы успешного восстановления данных есть, пускай и небольшие.
Прежде всего: ошибка ошибке рознь. Редко бывает так, чтобы сектор не читался весь целиком. Как правило, речь идет об искажении одного или нескольких принадлежащих ему байт. Причем, корректирующая способность избыточных кодов такова, что до 392 сбойных байт исправляется уже в декодере первого уровня (CIRC-декодере). Еще до 86 ошибок способны исправлять P-коды и до 52 ошибок –— Q-коды. Таким образом. е. при наиболее благоприятном распределении ошибок удается восстановить вплоть до 530 ошибок или до ~25% общей емкости сектора. Лишь чудовищная ненадежность оптических носителей приводит к тому, что даже такая колоссальная избыточность данных иной раз не в силах противостоять сбоям.
В зависимости от установочных параметров, накопитель, обнаружив неустранимый сбой, либо отдает сектор в том виде, в котором его удалось прочесть, либо же просто рапортует об ошибке, оставляя содержимое выходного буфера в неопределенном состоянии. Идея восстановления состоит в том, чтобы заставить привод выдавать все, что он только способен прочесть. Конечно, искаженные байты уже не вернуть назад, однако, многие форматы файлов вполне лояльно относятся к небольшим разрушениям. Музыка в формате MP3/WMA, видеофильмы, графические изображения –— все они будут вполне нормально воспроизводиться и только непосредственно на месте самого искажения возникнет щелчок той или иной громкости или мелькнет "артефакт". С архивами ситуация обстоит значительно хуже, однако, в подавляющем большинстве случаев вы потеряете всего один-единственный файл, а все остальные содержимое архива распакуется нормально (кстати, некоторые архиваторы, такие например, как RAR поддерживают собственные корректирующие коды, позволяющие при минимальной избыточности восстанавливать "битые" архивы).
Постойте! –— возразят мне иные читатели –— Как же, было дело! Пробовали мы восстанавливать не читающиеся диски теми или иными утилитами. Ну и что? "Вылеченный" mpg- или avi-файл система наотрез отказалась считать видео-файлом! Так все дело в том, –— резонно возражу я, –— что эти самые утилиты просто выкидывали все сектора, которые они не могли прочесть, в результате чего размер файла, а значит, и относительные смещения всех его структур изменились! Неудивительно, что после такой "кастрации" он перестал воспроизводится!
Воспользуйтесь любым копировщиком защищенных дисков, предоставляющим выборочное управление режимом обработки ошибок и выберите режим 24h (максимально возможная коррекция ошибок без прерывания передачи данных в случае невозможности их восстановления). Среди прочих утилит для этой цели подойдет тот же cd_raw_read, разработанный автором. Как альтернативный вариант вы можете использовать Alcohol 120% и/или Clone CDCloneCD.
Спрашиваете: а по каким причинам сектор может перестать читаться? Прежде всего это глубокие и широкие радиальные царапины со стороны верхней части. Преодолев тонкий барьер защитного лакового слоя царапины "выедают" непосредственно сам отражающий материал, а вместе с ним –— и полезные данные.
Немногочисленные узкие царапины в общем-то не опасны –— содержимое сектора размазано вдоль спиральной дорожки и потому выпадения нескольких байт легко компенсируются за счет избыточности. Правда, тут есть одно "но". Откуда приводу знать сколько именно "питов" и "лендов" было пропущено? Поскольку, "питы" и "ленды" напрямую не соответствуют двоичному нулю и единице, и единица кодируется переходом от "пита" к "ленду" или наоборот, а нуль –— отсутствуем переходов на данном участке, становится понятно, что пропадание нечетного числа "питов"/"лентов" как бы переворачивает весь хвост фрейма с ног на голову, т. е.
другими словами его "гробит" его. Отсюда: даже одна- единственная царапина способна породить целый каскад ошибок, не устранимых штатными корректирующими кодами, но в принципе, поддающимися ремонту в ручную. Ну, не то, чтобы совсем вручную, –— необходимая для этой цели утилита уже написана автором и сейчас проходит стадию альфа- тестирования, на ура читая те диски, которые не читались нормальным путем. Не исключено, что к моменту выхода данной книгистатьи она перейдет в стадию бета- тестирования и станет бесплатно доступна всем желающим. Впрочем, поскольку длина одного фрейма составляет всего 24 байта, разрушение нескольких подряд идущих фреймов может быть реконструировано и штатными корректирующими кодами и к помощи моей утилиты придетсяходится прибегать лишь на сильно поцарапанных дисках.
Широкие царапины –— другое дело. Мало того, что они "съедают" несколько фреймов целиком, так они еще и оптическую головку сбивают оптическую головку с дорожки. Попав в образованную царапиной дыру, головка совершенно дезориентируеются (ей становится попросту не на что "опираться"!) и "вылетает" в одну из соседних дорожек. Умные приводы автоматически распознают такую ситуацию и позиционируют головку на нужное место. Приводы поглупее (коих, кстати, подавляющее большинство) самоуверенно продолжают чтение, как ни в чем ни бывало. В результате, голова одного сектора скрещивается с хвостом другого и… естественно, при попытке восстановления такого сектора штатными корректирующими кодами ничего, кроме "мусора" не получается и привод уныло диагностирует неисправимую ошибку. Выход –— читать такой сектор до тех пор, пока головка не попадает на туже самую дорожку, с которой начиналось чтение сектора. Количество попыток чтения при этом должно быть достаточно велико (от 100 и больше). Ведь с точки зрения вероятности отклониться от спиральной дорожки намного проще, чем удержаться на ней!
Концентрические царапины –— самый деструктивный тип разрушений который только может быть.Размазывание информации вдоль спиральной дорожки теперь не в силах противостоять сбою, поскольку искажение затрагивает весь сектор целиком (радиальные царапины, напротив, искажают лишь небольшую часть сектора). К тому же концентрические царапины сбивают систему слежения, поскольку следящие лазерные лучи слега расфокусированы и потому оказываются весьма чувствительны к подобным дефектам поверхности.
Царапины, расположенные с нижней стороны диска, в большинстве случав устраняются полировкой, а вот царапины, "высверлившие" рабочий слой, ликвидировать –— увы –— невозможно.
Диск, начинающийся не с первого трека
"Ну и запросы у вас..." —–
"сказала" база данных и "повисла".
Требование стандарта начинать нумерацию треков с единицы в купе с наличием point'a указателя A0h, хранящего номер первого трека выглядит несколько странным, если не сказать —– избыточным. Мне могут возразить, что point указатель A0h имеет смысл для второй и всех последующих сессиий, но я резонно отвечу: во-первых, номер первого трека каждой сесссии равен point'у указателю A1h предыдущей сессии плюс единица, во-вторых, номер первого трека всякой сессии —– это наименьший номер трека из всех треков, принадлежащихей данной сессии. Так что point'ы указатели A0h и A1h все равно избыточны и предназначены исключительно для быстрого определения номера первого и последнего треков, без анализа всего TOC'а целиком, что для "хлипбких" микропроцессоров первых аудиоплееров было действительно актуальным.
Но вот интересно —– анализирут ли современные приводы эти point'ы указатели или молчаливо закладываются на номер первого трека по умолчанию (чего стандарт кстати деать не запрещает)? Попробуем это выяснить, создав диск, начинающийхся не с первого трека, а, например, сразу с трека номер два. Используя свой предыдующий опыт мы это сделаем без труда. Достаточно лишь изменить point указатель 0x01 на point указатель 0x02, соответствующим образом перенумеровав все point'ы указатели последующих треков (если они есть), изменить [TRACK 1] на [TRACK 2] и перенумеровать все последующие треки (если они есть), наконец, увеличить point указатель A1h первой сессии, а так же point'ы указатели A0h или /A1h всех последующих сессий на единицу. Если этого не сделать, оставив point'ы указатели "прозябать" по умолчанию, то такой диск не будет читаться приводом NEC вообще, приводу TEAC'у он будет доступен лишь на секторном уровне и даже ASUS "увидит" только первую сессию, да и то лишь после долгих раздумий и сексапильных подергиваний головкой (так что ASUS —– это "круто"рулез, правда в других отношения он ведет себя весьма нервно).
При попытке копирования такого диска посредством Clone CDCloneCD, последний "скажет", что диск пуст (хотя это и нет так), зато при очистке диска пишет "диск пуст: нет". Alcohol 120% Алкоголик на таком диске так же ничего "в упор не видит" и, равно как и Clone CDCloneCD, ничего и не копирует. Если вы уверены, что приводы ваших пользователей способны читать такие диски хотя бы на секторном уровне, вы легко можете создать практически непрошибаемую защиту, которую не скопирует практически ни один копировщик (мой —– скопирует, поскольку он вообще не заглядывает в TOC), однако, ввиду потенциальной конфликтности защиты такого типа на вашем месте я бы этого делать не стал, ну разве что в качестве курсовой работы, которая все равно никому не нужна, так почему бы тогда и не поизвращаться? (Помините историю о том парне, который написал в курсовой "тому, кто дочитает до этого места —– ставлю ящик пива/водки/шампанского"?).
Диск, начинающийся не с первого трека, но с корректно установленными point'ами указателями A0h и A1h практическими всеми, доступными мне приводами, читается нормально. И все бы хорошо, да вот досада —– привод NEC, под тихие щелкающие звуки, "завешивается" таким диском вплоть до нажатия на кнопку Eject. Так что это не очень хорошая защита, тем более, что тот же Clone CDCloneCD "обламывает" ее по полной программе, создавая вполне корректную и работоспособную копию. Иначе ведет себя Alcohol 120%Алкогль, выбрасывающий сообщение об ошибке "access violation" (нарушение доступа) и аварийно завершающий свое выполнение.
Таким образом, приводы ASUS и TEAC активно используют point'ы указатели A0h и A1h, а NEC судя по всему рассчитывает на то, что нумерация треков всякого диска обязательно должна начинаться с единицы, что с одной стороны не противоречит Стандарту, а с другой стороны – и не соответствует ему, т. к. point указатель A0h присутствует в нем не даром и его игонирование ни к чему хорошему не приводит.
Ситуация с Alcohol 120% Алкоголем до конца не ясна. Ошибка доступа —– очевидное следствие грубых алгоритмических ошибок и просчетов проектирования. Скорей всего, Alcohol 120% Алкоголь читает TOC в буфер и последовательно сканирует его на предмет поиска трека номер один, забывая при этом контролировать "вылет" за доступные буферу границы.
А вот Clone CDCloneCD понимает стандарт правильно, благодаря чему копирует защищенные диски на ура (впрочем, не в обиду будет сказано, такое поведение Clone CDCloneCD скорее исключение, чем правило и, как мы видели выше и, как мы увидим далее, редкий диск с искаженным TOC'ом удается скопировать без ошибок).
Диск не опознается приводом
Вы вставляете диск в привод. Привод раскручивает диск, судорожно мигая при этом индикатором активности, затем, убедившись в том, что на заданной скорости диск не читается, начинает снижать обороты вплоть до полной остановки диска. Индикатор "DISKIN" (если он присутствует на лицевой панели привода) печально тухнет, давая тем самым понять, что кусок пластика, засунутый в привод, с точки зрения привода представляет собой все, что угодно, но только не компакт-диск. При попытке обращения к диску выдается сообщение об отсутствии диска в дисководе и вежливое предложение его туда вставить.
Неспособность привода опознать диск в подавляющем большинстве случаев это свидетельство неисправности CD-ROM привода. Реже –— дефективности самого лазерного диска. Даже если вчера этот диск вполне уверенно опознавался и даже если привод опознает все остальные диски –— не спешите уверять себя в его, привода, работоспособности! Попробуйте прочитать диск на другом накопителеприводе. На худой конец –— уменьшите скорость вращения диска до минимальной, однако, будьте готовы к тому, что привод вас не послушается. Дело в том, что большинство приводов автоматически сбрасывают прежние установки скорости при смене диска и не позволяют изменять скорость вплоть до тех пор, пока диск не будет опознан (особенно этим "славятся" приводы TEAC, приводы от ASUS обычно ведут себя более демократично).
Если же подопытный диск отказывается опознаваться всеми доступными вам приводами, то причина скорее всего в том, что приводы не могут прочесть оглавление диска (так же называемое TOC'ом), хранящееся в области Lead-inLead-In области. Выньте диск из привода и внимательно рассмотрите узкое блестящее кольцо, расположенное у внутреннего края диска –— это и есть Lead-inLead-In. Нет ли на нем глубоких царапин или загрязнений? Загрязнения удалите чистой салфеткой (к слову сказать, при очистке диска про вводную область зачастую как-то забывают, вероятно, принимая ее за бесполезное декоративное украшение).
Бороться с царапинами намного труднее и без надлежащего опыта полировки лазерных дисков за это дело лучше не браться. Лучше всего было бы отнести такой диск в сервисный центр, специализирующийся на восстановлении информации, однако, далеко не во всяком городе такие центры вообще есть и далеко не всегда они выполняют такое восстановление оперативно и грамотно. Опять-таки: конфиденциальность, стоимость восстановления и прочее, прочее, прочее…
Можно ли восстановить такой диск самостоятельно? Да, можно, но для этого вам понадобиться определенное оборудование, стоящее порядка 1000 рублей (~30$). Конкретно –— отдельный привод CD-ROM привод, над которым будет не жалко поизмываться и потерей которого вы окажетесь не слишком сильно огорчены (очень хорошо подходят для этих целей низкоскоростные приводы, оставшиеся от последнего апгрейда (upgrade) системы).
Весь фокус в том, что для работы с диском на сектором уровне TOC не так уж и нужен и без него вполне можно обойтись. Фактически это не аппаратная, а программная проблема. Обнаружив, что в процессе чтения оглавления диска возникли неустранимые ошибки, микропрограмма, зашитая в ПЗУ привода, отказывает такому диску в обработке, несмотря на то, что содержимое TOC'a'а дублировано в Q-канале подкода и размазано по всей спиральной дорожке. Причем, привод реально нуждается лишь в трех основных полях TOC'a: адресе выводной области диска (чтобы знать до сих пор можно "дергать" головкой), стартовом адресе первого трека
(чтобы знать откуда начинать чтение данных) и адресе следующей вводной области (только для много сессионных приводов). Со стартовым адресом первого трека разобраться проще всего –— он по жизни равен 00:02:00 (что соответствует нулевому LBA-адресу). Адрес Lead-outLead-Out, напрямую зависящий от объема лазерного диска, не обязательно указывать точно, достаточно выбрать его таким, чтобы он был не меньше адреса настоящего Lead-outLead-Out, иначе все, расположенные за ним сектора, окажутся недоступными.
Установив адрес Lead-outLead- Out на 80- или даже 90 минут мы можем гарантировать, что вся поверхность диска будет доступна приводу. Короче говоря, имей мы доступ ко внутренним структурам прошивки привода, восстановление разрушенного TOC'a было бы плевым делом. Автор использует для этих целей специальным образом модифицированную им прошивку обыкновенного привода CD-ROM привода (старенькая 8x модель от no name), которая позволяет манипулировать любыми служебными данными и потому читает все, что только физически можно прочесть.
Если же взломхачинье микропроцессорных программ вам не по зубам, можно пойти другим путем. Аккуратно разберите привод CD-ROM привод и извлеките его начинку из корпуса (теперь вы поняли почему автор порекомендовал купить для этих целей отдельный –— максимально дешевый –— привод?). Теперь, открутите болты, удерживающие металлическую планку, на которой закреплен эдакий "пятачок", прижимающийся к верхнему краю лазерного диска и тем самым уберегающий его от проскальзывания. Вместо этой нехитройкузявой конструкции вы можете использовать металлическое кольцо или иную тяжесть. Главное, –— получить свободный доступ к лазерному диску и возможность его "горячей" смены на ходу без выдвижения лотка.
Подключите привод CD-ROM к компьютеру и, включив питание последнего, нормальным путем вставьте в привод специальным образом подготовленный диск, адрес выводной области которого лежит в районе 80 —– 90 минут (можно просто вставить любой компакт-дискCD с видеофильмом от 700 Ммегабайт). Убедившись, что диск нормально опознан, дождитесь его полной остановки и, – не выключая компьютера, – аккуратно снимите его с привода, ни в коем случае не открывая лоток. Теперь, – установите в привод тот диск, который вы собираетесь восстанавливать. Поскольку TOC старого диска уже находится в кэше, а замену диска, совершенную таким варварским способом, привод обнаружить не в состоянии, он будет работать с новым диском точно так же, как и со старым.
Только не пытайтесь читать содержимое диска средствами операционной системы –— это ни к чему хорошему ни приведет (ведь она тоже умеет кэшировать и сколько бы вы ни жали на "обновить" в окне проводника будет неизменно прежнее оглавление). Лучше возьмите любой "грабер", читающий диск на секторном уровне и не задающий при этом лишних вопросов (например, можно воспользоваться утилитой cd_raw_read, бесплатно распространяемой автором этой статьи) и скопируйте все содержимое диска от первого сектора до последнего в файл-образ, а затем, используя любую подходящую программу "прожига", поместитезалайте его на носитель CD-R или CD-RW. Пусть вы не восстановите сам диск, но зато –— его содержимое! Эта методика с одинаковым успехом применима как для аудиодисков, так и для дисков с данными.
Как вариант: можно не откручивать прижимную планку, а найти датчик смены диска и на время сделать ему "харакири", заставляя привод "думать", что восстанавливаемый диск не был заменен (дешевые приводы используют простые механические датчики, сразу же бросающиеся в глаза, в более дорогих моделях отдельного датчика вообще нет и признаком смены диска считается нажатие на кнопку Eject; в этом случае с некоторым риском можно воспользоваться отверстием для аварийного извлечения диска, однако, имейте ввиду, что извлечение диска на работающем приводе может необратимо искалечить его механическую часть).
К слову сказать, существуют и такие приводы, которые ухитряются читать диск даже при полностью разрушенном TOC'е. К ним в частности относятся некоторые модели пишущих устройств"писцов" от фирмы MSI. Обладателем этих приводов незачем развинчивать свой CD-ROM –— сбойный диск он прочтет и так.
Так же, при восстановлении многосессионных дисков можно попробовать просто зачернить вводную область диска черным маркером, –— содержимое первой сессии при этом окажется утраченным, но вот все последующие сессии большинство приводов прочтут на ура.Напоминаю, что вводная область диска выглядит как блестящее кольцо, расположенное вокруг внутренней кромки диска.
Диск опознается приводом, но не опознается операционной системой
Вы вставляете диск в привод. Привод раскручивает диск, зажигает индикатор DISK IN (если он есть), однако, попытка просмотра содержимого диска штатными средствами операционной системы приводит к сообщению о той или иной ошибке. Сканирование поверхности диска утилитой Ahead Nero CD Speed (или любой другой утилитой аналогичного назначения) выявляет один или несколько разрушенных (damaged) секторов.
Это –— явный симптом повреждения файловой системы, а точнее –— ее корневого каталога. Если это произошло –— не хватайтесь за сердце. Восстановление коревого каталога лазерных дисков в отличии от винчестеров и дискет не представляет большой проблемы. Подавляющее большинство лазерных дисков содержат не одну, а сразу две файловых системы, дублирующих друг друга –— ISO- 9660 и Joliet (таковыми являются все диски, выпушенные после 1995 года). Согласитесь, одновременное разрушение сразу двух корневых каталогов –— событие крайне маловероятное. К тому же, в силу отсутствия фрагментации, вложенные подкаталоги не разбросаны по всей поверхности лазерного диска, а сосредоточенны в одном месте, благодаря чему даже при полностью разрушенном корневом каталоге их достаточно легко восстановить. Наконец, каждая последующая сессия многосессионого диска включает в себя содержимое файловых систем всех предыдущих сессий (исключая, разумеется, удаленные файлы). А потому, при "смерти" файловой системы последней сессии, мы без труда можем спасти содержимое всех остальных.
К сожалению, штатные средства Windows не предоставляют возможности выборочного монтирования ни предпочтительной файловой системы, ни предпочтительной сессии, принудительно подсаживая нас на корневой каталог системы JolietДжульеты [n2k30] последней сессии диска. Самое простое, что можно сделать –— попробовать прочитать диск под "голой" MS-DOS с установленным драйвером MSCDEX, работающим исключительно с системой ISO- 9660 и игнорирующим существование Joliet. Как вариант, вы можете воспользоваться утилитой ISO 9660.dir, разработанной автором специально для работы с разпорушенными файловыми системами и восстанавливающей все, что только можно восстановить.
Естественно, в силу того, что максимальная длина файловых идентификаторов в системе ISO- 9660 составляет всего лишь 11 символов, длинные файловые имена оказываются необратимо искажены, однако, согласитесь, это все же лучше чем совсем ничего.
Диск с нулевым треком
Задумывались ли вы, почему нумерация треков лазерных дисков начинается с единицы, а не с нуля? Ведь говорят, чтобы отличить программиста от простого смертного достаточно дать ему команду "рассчитайсь!". Нормальный человек скажет "первый" (если он действительно первый) и будет по-своему прав. Программист же сначала уточнит в какой системе исчисления вести расчет (в двоичной, восьмеричной, шестнадцатеричной…) и затем, сделав шаг вперед, гордо скажет "нулевой". "Так ведь лазерные диски изначально разрабатывались для пользователей!"—– ответите вы, —– "а пользователи более привычны к натуральной, а не позиционной системе исчисления и потому первый трек должен быть именно первым, но никак не нулевым".
И все же, несмотря на всю убедительность своих доводов, вы будете не правы. Отсчет треков всякого диска начинается не с единицы, ано с нуля. Да, нулевой номер зарезервирован за служебным треком (вводной областью диска) и его содержимое недоступно на интерфейсном уровне, но это ничего не меняет! Поле TNO (Track Number) Q-подканала диска области Lead-inLead-In области диска равно нулю, следовательно, с точки зрения привода всякий диск начинается с трека номер ноль. Электронная начинка привода читает и адресует нулевой трек точно так же, как и любой другой трек диска, сохраняя тем самым прозрачность и упорядоченность принятой системы нумерации. С точки зрения системных программистов, разрабатывающих микропрограммные прошивки, отсчет треков всегда начинается с нуля. С точки же зрения пользователей привода —– с единицы. Одним словом, и "волки сыты и овцы целы!"
Атрибуты нулевого трека отсутствуют в TOC, поскольку этот трек как раз и служит для хранения TOC. Давайте задумаемся, что произойдет, если одному из point'ов указателей подлинного или фиктивного трека мы присвоим значение ноль, то есть, попросту говоря, создадим еще один нулевой трек в пользовательской области диска?
Если помимо внесения подложных данных в TOC, мы еще и скорректируем содержимое Q-канала подкода, забив заполнив поле TNO нулями, то с точки зрения привода такой трек будет неотличим от вводной области диска и попытка его посекторного чтения будет обречена на провал (хотя некоторые приводы и не такое читают).
Субканальные данные нулевого трека теоретически должны быть доступны для чтения командами SEEK или /READ SUBCHANNELL, но никаких гарантий на счет этого у нас нет, поскольку наличие двух подряд идущих областей Lead-inLead-In областей сильно нервирует привод, и его реакция становится совершенно непредсказуемой. Отказ от восстановления субканальных данных мало что меняет. Одно лишь наличие нулевого point'a указателя в TOC'е —– событие вполне неординарное и взаимно противоречивое.
Большинство приводов просто "свихнутся" и откажутся обрабатывать такой диск, совершенно непредсказуемым образом осуществляя чтение его оглавления. В частности, NEC при выполнении команды READ TOC возвращает ошибку, ASUS воспринимает нулевой трек как индикатор завершения TOC'а, а TEAC, столкнувшись с нулевым треком, начинает очень сильно "нервничать" и вместо атрибутов всех последующих треков "выплевывает" содержимое своих внутренних буферов вместе с мусором, оставшимся от TOC'а предыдущего диска. Короче говоря, нулевой трек делает лазерный диск практически полностью нечитабемльным.
На этом, собственно, можно было бы и остановиться (кому нужна жутко конфликтная защита, работающая исключительно в лабораторных условиях и крайне нежизнеспособная на практике?!) если бы не одно "НО". Стремительное падение цены на оптические носители позволяет использовать лазерный диск не только для хранения полезной информации, но и в качестве своеобразного ключа.
Весь фокус в том, что наличие нулевого трека на диске никак не препятствует чтению субканальных данных спиральной дорожки, но подавляющее большинство копировщиков (включая Alcohol 120% Алкоголь и Clone CDCloneCD) скорее "зависнут", чем скопируют такой диск! Таким образом, алгоритм работы защитного механизма сводится к "ручному" чтению TOC'а командами SEEK или /READ SUBCHANNEL с последующей проверкой его содержимого на предмет наличия нулевого трека.
И хотя ключевой диск не может содержать никаких других данных кроме собственно самого проверяемого TOC'а —– это ли беда? В каком-то смысле это даже достоинство.
Пусть на одном лазерном диске, никак не защищенном от копирования, содержится демонстрационная версия программы, свободно доступная и для скачивания через Интернет. Чтобы она превратилась в полноценную полнофункциональную версию, пользователь должен вставить в привод ключевой диск, полученный от регионального дилера или переданный непосредственно самим разработчиком по почте (собственно, не обязательно постоянно держать ключевой диск в приводе, защита может запомнить флаг регистрации и в реестре, запрашивая ключевой диск лишь изредка —– на тот случай если пользователь захочет одолжить его кому ни будь). Согласитесь, это гораздо надежнее ключевого файла или регистрационного номера, которым ничего не стоит поделиться с другом или выложить в Интернет, а, учитывая что cубканальные данные диска могут хранить не только ключ, но и исполняемый (интерпретируемый) код, обеспечивающий полнофункциональность зарегистрированной программы, становится ясно, что если у хакера нет ни одной полностью работоспособной копии защищенного приложения, взломать его за разумное время будет просто нереально. Но довольно слов, перейдем к делу, попытавшись перво-наперво создать образ защищенного диска с нулевым треком внутри. Как мы сейчас и увидим, это оказывается это не так-то просто сделать!
Если просто обнулить указатель (point) первого трека, то Clone CDCloneCD наотрез откажется открывать такой образ, ссылаясь на ошибку анализа. На самом деле этих ошибок по меньшей мере две. Первая —– полная неосведомленность Clone CDCloneCD о потенциальной возможности существования нулевых треков, коих он в упор не видит. Вторая —– непростительный оптимизм, закладывающийся на тот факт, что каждая сессия должна содержать в себе по меньшей мере один трек (что в действительности вовсе не факт, а лишь допущение).
Alcohol 120% Алкоголик воспринимает сессию с единственным нулевым треком внутри более благодушно, корректно отображая его номер (см. листинг 6.40 ниже), однако, при попытке записи такого образа на диск, выпрыгивает появляется сообщение об ошибке "access violation" (нарушение доступа) и копировщик "наглухо повисает", даже не удосужившись аварийно завершить свою работу (см.
рис. 6.210x104). Вызов "Диспетчера программ" с последующим " умерщвлением разбушевавшегося" процесса не решает проблемы, т. к. лоток диска остается заблокированным и приходится прибегать к помощи утилиты CD.lock.exe для уменьшения счетчика блокировок на единицу.
Листинг 6.40. Alcohol 120% открывает образ защищенного диска вполне успешно, честно отображая нулевой номер первого трека, правда некорректно определяет его длину
Тип: Файл-образ CloneCD
Путь: L:\CD-hack\
Имя: IMAGE.CCD
IMAGE.img
IMAGE.sub
Размер: 8.81 MB
Сессий: 2
Треков: 2
Сессия 01:
Трек 00: Mode 1, Длина: 000000(0 Byte), Адрес: 000000
Сессия 02:
Трек 01: Mode 1, Длина: 000000(0 Byte), Адрес: 013458
Листинг 32 Алкоголик открывает образ защищенного диска вполне успешно, честно отображая нулевой номер первого трека, правда некорректно определяет его длину.
![](image/disk-s-nulevym-trekom_1.gif)
Рис. 6.21. унок 16 0x104 Реакция Alcohol 120% Алкоголика на попытку записи образа диска с единственным нулевым треком внутри первой сессии
Постойте, но ведь это же… Это же настоящая золотая жила! Диск с единственным нулевым треком внутри первой сессии не то, что не копируется, он даже и не "прожигается"! Даже если хакер каким-то неизвестным науке способом снимет с защищенного диска правильный дамп, ему будет нечем этот дамп записать!!! Правда и разработчику защищаемого приложения ключевой диск нечем записать тоже… ну, разве что отважиться на разработку собственной программы "прожига". Естественно, изготовить штампованные CD с нулевым треков вообще не проблема, но этот путь доступен далеко не всем (индивидуальным программистам он недоступен точно).
Компромиссным вариантомв защиты становится добавление в искажаемую сессию хотя бы единственного ненулевого трека. Такой диск может быть изготовлен с помощью того же Clone CDCloneCD и корпеть над написанием собственной "прожигалки" в этом случае не надо, что есть плюс.
Однако коль скоро для создания оригинального диска используется утилита массового распространения, процесс создания несанкционированных дубликатов значительно упрощается. Хакеру достаточно снять с диска корректный дамп, а все остальное —– это забота копировщика Clone CDCloneCD. Необходимость разрабатывать специализированный софт для взлома при этом отпадает. Один словом: все, что легко защищается, легко и ломается. Впрочем, квалифицированных хакеров не так уж и много и для предотвращения "утекания" своей продукции намс с вами достаточно добиться некопируемости ключевого диска распространенными копировщиками в автоматическом режиме. И, как мы увидим в дальнейшем, защита данного типа полностью удовлетворяет этому требованию.
Процесс подготовки защищенного диска не лишен определенных тонкостей. Создание фиктивного трека с нулевым номером не вызывает особых трудностей, но вот вопрос: где его разместить? В первой, а, может быть, лучше во второй сессии? До подлинного трека или после? Поскольку вводная область первой сессии недоступна для чтения на субканальном уровне, то прочитать TOC первой сессии вручную нельзя. Нельзя и закладываться расчитывать на команду READ TOC, поскольку, как уже было сказано ранеевыше, ее корректное выполнение не гарантируется. Вводные области второй и всех последующих сессий свободно доступны на субканальном уровне и ручное чтение хранимогого ими TOC'а все таки возможно. Конкретная позиция нулевого трека внутри сессии особой роли не играет, и нулевой трек может быть с одинаковым успехом размещен как до ненулевого трека, так и после него.
Только, пожалуйста, не забывайте о необходимости коррекции point'а указателя A0h, хранящего номер "первого" трека всякой сессии. Если его значение оставить без изменений, то образ диска запишется без каких- либо препирательств со стороны Clone CDCloneCD, но никаких упоминаний о нулевом треке в TOC'е "прожженного" диска не окажется! Точно так же ведет себя и Alcohol 120%Алкоголь.
Чтобы этого избежать, значение point' а указателя A0h той сессии, к которой вы добавляете нулевой трек, должно быть сброшено в нольZero.
Фрагмент отредактированного CCD-файла приведен в листинге 6.41.ниже:
Листинг 6.41. Фрагмент CCD-файла с добавленным нулевым треком
TocEntries=13 |
TocEntries=14 |
; корректируем количество входов в TOC |
[Entry 8] |
[Entry 8] |
; это вход не обязательно должен быть восьмым… |
Session=2 |
Session=2 |
; …главное, чтобы Session == 2, а Point == A0h |
Point=0xa0 |
Point=0xa0 |
; этот Point отвечает на номер первого трека |
ADR=0x01 |
ADR=0x01 |
; это служебные поля ADR/Control, описывающие |
Control=0x04 |
Control=0x04 |
; режим обработки трека (это трек с данными) |
TrackNo=0 |
TrackNo=0 |
; TNO = 0 – это Lead-In область |
AMin=0 |
AMin=0 |
; \ |
ASec=0 |
ASec=0 |
; +- условный текущий абсолютный адрес |
AFrame=0 |
AFrame=0 |
; / |
ALBA=-150 |
ALBA=-150 |
; условный текущий LBA-адрес |
Zero=0 |
Zero=0 |
; это поле всегда равно нулю |
PMin=2 à |
PMin=0 |
; корректируем номер "первого" трека |
PSec=0 |
PSec=0 |
; эти поля не имеют никакого смысла и должны |
PFrame=0 |
PFrame=0 |
; быть равны нулю |
PLBA=8850 |
PLBA=8850 |
; LBA-"адрес" номера "первого" трека |
[Entry 11] |
; добавляем еще одно Entry, описывающее нулевой трек |
|
Session=2 |
; нулевой трек должен быть не в первой сессии |
|
Point=0x00 |
; номер трека - ноль |
|
ADR=0x01 |
; Sub-channel Q encodes current position data |
|
Control=0x04 |
; трек с данными |
|
TrackNo=0 |
; это Lead-In |
|
AMin=0 |
; \ |
|
ASec=0 |
; + - условный абсолютный адрес Lead-In |
|
AFrame=0 |
; / |
|
ALBA=-150 |
; условный LBA-адрес Lead-In |
|
Zero=0 |
; это поле должно быть равно нулю |
|
PMin=3 |
; \ |
|
PSec=1 |
; + - абсолютный стартовый адрес нулевого трека |
|
PFrame=66 |
; / |
|
PLBA=13458 |
; LBA-адрес нулевого трека |
TocEntries=13 TocEntries=14 ; корректируем количество входов в TOC
[Entry 8] [Entry 8] ; это вход не обязательно должен быть восьмым…
Session=2 Session=2 ; …главное, чтобы Session == 2, а Point == A0h
Point=0xa0 Point=0xa0 ; этот Point отвечает на номер первого трека
ADR=0x01 ADR=0x01 ; это служебные поля ADR/Control, описывающие
Control=0x04 Control=0x04 ; режим обработки трека (это трек с данными)
TrackNo=0 TrackNo=0 ; TNO = 0 – это Lead-In область
AMin=0 AMin=0 ; \
ASec=0 ASec=0 ; +- условный текущий абсолютный адрес
AFrame=0 AFrame=0 ; /
ALBA=-150 ALBA=-150 ; условный текущий LBA-адрес
Zero=0 Zero=0 ; это поле всегда равно нулю
PMin=2 à PMin=0 ; корректируем номер "первого" трека
PSec=0 PSec=0 ; эти поля не имеют никакого смысла и должны
PFrame=0 PFrame=0 ; быть равны нулю
PLBA=8850 PLBA=8850 ; LBA-"адрес" номера "первого" трека
[Entry 11] ; добавляем еще одно Entry, описывающее нулевой трек
Session=2 ; нулевой трек должен быть не в первой сессии
Point=0x00 ; номер трека - ноль
ADR=0x01 ; Sub-channel Q encodes current position data
Control=0x04 ; трек с данными
TrackNo=0 ; это Lead-In
AMin=0 ; \
ASec=0 ; + - условный абсолютный адрес Lead-In
AFrame=0 ; /
ALBA=-150 ; условный LBA-адрес Lead-In
Zero=0 ; это поле должно быть равно нулю
PMin=3 ; \
PSec=1 ; + - абсолютный стартовый адрес нулевого трека
PFrame=66 ; /
PLBA=13458 ; LBA-адрес нулевого трека
Листинг 33 фрагмент CCD-файла с добавленным нулевым треком
При просмотре геометрии защищенного таким образом диска Ahead Nero выдает приблизительно следующую информацию (см. рис. 6.220x105)[6]. То, что он посчитал вторую сессию открытой ("Session is open") вполне объяснимо, так как созданный нами нулевой трек был ошибочно принят Ahead Nero Нероном за вводную область, в результате чего шаткое равновесие между вводными и выводными областям оказалось нарушенным. Между тем, вторая сессия диска все-таки закрыта и анализ TOC подтверждает это. А не знать, что упоминание о вводных областях никогда в явном виде не присутствует в TOC'е —– глупо. Разобраработаться, почему нулевой трек оказался приобщенретен Ahead Nero Нероном к первой сессии несколько сложнее. По видимому, это грубая алгоритмическая ошибка, которая не делает чести ни самому Ahead NeroНерону, ни его разработчикам.
![](image/disk-s-nulevym-trekom_2.gif)
Рис. 6.22. унок 17 0x105 Наличие нулевого трека на диске срывает крышу путает Ahead NeroНерону, вводя его в глубокое заблуждение относительно состояния последней сессии диска. Ahead Nero Нерон считает, что вторая сессии открыта, хотя на самом деле это не так .
Clone CDCloneCD ведет себя аналогичным образом и при попытке копирования защищенного диска на приводах ASUS и TEAC со всего маху "врезается" в выводную область первой сессии диска. Вторая сессия (с нулевым треком) по причине грубых алгоритмических ошибок полностью выпадает из его поля зрения и в TOC'е скопированного диска о ней даже и не упоминается. Стартовый адрес выводной области первой сессии так же определяется неправильно (Clone CDCloneCD устанавливает его на стартовый адрес нулевого трека второй сессии). Point'ы Указатели B0h (стартовый адрес следующей позиции для дозаписи) и C0h (стартовый адрес первой вводной области диска) то же оказываются потерянными. Короче говоря, TOC скопированного диска более чем существенно отличается от TOC'а оригинального и обнаружить факт несанкционированного копирования не составит никакого труда, да вы их сами сравните (листинг 6.42).
TOC оригинального ключевого диска с нулевым треком внутри второй сессии (слева) при чем атрибуты нулевого трека выделены серой заливкой и TOC его копии, полученной с помощью копировщика CloneCD (справа). Все несовпадения выделены полужирным шрифтом.:
Листинг 6.42. TOC оригинального ключевого диска с нулевым треком внутри второй сессии (слева) и TOC его копии, полученной с помощью CloneCD (справа)
01 14 00 A0 00 00 00 00 01 00 00 |
01 14 00 A0 00 00 00 00 01 00 00 |
01 14 00 A1 00 00 00 00 01 00 00 |
01 14 00 A1 00 00 00 00 01 00 00 |
01 14 00 A2 00 00 00 00 00 1D 21 |
01 14 00 A2 00 00 00 00 03 01 42 |
01 14 00 01 00 00 00 00 00 02 00 |
01 14 00 01 00 00 00 00 00 02 00 |
01 54 00 B0 02 3B 21 03 16 0E 22 |
|
01 54 00 C0 A2 C8 E0 00 61 1B 15 |
|
02 14 00 A0 00 00 00 00 02 00 00 |
|
02 14 00 A1 00 00 00 00 00 00 00 |
|
02 14 00 A2 00 00 00 00 03 18 17 |
|
02 14 00 00 00 00 00 00 03 01 42 |
|
Листинг 34 TOC оригинального ключевого диска с нулевым треком внутри второй сессии (слева, атрибуты нулевого трека выделены серой заливкой) и TOC его копии, полученной с помощью Clone CD (справа). Все несовпадения выделены жирным шрифтом.
При попытке копирования защищенного диска на приводе NEC (который, как уже отмечалось отказывается читать TOC с нулевым треком), копировщик Clone CDCloneCD удивленно спрашивает "диск пуст?" и вне зависимости от нашего ответа, даже и не пытается приступить к его копированию, вызывая страшный хакерский гнев и раздражение.
Alcohol 120% Алкоголь при попытке копирования защищенного диска просто "виснет", едва лишь успев перед смертью выбросить исключение "access violation" (нарушение доступа), но зато заблокировав лоток привода, так что без уменьшения счетчика блокировок извлечь диск помогает разве что тотальная перезагрузка системы.
В общем, дело —– мрак! (С точки зрения "пиратов" конечно).
И мы по праву можем считать, что процедура создания некопируемого ключевого диска завершилась успешно. Теперь, недурно бы разобраться: как с полученным диском вообще работать и каким именно образом защитный механизм сможет отличить копию от оригинала.
Первое, что приходит нам в голову —– прочитать "сырой" TOC командой READ TOC и проверить наличие трека номер ноль, а для пущей надежности – и его атрибуты. Если нулевой трек действительно присутствует в TOC'е и его атрибуты (т. е. поля Session, ADR, Control, PMin:PSec:Pfarme) полностью соответствуют эталону, —– это оригинальный диск и, соответственно, наоборот. Достоинство такого алгоритма в том, что он очень просто реализуется, укладываясь буквально в десяток строк кода, а недостаток —– нестабильность опознавания ключевого диска на определенных моделях приводов. Привод может отказаться читать TOC и к такому повороту событий защита должна быть заранее готова. Давайте сделаем так: если команда READ TOC возвращает ошибку, но диск присутствует в приводе и не препятствует выполнению команды SEEK, то – это оригинальный диск. Конечно, подобное эвристическое допущение значительно ослабляет защиту, однако для большинства применений ее стойкости будет вполне достаточно.
Однако безошибочное выполнение команды READ CD еще не есть свидетельство того, что она выполнена успешно. Ни один известный мне привод, способный читать TOC с нулевым треком, не читает его правильно, а потому защита должна заранее учитывать характер возможных искажений TOC'а и адекватно ему противостоять.
Рассмотрим, например, какой результат возвращает привод TEAC (листинг 6.43). Нулевой трек выделен серой заливкой, а "мусор", следующий за ним — полужирным шрифтом.:
Листинг 6.43. Содержимое TOC ключевого диска, возращенное приводом TEAC
Номер сессии
| ADR/Control
| | TNO
| | | Point
| | | | | AM:AS:AF PM:PS:PF
| | | | | | | | | | |
01 14 00 A0 00 00 00 00 01 00 00 ; point A0 – номер первого трека сессии 1 в PM
01 14 00 A1 00 00 00 00 01 00 00 ; point A1 – номер последнего трека сессии 1 в PM
01 14 00 A2 00 00 00 00 00 1D 21 ; point A2 – адрес Lead-out сессии 1 в PM:PS:PF
01 14 00 01 00 00 00 00 00 02 00 ; point 01 – стартовый адрес трека 1 в PM:PS:PF
01 54 00 B0 02 3B 21 03 16 0E 22 ; point B0 – позиция дозаписи в AM:AS:AF
01 54 00 C0 A2 C8 E0 00 61 1B 15 ; point C0 – стартовый адрес Lead-InLead-In в
; PM:PS:PF /искж
02 14 00 A0 00 00 00 00 02 00 00 ; point A0 - номер первого трека сессии 1 в PM
02 14 00 A1 00 00 00 00 00 00 00 ; point A1 – номер последнего трека сессии 2 в PM
02 14 00 A2 00 00 00 00 03 18 17 ; point A2 – адрес Lead-out сессии 2 в PM:PS:PF
02 14 00 00 00 00 00 00 03 01 42 ; point 00 – стартовый адрес трека 0 в PM:PS:PFpoint 00 – стартовый адрес трека 0 в PM:PS:PF
FB FD 00 FB F4 FB 7A FF FD FD FF ; \ | как видно, встретив нулевой трек, TEAC
FB DF 00 FA FD F5 FF BF FB FE FF ; + - мусор | TEAC вместо осмысленных данных начал изры-
FE F7 00 FB FF FD FB FF FF F7 FF ; / | начал изрыгать мусор
Листинг 35 содержимое TOC'а ключевого диска, возращенное приводом TEAC, нулевой трек выделен серой заливкой, а мусор, следующий за ним – жирным шрифтом
Что это за подозрительный мусор, расположенный следом за нулевым треком? Это —– содержимое внутренних буферов привода, попавшее сюда в результате грубой программистской ошибке в микропрограмме привода (между прочим, тестировалась самая свежая на момент написания этих строк прошивка —– 1.09). Небольшое "расследование" убедительно доказывает, что "мусор" носит не случайный харакер и представляет собой "хвост" TOC'а предыдущего диска.
Давайте, вставив в привод какой ни будь диск (например, "Soul Ballet Hit Collection"), заново сменим его на ключевой и посмотрим что у нас из этого получится (листинг 6.44).:
Листинг 6.44. TOC диска Soul Ballet (слева) и TOC ключевого диска (справа), возвращенные приводом TEAC
01 10 00 A0 00 00 00 00 01 00 00 01 14 00 A0 00 00 00 00 01 00 00
01 10 00 A1 00 00 00 00 10 00 00 01 14 00 A1 00 00 00 00 01 00 00
01 10 00 A2 00 00 00 00 48 1C 05 01 14 00 A2 00 00 00 00 00 1D 21
01 10 00 01 00 00 00 00 00 02 00 01 14 00 01 00 00 00 00 00 03 00
01 10 00 02 00 00 00 00 03 35 40 01 54 00 B0 02 3B 21 03 16 0E 22
01 10 00 03 00 00 00 00 08 14 33 01 54 00 C0 A2 C8 E0 00 61 1B 15
01 10 00 04 00 00 00 00 0C 21 0D 02 14 00 A0 00 00 00 00 02 00 00
01 10 00 05 00 00 00 00 10 3A 2D 02 14 00 A1 00 00 00 00 00 00 00
01 10 00 06 00 00 00 00 16 23 19 02 14 00 A2 00 00 00 00 03 18 17
01 10 00 07 00 00 00 00 1C 1B 0C 02 14 00 00 00 00 00 00 03 01 42
01 10 00 08 00 00 00 00 21 07 49 09 25 00 1F 00 00 00 00 19 01 10
01 10 00 09 00 00 00 00 25 1F 19 0A 2A 00 01 00 00 00 00 06 01 10
01 10 00 0A 00 00 00 00 2A 01 06 0B 2D 00 2D 00 00 00 00 00 01 10
01 10 00 0B 00 00 00 00 2D 2D 00 0C 33 00 29 00 00 00 00 02 01 10
01 10 00 0C 00 00 00 00 33 29 02 0D 39 00 08 00 00 00 00 45 01 10
01 10 00 0D 00 00 00 00 39 08 45 0E 3F 00 1E 00 00 00 00 27 01 10
01 10 00 0E 00 00 00 00 3F 1E 27 0F 43 00 1E 00 00 00 00 29 01 10
01 10 00 0F 00 00 00 00 43 1E 29 10 44 00 03 00 00 00 00 15 FF FF
01 10 00 10 00 00 00 00 44 03 15
Листинг 36 TOC диска Soul Ballet (слева) и TOC ключевого диска (справа), возвращенные приводом TEAC
Смотрите! Сейчас содержимое TOC'а ключевого диска существенно изменилось. Пускай не все содержимое, но вот "хвост" изменился точно. Причем, последовательность байт "хвоста" ключевого диска соотвествует последовательности байт диска "Soul Ballet". Пускай "…09 00 00 00 00 25 1F 19…" и "…09 25 00 1F 00 00 19…" и не совсем тождественные последовательности, но если убрать паразитные нули мы получим: "…09 25 1F 19…" и "…09 25 1F 19…", которые побайтно равны друг другу.
Так что мы действительно имеем дело с ошибкой в прошивке, что не делает чести ни самому приводу, ни его разработчикам.
Привод ASUS ведет себя более корректно (листинг 6.45), просто "обрубая" TOC по нулевому треку, даже в том случае когда нулевой трек —– не последний трек диска, что тоже расценивается как микропрограммная ошибка, пускай и не такая грубая.
Листинг 6.45. TOC ключевого диска, возращенный приводом ASUS
01 14 00 A0 00 00 00 00 01 00 00
01 14 00 A1 00 00 00 00 01 00 00
01 14 00 A2 00 00 00 00 00 1D 21
01 14 00 01 00 00 00 00 00 03 00
01 54 00 B0 02 3B 21 03 16 0E 22
01 54 00 C0 A2 C8 E0 00 61 1B 15
02 14 00 A0 00 00 00 00 02 00 00
02 14 00 A1 00 00 00 00 00 00 00
02 14 00 A2 00 00 00 00 03 18 17
02 14 00 00 00 00 00 00 03 01 42
Привод Листинг 37 TOC ключевого диска, возращенный приводом ASUS
NEC, как уже говорилось ранеевыше, вообще не выдает ничего, кроме ошибки, которою защитный механизм вынужден интерпретировать как индикатор лицензионности диска, в противном случае разработчик может поплатиться своими яйцамисвоей головой, которуюые с удовольствием оторвут легальные пользователи, пытающиеся "скормить" защищенный диск "неправильному" (с точки зрения защиты) приводу.
Тем не менее, преднамеренное ослабление стойкости защиты —– это не выход. Уж лучше попробовать прочитать TOC вручную. Это достаточно трудно реализуется на программном уровне, но еще труднее "ломается"! Если команду READ TOC легко и проэмулировать, то воссоздать особенности обработки субканальных данных практически нереально, благодаря чему усиленный вариант защиты с легкостью обойдет все копировщики, эмулирующие виртуальные диски.
Опасаясь охладить ваш воинственный программистский пыл, я все же скажу: правильно считать субканальные данные не так-то просто, как это может показаться на первый взгляд и одних лишь спецификаций для успешной реализации защитного механизма мало. В них не отображено и доли тех "чудачеств" приводов, с которыми приходится сталкиваться на практике.
q Первое и самое главное: абсолютные адреса секторов ни коим образом не связаны с "соответствующими" им субканальными данными, хотя бы уже потому, что одна секция субканальных данных "размазана" по нескольким секторам, причем в силу определенных конструктивных особенностей, обработка субканальных данных и данных основного потока осуществляется раздельно, благодаря чему при позиционировании головки на сектор N [Y177] [n2k178] X с последующим вызовом команды READ SUBCHANNEL, мы получим субканальные данные не сектора XN, ано сектора MY, лежащего "поблизости" от сектора NX. Понятие "поблизости" всяк производитель определяет самостоятельно, зачастую уползая не на одну сотню секторов вперед.
q Второе: связка команд SEEK и /READ SUBCHANNEL неустойчива
и обладает хреновой плохой воспроизводимостью результатов. Никто не гарантирует, что позиционирование на сектор NX+k приведет к чтению субканальных данных сектора MY+k. Привод может возвратить как данные сектора MY, так и данные сектора MY+i. Так же никто не гарантирует, что повторное позиционирование не сектор NX приведет к чтению субканальных данных сектора MY. (кстати, не забывайте между двумя соседними вызовами командыами SEEK выдерживать паузу хотя бы в 1 сек, иначе головка просто не успеет переместиться на новое место и привод как ни в чем не бывало возвратит уже скэшированные субканальные данные с места предыдущего позиционирования). Остается опираться лишь на текущие адреса секторов, возвращаемые в самой субканальной информации (поле "Absolute CD Address"). Встретив в этом поле адрес "своего" сектора мы может быть абсолютно уверенными в том, что эта субканальная информация принадлежит именно "нашему" сектору, а не какому-то сектору еще.
Листинг 6.46. Правильная интерпретация субканальной информации
LBA - 10D4:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 6D
^^^^^^^^^^ ^ ^^^^^^^^^^^ ^^^^^^^^^^^
| | | |
LBA-адрес сектора, на который | атрибуты point'a LBA-адрес сектора, чьи
осуществлялось позиционирование | чьи субканальные данные
командой SEEK point осуществлялось позиционирование | данные возвратил привод
командой SEEK point
q
q Листинг 38 правильная интерпретация субканальной информации
q Третье: конкретный формат субканальной информации определяется не стандартом, а самим приводом и значительно варьируется от одной модели к другой. Наиболее непостоянны в этом смысле вводные и выводные области диска. Стандарт вообще ничего не говорит о возможности их чтения на субканальном уровне, молчаливо полагая, что это никому на хрен и не нужно. Вот производители и "извращаются" в меру своей распущенности и фантазиий. Поля абсолютных и относительных адресов могут безо всяких предупреждений меняться местами, а сами адреса могут задаваться как в формате M:S:F, так и в LBA-форме. Значения point'ов указателей A0h, A1h и A2h (номер первого трека, номер последнего трека и адрес области Lead-outLead-Out области) могут замещаться значениями 64h, 65h и 66h соответственно. Наконец, нестандартные point'ы указатели (в том числе и нулевые point'ыуказатели) в субканальных данных зачастую попросту отсутствуют —– вместо этого возвращаются данные либо предыдущей, либо последующей секций!
Все это значительно усложняет интерпретацию субканальных данных и поиск в ней нулевых треков, поэтому приходится действовать так: последовательно читая субканальные данные различных секторов диска дожидаемся того момента когда номера треков сменяться сначала на AAh, а затем и на 00h, что будет соответствовать переходу головки с области Lead-outLead-Out области первой сессии на область Lead-inLead-In область второй сессий.
Продолжая читать Lead-inLead-In, мы попытаемся определить в какой закономерности изменяются поля абсолютных и относительных адресов и форму их представления (LBA или M:S:F). Собственно, формат представления определить очень легко. Если младший байт адреса принимает значения больше, чем 75 (4Bh), то – это, несомненно, – LBA и наоборот. Далее —– поскольку поля относительных адресов в вводной области диска используются для хранения атрибутов "своего" point'ауказателя, то они чрезвычайно сильно отличаются от текущих адресов секторов —– тех, на которых и осуществлялось позиционирование. Напротив, поля абсолютных адресов к текущим адресам должны быть достаточно близки.
Остается решить последнюю проблему —– что делать, если в субканальных данных Lead-inLead-In области нулевого трека попросту не окажется? Не спешите делать вывод о нелицензионности диска —– ведь, как уже было сказано ранеевыше, некоторые приводы нестандартные point'ы указатели просто не возвращают. При этом абсолютные адреса секторов, хранящих субканальные атрибуты нулевого трека, в считанном TOC'е не будут присутствовать! Копия диска, полученная любым из существующих на данный момент копировщиков, по данным абсолютным адресам будет содержать атрибуты совершенно других треков, которые привод вполне корректно прочитает и возвратит, либо же вовсе откажется позиционировать головку на эту область, выдавая ту или иную ошибку.
Ну, что, парни, слабо реализовать такое? Для облегчения восприятия материала ниже далее будут приведены субкальные данные ключевого диска, возращенные различными приводами. А подробные комментарии, щедро разбросанные автором, помогут разобраться что к чему.
Листинг 6.47. Резултат чтения субканальной информации из области Lead-In на приводе TEAC; отчетливо просматривается нулевой трек
+internal+ Format
| | | | | ADR/Control
| | | | | | TNO
| | | | | | | Point
| | | | | | | | +- PLBA -+ +- ALBA -+
| | | | | | | | | | | | | | | |
LBA - 10D4: 00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 6D ; LBA 10D4 à
116D
LBA - 10D5:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 6D ; LBA 10D5 à
116D
LBA - 10D6:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 6E
; LBA 10D5 à
116E
LBA - 10D7:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 6D ; LBA 10D7 à
116D (!)
LBA - 10D8:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 6E ; ("биение" головки)
LBA - 10D9:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 6E ; LBA 2292 = 02:00:00
; M:S:F
LBA - 10DA:00 15 00 0C 01 14 00 A1 00 FF FF 6A 00 00 11 73 ; LBA FFh – 6Ah = 95h
; (149)
LBA - 10DB:00 15 00 0C 01 14 00 A1 00 FF FF 6A 00 00 11 73 ; LBA 149 == MSF 0:0:1
LBA - 10DC:00 15 00 0C 01 14 00 A1 00 FF FF 6A 00 00 11 74 ; что удивительно, т.к.
LBA - 10DD:00 15 00 0C 01 14 00 A1 00 FF FF 6A 00 00 11 75 ; последнего трека
; должен
LBA - 10DE:00 15 00 0C 01 14 00 A1 00 FF FF 6A 00 00 11 74 ; быть в PM, но не в PF
LBA - 10DF:00 15 00 0C 01 14 00 A1 00 FF FF 6A 00 00 11 74 ; учитывайте это!
LBA - 10E0:00 15 00 0C 01 14 00 A2 00 00 3B 45 00 00 11 79 ; продолжается биение го-
LBA - 10E1:00 15 00 0C 01 14 00 A2 00 00 3B 45 00 00 11 79 ; головки: сектора идут не
LBA - 10E2:00 15 00 0C 01 14 00 A2 00 00 3B 45 00 00 11 7B ; не упорядочено:
; 1179, 1179,
LBA - 10E3:00 15 00 0C 01 14 00 A2 00 00 3B 45 00 00 11 79 ; 117B, 1179, 117A, 117A и
LBA - 10E4:00 15 00 0C 01 14 00 A2 00 00 3B 45 00 00 11 7A ; и нету секторов 1176, 1177
LBA - 10E5:00 15 00 0C 01 14 00 A2 00 00 3B 45 00 00 11 7A ; 1177 и 178
LBA - 10E6:00 15 00 0C 01 14 00 02 00 00 34 92 00 00 11 80 ;
LBA - 10E7:00 15 00 0C 01 14 00 02 00 00 34 92 00 00 11 80 ;
LBA - 10E8:00 15 00 0C 01 14 00 02 00 00 34 92 00 00 11 80 ; биение головки
LBA - 10E9: 00 15 00 0C 01 14 00 02 00 00 34 92 00 00 11 7F ; продолжается
LBA - 10EA:00 15 00 0C 01 14 00 02 00 00 34 92 00 00 11 80 ;
LBA - 10EB:00 15 00 0C 01 14 00 02 00 00 34 92 00 00 11 81 ;
LBA - 10EC:00 15 00 0C 01 14 00 00 00 00 34 B3 00 00 11 85 ; а вот и нулевой трек, он
LBA - 10ED:00 15 00 0C 01 14 00 00 00 00 34 B3 00 00 11 85 ; он располагается в
; секторах секторах
LBA - 10EE:00 15 00 0C 01 14 00 00 00 00 34 B3 00 00 11 86 ; 1185 и 1186 – запомним
LBA - 10EF:00 15 00 0C 01 14 00 00 00 00 34 B3 00 00 11 85 ; это обстоятельство!
LBA - 10EF:00 15 00 0C 01 14 00 00 00 00 34 B3 00 00 11 85 ; это обстоятельство!
LBA - 10F0:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 8C ; point A0 повторяется…
LBA - 10F1:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 8C ; а вместе с ним и все
LBA - 10F2:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 8B ; остальные point'ы. Читая
LBA - 10F3:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 8B ; Читая субканальные
; данные дальше мы вновь -
LBA - 10F4:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 8C ; ше мы вновь встретим нулевой трек,-
LBA - 10F5:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 8B ; левой трек, но уже в других -
LBA - 10F6:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 8B ; гих секторах. Запомним и
LBA - 10F7:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 8B ; ихз для надежности
Листинг 39 резултат чтения субканальной информации из Lead-In на приводе TEAC; отчетливо просматривается нулевой трек
Здесь абсолютные адреса представлены в LBA-форме, причем расхождение между адресом на который осуществляется позиционирование головки и адресом, чьи субканальные данные при этом читаются (далее по тексту —– дельта) составляет порядка –400 секторов. Правда, равномерность "часового хода" очень хороша, абсолютные идут кучным пучком строго социалистического характера, хотя TEAC все-таки не без греха и оплошности типа 11:6D, 11:6E, 11:6D, 11:6E случаются сплошь и рядом.
Атрибуты нулевого трека присутствуют в явном виде, что не может не радовать.
А вот привод ASUS ведет себя более "разболтано" (листинг 6.48).:
Листинг 6.48. Результат чтения субканальной информации из Lead-In на приводе ASUS
+internal+ Format
| | | | | ADR/Control
| | | | | | TNO
| | | | | | | Point
| | | | | | | | +- PLBA -+ +- ALBA -+
| | | | | | | | | | | | | | | |
LBA - 10D3:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 6F ; здесь субканальные данные
LBA - 10D4:00 15 00 0C 01 14 00 A1 00 FF FF 6A 00 00 11 73 ; данные возвращаются еще более
LBA - 10D5:00 15 00 0C 01 14 00 A1 00 FF FF 6A 00 00 11 73 ; еще более беспорядочно, и потому
LBA - 10D6:00 15 00 0C 01 14 00 A1 00 FF FF 6A 00 00 11 73 ; беспорядочно, и потому
; отделить соседние point'ы
LBA - 10D7:00 15 00 0C 01 14 00 A1 00 FF FF 6A 00 00 11 73 ; point'ы друг от друга уже не
LBA - 10D8:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 6F ; уже не удается, между тем они
LBA - 10D9:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 6F ; тем они лежат по тем же самым
LBA - 10DA:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 6F ; же самым ALBA адресам, что и в
LBA - 10DB:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 6F ; что и в предыдущем случае, а
LBA - 10DC:00 15 00 0C 01 14 00 A1 00 FF FF 6A 00 00 11 73 ; случае, а потому, ALBA адреса могут
LBA - 10DD:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 6F ; адреса могут служить надежной опорой
LBA - 10DE:00 15 00 0C 01 14 00 A1 00 FF FF 6A 00 00 11 73 ; надежной опорой в идентификации point'ов
LBA - 10DF:00 15 00 0C 01 14 00 A1 00 FF FF 6A 00 00 11 73 ; идентификации point'ов не зависящей от насторо-
LBA - 10E0:00 15 00 0C 01 14 00 A2 00 00 3B 45 00 00 11 79 ; не зависящей от ения, разболтанности
LBA - 10E1:00 15 00 0C 01 14 00 A2 00 00 3B 45 00 00 11 79 ; настороения, разбол-и конструктивных особен-
LBA - 10E2: 00 15 00 0C 01 14 00 A2 00 00 3B 45 00 00 11 79 ; танности и конструк-ностей конкретных моделей
LBA - 10E3:00 15 00 0C 01 14 00 A1 00 FF FF 6A 00 00 11 75 ; тивных особенностей приводов, что значительно
LBA - 10E4:00 15 00 0C 01 14 00 A2 00 00 3B 45 00 00 11 7A ; конкретных моделей
; приводов, что значи-
; тельно упрощает процедуру про-
LBA - 10E5:00 15 00 0C 01 14 00 A1 00 FF FF 6A 00 00 11 75 ; процедуру проверки степени лицензион-
LBA - 10E6:00 15 00 0C 01 14 00 02 00 00 34 92 00 00 11 80 ; степени лицензионной
; "чистоты" анализиру-
LBA - 10E7:00 15 00 0C 01 14 00 02 00 00 34 92 00 00 11 81 ; емого диска…
LBA - 10E8:00 15 00 0C 01 14 00 A1 00 FF FF 6A 00 00 11 75
LBA - 10E9:00 15 00 0C 01 14 00 A2 00 00 3B 45 00 00 11 7B
LBA - 10EA:00 15 00 0C 01 14 00 00 00 00 34 B3 00 00 11 85 ; атрибуты трека 0, обрати-
LBA - 10EB:00 15 00 0C 01 14 00 02 00 00 34 92 00 00 11 81
LBA - 10EC:00 15 00 0C 01 14 00 A2 00 00 3B 45 00 00 11 7B
LBA - 10ED:00 15 00 0C 01 14 00 00 00 00 34 B3 00 00 11 85 ; обратите внимание, что
они рас-
LBA - 10EE:00 15 00 0C 01 14 00 02 00 00 34 92 00 00 11 81
LBA - 10EF:00 15 00 0C 01 14 00 00 00 00 34 B3 00 00 11 86 ; они располгаются по тем же LBA
LBA - 10F0:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 8B
LBA - 10F1:00 15 00 0C 01 14 00 00 00 00 34 B3 00 00 11 85 ; тем же LBA адресам:
; 1185h и 1186!
LBA - 10F2:00 15 00 0C 01 14 00 A0 00 00 22 92 00 00 11 8B
Листинг 40 результат чтения субканальной информации из Lead-In на приводе ASUS
Здесь: абсолютные адреса так же представлены в формате LBA и дельта составляет все те же 400 секторов, однако, степень неупорядоченности возвращаемой информации значительно выше и номера секторов начинают потихонечку "плясать" (листинг 6.49) (обратите внимание насм.
поле ALBA).
Листинг 6.49. Результат чтения субканальной информации из Lead-In приводом NEC
+internal+ Format
| | | | | ADR/Control
| | | | | | TNO
| | | | | | | Point
| | | | | | | | +- ALBA -+ +- PLBA -+
| | | | | | | | | | | | | | | |
LBA - 1171:00 15 00 0C 01 14 00 64 00 00 11 6E 00 02 00 00 ; point 64 – это на самом
LBA - 1172:00 15 00 0C 01 14 00 64 00 00 11 6E 00 02 00 00 ; самом деле point A0 (номер пер-
LBA - 1173:00 15 00 0C 01 14 00 64 00 00 11 6E 00 02 00 00 ; (номер первого трека), просто глу-
LBA - 1174:00 15 00 0C 01 14 00 64 00 00 11 6E 00 02 00 00 ; просто глупый привод так нелепо его
LBA - 1175:00 15 00 0C 01 14 00 64 00 00 11 6E 00 02 00 00 ; так нелепо его исказил, так же обратите
LBA - 1176:00 15 00 0C 01 14 00 64 00 00 11 6E 00 02 00 00 ; исказил, так же
; обратите внимание, что
; все адреса идут к все адреса
LBA - 1177:00 15 00 0C 01 14 00 64 00 00 11 6E 00 02 00 00 ; сектору 116E!; идут к сектору 116E!
LBA - 1178:00 15 00 0C 01 14 00 65 00 00 11 74 00 00 00 00 ; резкий переход адресов переход адресов
LBA - 1179:00 15 00 0C 01 14 00 64 00 00 11 6E 00 02 00 00 ; с 116E на 1174 (+6) с 116E на 1174 (+6)
LBA - 117A:00 15 00 0C 01 14 00 65 00 00 11 74 00 00 00 00 ; такова дискретность такова дискретность SEEKа
LBA - 117B:00 15 00 0C 01 14 00 65 00 00 11 74 00 00 00 00 ; SEEKа привода NEC!
привода NEC!
LBA - 117C:00 15 00 0C 01 14 00 65 00 00 11 74 00 00 00 00
LBA - 117D:00 15 00 0C 01 14 00 65 00 00 11 74 00 00 00 00
LBA - 117E:00 15 00 0C 01 14 00 65 00 00 11 74 00 00 00 00
LBA - 117F:00 15 00 0C 01 14 00 65 00 00 11 74 00 00 00 00
LBA - 1180:00 15 00 0C 01 14 00 65 00 00 11 74 00 00 00 00
LBA - 1181:00 15 00 0C 01 14 00 66 00 00 11 7A 00 00 3B 45
LBA - 1182:00 15 00 0C 01 14 00 66 00 00 11 7A 00 00 3B 45
LBA - 1183: 00 15 00 0C 01 14 00 02 00 00 11 7F 00 00 34 92 ; 117F – …
LBA - 1184:00 15 00 0C 01 14 00 66 00 00 11 7A 00 00 3B 45
LBA - 1185:00 15 00 0C 01 14 00 66 00 00 11 7A 00 00 3B 45
LBA - 1186:00 15 00 0C 01 14 00 66 00 00 11 7A 00 00 3B 45
LBA - 1187:00 15 00 0C 01 14 00 02 00 00 11 81 00 00 34 92 ; 117F – 1181 диапазон
LBA - 1188:00 15 00 0C 01 14 00 02 00 00 11 7F 00 00 34 92 ; адресов, занятых субка-
LBA - 1189:00 15 00 0C 01 14 00 02 00 00 11 7F 00 00 34 92 ; субканальной информацией
LBA - 118A:00 15 00 0C 01 14 00 02 00 00 11 81 00 00 34 92 ; информацией
point'a == 2
LBA - 118B:00 15 00 0C 01 14 00 02 00 00 11 80 00 00 34 92 ; point'a == 2
LBA - 118C:00 15 00 0C 01 14 00 02 00 00 11 81 00 00 34 92
LBA - 118D:00 15 00 0C 01 14 00 66 00 00 11 7A 00 00 3B 45
LBA - 118E:00 15 00 0C 01 14 00 64 00 00 11 8B 00 02 00 00 ; смотрите! резкий переходпере-
LBA - 118F:00 15 00 0C 01 14 00 64 00 00 11 8B 00 02 00 00 ; ход с адреса 1181 на адрес
LBA - 1190:00 15 00 0C 01 14 00 64 00 00 11 8B 00 02 00 00 ; адрес 118B – 10 секто-ров пропу-
LBA - 1191:00 15 00 0C 01 14 00 64 00 00 11 8B 00 02 00 00 ; ров пропущено, причем это не прсто
LBA - 1192:00 15 00 0C 01 14 00 64 00 00 11 8B 00 02 00 00 ; это не прсто биение головки – этих
LBA - 1193:00 15 00 0C 01 14 00 64 00 00 11 8D 00 02 00 00 ; головки – этих секто-ров в субканальных
LBA - 1194:00 15 00 0C 01 14 00 64 00 00 11 8D 00 02 00 00 ; ров в субканальных
; дДанных нет вообще! И как
LBA - 1195:00 15 00 0C 01 14 00 64 00 00 11 8B 00 02 00 00 ; И как раз в них и содержатся
LBA - 1196:00 15 00 0C 01 14 00 65 00 00 11 92 00 00 00 00 ; содержатся атрибуты трека ноль, а
LBA - 1197:00 15 00 0C 01 14 00 65 00 00 11 92 00 00 00 00 ; трека 0, а раз так, то то трек ноль все
LBA - 1198:00 15 00 0C 01 14 00 65 00 00 11 92 00 00 00 00 ; трек 0 все-таки есть есть на диске (иначе
LBA - 1199:00 15 00 0C 01 14 00 65 00 00 11 92 00 00 00 00 ; на диске (иначе бы эти
; бы эти сектора возращ.)
Листинг 41 результат чтения субканальной информации из Lead-In приводом NEC
Здесь: дельта "уползания" составляет порядка 10 секторов, а зачастую даже менее того, однако, сама упорядоченность секторов вообще никакая, а нулевых point'ов указателей вообще нет. Сектора с адресами 1185h и 1186h (в которых собственно и храняться атрибуты нулевых треков) "в наглую" отсутствуют —– вместо этого привод спозиционировал головку на адреса 118Bh и 118Dh, в результате чего количество 64h point'ов указателей (в "девичестве" —– A0h) до неприличия возросло. Ко всему прочему абсолютные адреса секторов по непонятной причине перекочевали в поле относительных адресов, и если бы мы попытались проанализировать субканальную информацию согласно стандарту, у нашей защиты точно бы "съехала крыша".
Итак, несмотря громоздкость и трудности реализации защитного механизма, он все-таки может быть реализован так, чтобы уверенно отличать ключевой диск на любой из моделей приводов. Но стоит ли овчинка выделки или, говоря другими словами, каким образом защищенный диск можно взломать?
А вот со взломом у нас туго. Да, в принципе такой диск можно скопировать, но только не с помощью Сlone CD или Alcohol 120% Алкоголем и не на всех моделях приводах. Для взлома пригодны лишь те приводы, что уверенно читают TOC и возвращают атрибуты нестандартных point'овуказателей, поскольку ко всему этому может привязываться защита. Постойте! —– воскликните вы, —– но ведь защита не должна опиратьсязакладываться на доступность TOC'а и атрибуты нулевого point'ауказателя, иначе программа окажется неработоспособна на некоторых моделях приводов! Это верно, однако, если привод соглашается читать TOC — – отчего же его не проверить?
Если привод взломщика не позволяет читать содержимое TOC, взломщик не сможет восстановить оригинальный TOC (ну разве что отдизассемблирует весь защитный механизм целиком) и потому скопированный диск будет работать лишь на его приводе!
Правда при наличии привода, читающего TOC и "хорошо смазанных подшипников в котелке", копирование защищенного диска осуществляется очень просто. Достаточно лишь считав TOC (команда READ TOC), считать и само содержимое диска на субканальном уровне (команды SEEK или /READ SUBCHANNEL), а так же содержимое основного канала (команда READ CD), после чего остается лишь сформировать CCD-, -IMG- и SUB-файлы и с помощью того же копировщика Clone CDCloneCD записать их на диск. Однако, на такой взлом "по зубам" далеко не всякому хакеру, а с натиском желторотых пользователей эта защита без труда справится.
Доктор Ватсон
"Доктор Ватсон" является штатным обработчиком критических ошибок, входящим в базовый пакет поставки всех операционных систем семейства Windows. По своей природе он представляет собой статическое средство сбора релевантной информации. Предоставляя исчерпывающий отчет о причинах сбоя, "Доктор Ватсон" в тоже самое время лишен активных средств воздействия на некорректно работающее программы. Утихомирить разбушевавшееся приложение, заставив его продолжить свою работу с помощью одного "Доктора Ватсона", вы не сможете и для этого вам придется прибегать к интерактивным отладчикам, одним из которых является MicrosoftVisual Studio Debugger, входящий в состав одноименной среды разработки и рассматриваемый несколькими страницами далеениже.
Считается, что "Доктор Ватсон" предпочтительнее использовать на рабочих станциях (точнее –— на автоматизированных рабочих местах), а интерактивные средства отладки –— на серверах. Дескать, во всех премудростях ассемблера пользователи все равно не разбираются, а вот на сервере "продвинутый" отладчик будет как нельзя кстати. Отчасти это действительно так, но не стоит игнорировать то обстоятельство, что далеко не все источники ошибок обнаруживаются статическими средствами анализа, к тому же интерактивные инструменты значительно упрощают процедуру анализа. С другой стороны, "Доктор Ватсон" достается нам даром, а все остальные программные пакеты приходится приобретать за дополнительную плату. Так что предпочтительный обработчик критических ошибок вы должны выбирать сами.
Для установки "Доктора Ватсона" отладчиком по умолчанию добавьте в реестр следующую запись (листинг 3.1) или запустите файл Drwtsn32.exe c ключом "–i" (для выполнения обоих действий вы должны иметь права администратора).:
Листинг 3.1. Установка "Доктора Ватсона" отладчиком по умолчанию
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug]
"Auto"="1"
"Debugger"="drwtsn32 -p %ld -e %ld -g"
"UserDebuggerHotKey"=dword:00000000
Листинг 1 установка "Доктора Ватсона" отладчиком по умолчанию
Теперь возникновение критических ошибок программы станет сопровождаться генерацией отчета, составляемого "Доктором Ватсоном" и содержащим более или менее подробные сведения о характере ее происхождения (рис. 3.3).
![](image/doktor-vatson_1.gif)
Рис.унок 3.4 3. drw.gif Реакция "Доктора Ватсона" на критическую ошибку
Образец дампа (dump), созданный "Доктором Ватсоном", приведен в листинге 3.2ниже. Комментарии, добавленные автором, выделены серым цветом.:
Листинг 3.2. Образец отчета "Доктора Ватсона" с комментариями автора
Исключение в приложении:
Прил.: (pid=612)
; pid процесса, в котором произошло исключение
Время: 14.11.2003 @ 22:51:40.674
; время, когда произошло исключение
Номер: c0000005 (нарушение прав доступа)
; код категории исключения
; расшифровку кодов исключений можно найти в WINNT.H
; входящим в состав SDK, прилагаемом к любому Windows-компилятору
; подробное описание всех исключений содержится в документации
; по процессорам Intel и AMD, бесплатно распространяемой их производителями
; (внимание: для перевода кода исключения операционной системы в
; вектор прерывания ЦП, вы должны обнулить старшее слово)
; в данном случае это 0x5 – попытка доступа к памяти по запрещенному адресу
*----> Сведения о системе <----*
Имя компьютера: KPNC
Имя пользователя: Kris Kaspersky
Число процессоров: 1
Тип процессора: x86 Family 6 Model 8 Stepping 6
Версия Windows 2000: 5.0
Текущая сборка: 2195
Пакет обновления: None
Текущий тип: Uniprocessor Free
Зарегистрированная организация:
Зарегистрированный пользователь: Kris Kaspersky
; краткие сведения о системе
*----> Список задач <----*
0 Idle.exe
8 System.exe
232 smss.exe
…
1244 os2srv.exe
1164 os2ss.exe
1284 windbg.exe
1180 MSDEV.exe
1312 cmd.exe
612 test.exe
1404 drwtsn32.exe
0 _Total.exe
(00400000 - 00406000)
(77F80000 - 77FFA000)
(77E80000 - 77F37000)
; перечень загруженных DLL
; согласно документации, справа от адресов должны быть перечислены имена
; соответствующих модулей, однако практически все они так хорошо "замаскировались",
; что стали совершенно не видны. вытащить их имена из файла протокола все-таки можно,
; но придется немного пошаманить (см. ниже "таблицу символов")
Копия памяти для потока 0x188
; ниже идет копия памяти потока, вызывавшего исключение
eax=00000064 ebx=7ffdf000 ecx=00000000 edx=00000064 esi=00000000 edi=00000000
eip=00401014 esp=0012ff70 ebp=0012ffc0 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000202
; содержимое регистров и флагов
функция: <nosymbols>
; распечатка окрестной точки cбоя
00400ffc 0000 add [eax],al ds:00000064=??
; записываем в ячейку на которую ссылает EAX значение AL
; значение адреса ячейки, вычисленной Доктором Ватсоном, равно 64h
; что, очевидно, не соответствует действительности;
; Доктор Ватсон подставляет в выражение значение регистра EAX
; на момент возникновения сбоя, и это совсем не то значение, которое
; было в момент исполнения! к сожалению, чему был равен EAX в момент
; исполнения ни нам, ни Доктору Ватсону не известен.
00400ffe 0000 add [eax],al ds:00000064=??
; записываем в ячейку, на которую ссылает EAX значение AL
; как? опять? что это за бред?! вообще-то так кодируется
; последовательность 00 00 00 00, по всей видимости являющаяся
; осколком некоторой машинной команды, неправильно интерпретированной
; дизассемблерным движком Доктора Ватсона;
00401000 8b542408 mov edx,[esp+0x8] ss:00f8d547=????????
; загружаем в EDX аргумент функции
; какой именно аргумент – сказать невозможно, т.к. мы не знаем адрес
; стекового фрейма;
00401004 33c9 xor ecx,ecx
; обнуляем ECX
00401006 85d2 test edx,edx
00401008 7e18 jle 00409b22
; если EDX == 0, прыгаем на адрес 409B22h
0040100a 8b442408 mov eax,[esp+0x8] ss:00f8d547=????????
; загружаем уже упомянутый аргумент в регистр EAX
0040100e 56 push esi
; сохраняем ESI в стеке, перемещая тем самым указатель вершины стека
; на 4 байта вверх (в область младших адресов)
0040100f 8b742408 mov esi, [esp+0x8] ss:00f8d547=????????
; загружаем в ESI очередной аргумент
; поскольку ESP был только что изменен, это совсем не тот аргумент,
; с которым мы имели дело ранее
00401013 57 push edi
; сохраняем регистр EDI в стеке
СБОЙ -> 00401014 0fbe3c31 movsx edi,byte ptr [ecx+esi] ds:00000000=??
; вот мы и добрались до инструкции, возбудившей исключение доступа
; она обращается к ячейке памяти, на которую указывает сумма
; регистров ECX и ESI
; а чему равно их значение? прокручиваем экран немного вверх и находим, что
; что ECX и ESI равны 0, о чем Доктор Ватсон нам и сообщает: "ds:000000"
; отметим, что этой информации можно верить, поскольку подстановка
; эффективного адреса осуществлялась непосредственно в момент исполнения
; теперь вспомним, что ESI содержит копию переданного функции аргумента
; и что ECX был обнулен явно, следовательно в выражении [ECX+ESI]
; регистр ESI – указатель, а ECX – индекс.
; раз ESI равен нулю, то нашей функции передали указатель на невыделенную
; область памяти. обычно это происходит либо вследствие алгоритмической
; ошибки в программе, либо вследствие исчерпания виртуальной памяти
; к сожалению, Доктор Ватсон не осуществляет дизассемблирование
; материнской функции, и какой из двух предполагаемых вариантов правильный
; нам остается лишь гадать… правда, можно дизассемблировать дамп памяти
; процесса (если, конечно, он был сохранен), но это уже не то…
00401018 03c7 add eax, edi
; сложить содержимое регистра EAX с регистром EDI и записать результат в EAX
0040101a 41 inc ecx
; увеличить ECX на единицу
0040101b 3bca cmp ecx,edx
0040101d 7cf5 jl 00407014
; до тех пор пока ECX < EDX, прыгать на адрес 407014
; (очевидно, мы имеем дело с циклом, управляемым счетчиком ECX)
; при интерактивной отладке мы могли бы принудительно выйти
; из функции, возвратив флаг ошибки, чтобы материнская функция
; (а с ней и вся программа целиком) могла продолжить свое выполнение
; и в этом случае потерянной окажется лишь последняя операция, но все
; остальные данные окажутся неискаженными;
0040101f 5f pop edi
00401020 5e pop esi
00401021 c3 ret
; выходим из функции
*----> Обратная трассировка стека <----*
; содержимое стека на момент возникновения сбоя
; распечатывает адреса и параметры предыдущих выполняемых функций,
; при интерактивной отладке мы могли бы просто передать управление
; на одну из вышележащих функций, что эквивалентно возращению в прошлое
; это только в реальной жизни разбитую чашку восстановить нельзя,
; в компьютерной вселенной возможно все!
FramePtr ReturnAd Param#1 Param#2 Param#3 Param#4 Function Name
; FramePtr: указывает на значение фрейма стека,
; выше (т.е. в более младших адресах) содержатся аргументы функции
; ниже – ее локальные переменные
;
; ReturnAd: бережно хранит адрес возврата в материнскую функцию
; если здесь содержится мусор и обратная трассировка стека[Y87]
; начинает характерно шуметь, с высокой степенью вероятности
; можно предположить, что мы имеем дело с ошибкой "срыва стека"
; а возможно, и с попыткой атаки вашего компьютера
;
; Param#: четыре первых параметра функции – именно столько параметров
; Доктор Ватсон отображает на экране; это достаточно жесткое
; ограничение – многие функции имеют десятки параметров и
; четыре параметра еще ни о чем не говорят; однако недостающие
; параметры легко вытащить из копии необработанного стека вручную
; достаточно лишь перейти по указанному в поле FramePtr адресу
;
; Func Name: имя функции (если только его возможно определить); реально
; отображает лишь имена функций, импортируемые из других DLL,
; поскольку встретить коммерческую программу, откомпилированную
; вместе с отладочной информацией практически нереально
;
0012FFC0 77E87903 00000000 00000000 7FFDF000 C0000005 !<nosymbols>
0012FFF0 00000000 00401040 00000000 000000C8 00000100 kernel32!SetUnhandledExceptionFilter
; функции перечисляются в порядке их исполнения; самой последней исполнялась
; kernel32!SetUnhandledExceptionFilter функция, обрабатывающая данное исключение
*----> Копия необработанного стека <----*
; копия необработанного стека содержит стек таким, какой он есть
; очень помогает при обнаружении buffer overfull атак – весь shell-код,
; переданный злоумышленником, будет распечатан Доктором Ватсоном, и вам
; останется всего лишь опознать его (подробнее об этом рассказывается
; в моей книге "Техника сетевых атак")
0012ff70 00 00 00 00 00 00 00 00 - 39 10 40 00 00 00 00 00 ........9.@.....
0012ff80 64 00 00 00 f4 10 40 00 - 01 00 00 00 d0 0e 30 00 d.....@.......0.
…
00130090 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
001300a0 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
*----> Таблица символов <----*
; таблица символов содержит имена всех загруженных DLL вместе с именами
; импортируемых функций. используя эти адреса в качестве отправной точки,
; мы без труда сможем восстановить «перечень загруженных DLL»
ntdll.dll
77F81106 00000000 ZwAccessCheckByType
…
77FCEFB0 00000000 fltused
kernel32.dll
77E81765 0000003d IsDebuggerPresent
…
77EDBF7A 00000000 VerSetConditionMask
;
; итак, возвращаемся к таблице загруженных DLL
; (00400000 - 00406000) - это, очевидно, область памяти, занятая самой программой
; (77F80000 - 77FFA000) – это KERNEL32.DLL
; (77E80000 - 77F37000) - это NTDDL.DLL
Доступ через MSCDEX драйвер
Знаменитый MSCDEX, созданный еще во времена царствования MS-DOS, несмотря на свои многочисленныех недостатки, все-таки обеспечивал программистов всем необходимым им функционалом и достаточно полно поддерживал возможности существующих в то время приводов. Так, например, чтение отдельных секторов осуществлялось функцией 1508h прерывания INT2Fh, а если возникала необходимость спуститься на "сырой" уровень, мы всегда могли "попросить" драйвер MSCDEX передать приводу ATAPI-пакет напрямую, чем занималась функция 1510h того же прерывания (загляните в список прерываний (Interrupt List[n2k143] ), если нуждаетесь в более подробной информации).
Забавно, но возможности штатного драйвера "новейшей" и "могучей" Windows 9x, не в пример беднее и спуститься на секторный уровень, при этом, не набиве разорвав себе задницу[n2k144] , под ее управлением, по-видимому, нельзя. Судя по всему, архитекторы системы сочли секторный обмен ненужным и к тому же системно-зависимым, а "правильные" приложения должны разрабатываться как полностью переносимые и довольствующиеся исключительно стандартными вызовами интерфейса win32 API. Все остальное от лукавого!
Между тем, для сохранения обратной совместимости с программами, написанными для MS-DOS и Windows 3.1, операционная система Windows 95 поддерживает MSCDEX-интерфейс, причем по соображениям производительности, реализует его не в "настоящем" MSCDEX, который и вовсе может отсутствовать на диске, а в драйвере CD-ROM драйвере, исполняющемся в 32-разрядном защищенном режиме. Выходит, что весь необходимый нам функционал в системе все-таки есть, а значит, есть и надежда как-то до него добраться. Естественно, с уровня ядра эта задача решается без проблем, но… писать свой собственный драйвер только для того, чтобы "пробить интерфейсную шахту" к уже существующему драйверу, –— это маразм какой-то!
К счастью, готовый (и даже задокументированный!) интерфейс между win32-приложениями и MSCDEX-драйвером в системе Windows 9x действительно есть.
К несчастью, он реализован через ж…опу (и … именно через ж…)опу и не надо пытаться вычеркнуть эту фразу, иначе я шибко разозлюсь (последняя фраза предназначается в первую очередь для редакторов). В общих чертах схема "прокладывания туннеля" к драйверу MSCDEX'у выглядит приблизительно так: создав 16-разрядную динамически подключаемую библиотеку (DLL), мы получаем возможность взаимодействовать с интерфейсом DPMI через функции прерывания INT 31h.
Замечание
DPMI (DOS Protected Mode Interface) –— интерфейс, спроектированный специально для того, чтобы разработчики приложений защищенного режима, исполняющихся в среде MS-DOS, могли пользоваться функциями 16-разрядной операционной системы реального режима, коей MS-DOS и является.
Конкретно нас будет интересовать функция 1508h, –— DPMI Simulate Real Mode Interrupt, позволяющая вызывать прерывания реального режима из защищенного. Обращаясь к эмулятору MSCDEX-драйвера через родное для него прерывание INT 2Fh, мы можем делать с приводом практически все, что нам только вздумается, поскольку интерфейс драйвера MSCDEX'a,'а как уже отмечалось ранее, могуч и велик.
Таким образом, вырисовывается следующий программистский маршрут: win32 приложение à 16-разрядная DLL à DMPI Simulate RM Interrupt à MSCDEX à CDFS. Не слишком ли наворочено, а? Уж лучше воспользоваться интерфейсом ASPI (благо в Windows 95 оно[n2k145] присутствуетесть) или засесть за написание собственного драйвера. Тем не менее, даже если вы не собираетесь управлять приводом посредством драйверачерез MSCDEX, знать о существовании такого способа взаимодействия с оборудованием все-таки небесполезно, особенно, если вы планируете заняться взломом чужих программ. В этом случае точки останова, установленные на API- функции, ничего не дадут, поскольку чтение секторов осуществляется через прерывания INT 31h (DMPI) и INT 2Fh.
К сожалению, прямая установка точек останова на последние дает очень много ложных срабатываний, а применение фильтров вряд ли окажется эффективным, поскольку количество возможных вариаций слишком велико. Уж лучше поискать вызовы прерываний в дизассемблерном тексте программы!
Дополнительную информацию по этому вопросу можно найти в технической заметке Q137813, входящей в состав MSDN, распространяемой вместе с Microsoft Visual Studio и озаглавленную как "How Win32 Applications Can Read CD-ROM Sectors in Windows 95". Полный перечень DMPI- и MSCDEX-функций содержится в списке Interrupt-List'e'е Ральфа Брауна[Y146] ,[n2k147] так что никаких проблем с использованием данного приема у вас возникнуть не должно (правда, раздобыть компилятор, способный генерировать 16-разрядный код и "линкер" (linker, иначе компановщик) под Windows 3.1 сегодня не так-то просто! К слову сказать, Microsoft Visual Studio 6.0 для этой цели уже не подходит, ибо, начиная с некоторой версии –— уже сейчас и не вспомню какой –— он утратил возможность создания проектов под операционные системы MS-DOS/Windows 3.1).
ДалееНиже приводится ключевой фрагмент программы (листинг 1.4.29), позаимствованный из MSDN, и демонстрирующий технику вызова прерываний реального режима из 16-разрядных динамически подключаемых библиотек (DLL), исполняющихся в среде Windows.
Листинг 2.1.4.3029. Ключевой фрагмент программы, демонстрирующей технику взаимодействия с драйвером MSCDEX из 16-разрядного защищенного режима
BOOL FAR PASCAL MSCDEX_ReadSector(BYTE bDrive, DWORD StartSector, LPBYTE RMlpBuffer)
{
RMCS callStruct;
BOOL fResult;
// Prepare DPMI Simulate Real Mode Interrupt call structure with
// the register values used to make the MSCDEX Absolute read call.
// Then, call MSCDEX using DPMI and check for errors in both the DPMI
// call and the MSCDEX call
BuildRMCS (&callStruct);
callStruct.eax = 0x1508; // MSCDEX функция "ABSOLUTE READ"
callStruct.ebx = LOWORD(RMlpBuffer); // смещение буфера для чтения сектора
callStruct.es = HIWORD(RMlpBuffer); // сегмент буфера для чтения сектора
callStruct.ecx = bDrive; // буква привода 0=A, 1=B, 2=C и т.д.
callStruct.edx = 1; // читаем один сектор
callStruct.esi = HIWORD(StartSector); // номер читаемого сектора(старшее слово)
callStruct.edi = LOWORD(StartSector); // номер читаемого сектора(младшее слово)
// вызываем прерывание реального режима
if (fResult = SimulateRM_Int (0x2F, &callStruct))
fResult = !(callStruct.wFlags & CARRY_FLAG);
return fResult;
}
BOOL FAR PASCAL SimulateRM_Int(BYTE bIntNum, LPRMCS lpCallStruct)
{
BOOL fRetVal = FALSE; // Assume failure
__asm
{
push di ; сохраняем регистр DI
mov ax, 0300h ; DPMI Simulate Real Mode Interrupt
mov bl, bIntNum ; номер прерывания реального режима для вызова
mov bh, 01h ; бит 0 = 1; все остальные должны быть равны нулю
xor cx, cx ; ничего не копируем из стека PM в стек RM
les di, lpCallStruct ; указатель на структуру со значением регистров
int 31h ; шлюз к DMPI
jc END1 ; если ошибка, – прыгаем на END1
mov fRetVal, TRUE ; все ОК
END1:
pop di ; восстанавливаем регистр DI
}
// возвращаемся
return (fRetVal);
}
Доступ посредствомчерез ASPI
Отлаженная программа–— это такая программа, для которой еще не найдены условия, в которых она откажет.
Программистский фольклор
Вот два основных недостатка интерфейса SPTI (только что описанного ранеевыше): для взаимодействия с устройством он требует наличия прав администратора и, что еще хуже, интерфейс SPTI поддерживается только операционными системами семейства NT и отсутствует вна Windows 9x/ME. Единственный легальный способ "дотянуться" до привода CD-ROM'а под Windows 9x –— это воспользоваться 16-разрядным шлюзом, напрямую обращаясь к MS-DOS драйверу MSCDEX, который обеспечивает значительно большую функциональность, нежели Windows-драйвер. Естественно, параллельная поддержка двух семейств операционных систем требует от программиста значительных усилий, что существенно повышает себестоимость программного продукта.
Для упрощения разработки кросс-платформенных приложений фирма Adaptec разработала специальный системно-независимый интерфейс, позволяющий управлять различными SCSI-устройствами с прикладного уровня, и назвала его ASPI –— Advanced SCSI Programming Interface (хотя неофициально его расшифровывают как Adaptec SCSI Programming Interface, поскольку это больше соответствует истине).
Системонезависимость интерфейса ASPI обеспечивается двухуровневой моделью его организации: архитектурно он состоит из низкоуровневого драйвера и прикладной библиотеки-обертки. ASPI-драйвер разрабатывается с учетом специфики конкретной операционной системы и отвечает за непосредственноей управление SCSI-шиной (реальной или виртуальной –— не суть важно). Поскольку интерфейс между операционной системой и драйвероамдрайвером меняется от одной операционной системы к другой, то для сокрытия всех этих различий используется специальная ASPI-библиотека, предоставляющая единый унифицированный интерфейс для всех операционных систем.
Рассмотрим, как осуществляется внедрение ASPI-интерфейса в операционную систему на примере Windows MEe (см. рис. 1.4.20х34).
На самом высоком уровне иерархии находятся прикладные библиотеки Wnaspi32.dll и Winaspi.dll для 32- и 16-разрядных приложений соответственно. Они экспортируют три базовых ASPI-функции: GetASPI32DLLVersion, GetASPI32SupportInfo и SendASPI32Command (причем последняя –— самая важная) и три вспомогательных: GetASPI32Buffer, FreeASPI32Buffer, TranslateASPI32Address (последняя –— только в 32-разрядной версии библиотеки).
Посредством функции DeviceIoControl они взаимодействуют с ASPI-драйвером, расположенным "ниже" и, в зависимости от версии операционной системы, называющимся либо APIX.VXD (Windows 9x), либо ASPI.SYS (Windows NT) и создающим в процессе своей инициализации устройство с непроизносимым названием MbMmDp32. Только не спрашивайте меня, как это абракадабра расшифровывается –— ответ похоронен в застенках компании Adaptec.
Замечание
В 16-разрядных приложениях взаимодействие с драйвером осуществляется через с помощьючерез функциию 1868h прерывания 2Fh., Подробности этого процесса можно узнатьузнать, дизассемблируя Winaspi.dll. Она, кстати, совсем крошечная –— всего 5 Ккилобайт.
В принципе, ничего не мешает взаимодействовать с ASPI-драйвером и напрямую –— в обход библиотеки Wnaspi32.dll. Собственно, многие разработчики защитных механизмов именно так и поступают. Достаточно лишь дизассемблировать Wnaspi32.dll и разобраться каким ASPI-командамASPI-командам, какие IOCTL-коды соответствуют (ASPI-протокол по понятным соображениям не документирован). Действительно, на функции SendASPI32Command очень легко поставить бряк[Y105] [n2k106] , и тогда хакер (hacker) мгновенно локализует защитный код. С вызовами же функции DeviceIoControl в силу их многочисленности взломщикам справиться намного труднее. К тому же начинающие ломатели защит (а таких среди хакеров –— большинство) весьма смутно представляют себе архитектуру ввода-вывода и уж тем более не разбираются в ASPI-протоколе. Впрочем, для опытных хакеров такая защита —– не преграда (подробнее см.
главу 52 "Способы разоблачения защитных механизмов").
Сам же ASPI-драйвер "подключен" к SCSI- и IDE/ATAPI- портам (рис. 1.4.4), за счет чего он позволяет управлять всеми этими устройствами (и приводами CD-ROM в том числе).
Рис. 2.1.4.54. 0x099 Архитектура подсистемы ввода-вывода Windows 98.
Клиентские модули (на данной схеме они обозначены цифрами 1, 2 и 3) посылают свои запросы[n2k107] драйверу файловой системы –— Instable File System (обозначенному цифрой 6). В распоряжении клиентских модулей также имеются ASPI-библиотеки ASPI для 32- и 16-разрядных приложений соответственно (они обозначены цифрами 4 и 6). От всей системы они стоят особняком, поскольку разработаны независимой компанией Adaptec и представляют собой факультативные компоненты. Драйвер файловой системы перенаправляет полученный им запрос на один их следующих специализированных драйверов, среди которых присутствует и драйвер привода CD-ROM, –— CDFS.VxD, обозначенный цифрой 8. В его задачи входит поддержка файловых систем лазерных дисков, таких как-то: ISO- 9660, High Sierra или другихе файловыхе системы. Уровнем ниже лежит Volume Tracker (цифра 14), отслеживающий смену диска в накопителе, а еще ниже находится непосредственно сам драйвер, поддерживающий данную модель CD-ROM, –— так называемый CD type specific driver, реализуемый драйвером CDVSD.VxD и среди прочих обязанностей отвечающий за назначение буквы приводу. Это и есть секторный уровень взаимодействия с диском, никаких файловых систем здесь нет и в помине. Несмотря на то, что данный драйвер специфичен для конкретной модели привода CD-ROM, он совершенно независим от его физического интерфейса, поскольку опирается на CD-ROM device SCSI'zer (цифра 21), преобразующий IOP-запросы, поступающие от вышележащих драйверов, в SRB-пакеты, направляемые нижележащим драйверам (подробнее об этом см. раздел "Доступ посредствомчерез SCSI-порта" данной главы).
Еще ниже находится SCSI CD-ROM helper (цифра 23), обеспечивающий стыковку SCSI'zer'a-а с SCSI-портом. Сам же SCSI-port, создаваемый менеджером SCSI-портов ( цифра 26) представляет собой унифицированное системно-независимое средство взаимодействия драйверов среднего уровня с физическим (или виртуальным) оборудованием. К одному из таких SCSI-портов и подключается ASPI-драйвер (цифра 18), реализованный в файле APIX.VxD и восходящий к своим "оберткам" –— Wnaspi32.dll и Wnaspi.dll (цифры 11 и 12 соответственно). Ниже SCSI-менеджера расположены драйверадрайвераы мини-портов, переводящие SCSI-запросы в язык конкретной интерфейсной шины. В частности, драйвер, обеспечивающий поддержку IDE-устройств, реализован в файле ESDI_506.PDR (цифра 29). Естественно, при желании мы можем общаться с IDE-устройствами и через IDE/ATAPI-порты (цифра 25), реализованные все тем же драйвером ESDI_506.PDR (ASPI-драйвер по соображениям производительности именно так, собственно, и поступает). Левую часть блок-схемы, изображающую иерархию драйверов прочих дисковых устройств мы не рассматриваем, т. к. она не имеет никакого отношения к теме нашего обсуждения.
Для программирования под ASPI требуются как минимум две вещи: ASPI-драйвер и ASPI-SDK. Драйвер можно бесплатно скачать с сервера самой Adaptec (ею разработаны драйверадрайвераы для следующих операционных системы: MS-DOS, Novell, Windows 9x, Windows NT/W2K/XP), а вот SDK [Y108] [n2k109] с некоторого момента распространяется за деньги. И хотя его стоимость чисто символическая (что-то около 10$, если мне не изменяет память), неразвитость платежных систем в России превращает процесс покупки в довольно затруднительное дело. Однако все необходимое для работы (документация, заголовочные файлы, библиотеки) можно позаимствовать из… Windows MEe DDK [n2k110] (кстати, входящего в состав DDK для Windows 2000). Так что, если у вас уже есть Windows 20002K DDK, вам не о чем беспокоиться.
В противном случае попробуйте обратиться к MSDN[Y111] [n2k112] , распространяемой вместе с Microsoft Visual Studio 6.0. Здесь вы найдете документацию и заголовочные файлы, ну а недостающие библиотеки из соответствующих DLL (Dynamic Link Library)[n2k113] можно получить и самостоятельно (lib.exe с ключом /DEF), либо же вовсе обойтись без них, загружая все необходимые функции через LoadLibrary[Y114] /GetProcAddress.
Поскольку ASPI-интерфейс хорошо документирован (руководство по программированию насчитывает порядка 35 листов), то его освоение не должно вызвать никаких непреодолимых проблем (во всяком случае, после знакомства с SPTI). К тому же, в Windows MEe DDK входит один законченный демонстрационный пример использования ASPI, найти который можно в папке "\src\win_me\block\wnaspi32\". Несмотря на досадный суффикс "Me", он отлично уживается и с другими операционными системами, как-то: Windows 98, Windows 2000, Windows XP и т. д.
Впрочем, реализован этот пример на редкость "криво" и с большим количеством ошибок, а его наглядность такова, что менее наглядного примера для демонстрации ASPI пожалуй и не подобрать! Уж лучше исследовать исходные тексты программы CD slow, которые можно легко найти в Интернете (однако она написана на ассемблере, а с ассемблером знаком не всякий).
Кратко перечислим основные недочеты демонстрационного примера aspi32ln.c:
q Вво-первых, это не консольная программа, а относящаяся кно [n2k115] GUI'вая (Graphical User Interface) — графическому интерфейсу пользователя'ая,[n2k116] а потому большая часть ее кода к интерфейсу ASPI вообще никакого отношения не имеет.
q Во-вторых, используется единая функция для получения уведомлений сразу от выполнения двух команд: SCSI_INQUIRY и SCSI_READ10, причем последняя в половине случаев заменена своей константой 0x28, что тоже не способствует ее пониманию.
q В-третьих, накопители на CD- ROM программой поддерживаются лишь частично. Плохо спроектированная архитектура программы не позволила разработчикам осилить поставленную перед ними задачу. Поэтому ветка, отвечающая за чтение с приводов CD-ROM в функции ASPI32Post, специальным образом закомментирована. Если же наложенную блокировку убрать, то при чтении будет происходить ошибка, поскольку программа ориентирована лишь на те накопители, чей размер сектора составляет 0x200 (512) байт. Приводы CD-ROM дисков, чей сектор вчетверо больше, очевидно, к этой категории не относятся и, чтобы не переписывать всю программу целиком, единственное, что можно сделать –— это увеличить размер запрашиваемого блока данных до 0х800 (2048) байт (с жестких дисков будет считываться по четыре сектора за раз, что вполне допустимо).
q Наконец, В-пятыхВ четвертых[Y117] , инкремент (т. е. вычисление адреса следующего считываемого блока) реализован неверночерез одно место и поэтому вообще не работоспособен.
Ладно, не будет увлекаться критикой сопроводительных примеров (даже плохой программный код все же лучше, чем совсем ничего) и перейдем непосредственно к изучению ASPI-интерфейса, а точнее –— его важнейшей команды SendASPI32Command, обеспечивающей передачу SRB-блоков устройству (со всеми остальными командами вы без труда справитесь и самостоятельно).
Структура SRB_ExecSCSICmd[Y118] ,[n2k119] в которую, собственно, и упаковывается SRB-запрос, как две капли воды похожа на структуру SCSI_PASS_THROUGH_DIRECT. Во всяком случае, между ними больше сходства, чем различий (листинг 1.4.11). Вот, взгляните сами:
Листинг 2.1.4.11. Структура SRB_ExecSCSICmd
typedef struct
{
BYTE SRB_Cmd; // ASPI command code = SC_EXEC_SCSI_CMD
BYTE SRB_Status // ASPI command status byte
BYTE SRB_HaId; // ASPI host adapter number
BYTE SRB_Flags; // ASPI request flags
DWORD SRB_Hdr_Rsvd; // Reserved, MUST = 0
BYTE SRB_Target; // Target's SCSI ID
BYTE SRB_Lun; // Target's LUN number
WORD SRB_Rsvd1; // Reserved for Alignment
DWORD SRB_BufLen; // Data Allocation Length
LPBYTE SRB_BufPointer; // Data Buffer Pointer
BYTE SRB_SenseLen; // Sense Allocation Length
BYTE SRB_CDBLen; // CDB Length
BYTE SRB_HaStat; // Host Adapter Status
BYTE SRB_TargStat; // Target Status
LPVOID SRB_PostProc; // Post routine
BYTE SRB_Rsvd2[20]; // Reserved, MUST = 0
BYTE CDBByte[16]; // SCSI CDB
BYTE SenseArea[SENSE_LEN+2]; // Request Sense buffer
}
SRB_ExecSCSICmd, *PSRB_ExecSCSICmd;
Обратите внимание: для взаимодействия с устройством вам совершенно незачем знать его дескриптор! Достаточно указать его физический адрес на шине (т. е. правильно заполнить поля SRB_HaId и SRB_Target)…. а как их узнать? Да очень просто: достаточно разослать по всем физическим адресам команду INQUIRY (код 12h). Устройствао, реально (и/или виртуально) подключенные к данному порту вернут идентификационную информацию (среди прочих полезных данных содержащую и свое имя), а несуществующие устройства не вернут ничего и операционная система "отрапортует" об ошибке.
Простейшая программа опроса устройств может выглядеть, например, так как показано в листинге 1.4.12.:
Листинг 2.1.4.12. Последовательный опрос портов на предмет наличия подключенных к ним устройств
#define MAX_ID 8
#define MAX_INFO_LEN 48
SEND_SCSI_INQUITY()
{
#define MAX_LUN 8 // макс. возможное кол-во логических устройств
BYTE AdapterCount;
DWORD ASPI32Status;
unsigned char buf[0xFF];
unsigned char str[0xFF];
unsigned char CDB[ATAPI_CDB_SIZE];
long a, real_len, adapterid, targetid;
// получаем кол-во адаптеров на шине
ASPI32Status = GetASPI32SupportInfo();
AdapterCount = (LOBYTE(LOWORD(ASPI32Status)));
// готовим CDB-блок
memset(CDB, 0, ATAPI_CDB_SIZE);
CDB[0] = 0x12; // INQUIRY
CDB[4] = 0xFF; // размер ответа
// спамим порты в надежде найти тех, кто нам нужен
for (adapterid = 0; adapterid < MAX_LUN; adapterid++)
{ // внимание! нельзя здесь ^^^^^^^^^^^^^ использовать AdapterCount,
// как это рекомендуется в некоторых руководствах, поскольку номера
// адаптеров устройств далеко не всегда идут вплотную друг к другу,
// и если в нумерации возникает "разрыв", одно или более устройств
// останутся необнаруженными
for (targetid = 0; targetid < MAX_ID; targetid++)
{
a = SEND_ASPI_CMD(adapterid, targetid, CDB,
ATAPI_CDB_SIZE, 0, buf, 0xFF, ASPI_DATA_IN);
if (a == SS_COMP)
{
real_len=(buf[4]>MAX_INFO_LEN)?buf[4]:MAX_INFO_LEN;
memcpy(str,&buf[8],real_len);str[real_len]=0;
printf("%d.%d <-- %s\n",adapterid, targetid, str);
}
}
}
}
Результат работы программы на компьютере автора выглядит так, как показано в листинге 1.4.13 (обратите внимание, что адреса устройств, подключенных к виртуальной SCSI-шине, созданной драйвером ASPI, могут и не соответствовать их физическим адресам; в данном случае, привод PHILIPS висящий на физическом IDE-порту с номером 0, попал на виртуальный порт с номером 1, поскольку нулевой порт занят драйвером Virtual Clone CD, при удалении последнего из системы, соответствие виртуальных и физических адресов полностью восстанавливается, однако ручаться за это нельзя).
Первая слева цифра в листинге — adapter ID, следующая за ней — target ID.:
Листинг 2.1.4.13. Устройства, подключенные к компьютеру автора. Первая слева цифра –— adapter ID, следующая за ней –— target ID.
0.0 <-- ELBY DVD-ROM 1.0
1.0 <-- IBM-DTLA-307015 TX2O
1.1 <-- PHILIPS CDRW2412A P1.55VO1214DM10574
2.0 <-- ST380011A 3.06
2.1 <-- TEAC CD-W552E 1.09
3.0 <-- AXV CD/DVD-ROM 2.2a
3.1 <-- AXV CD/DVD-ROM 2.2a
3.2 <-- AXV CD/DVD-ROM 2.2a
Другое немаловажное достоинство ASPI-интерфейса по сравнению с SPTI состоит в поддержке асинхронного режима обработки запросов. Отдав запрос на чтение такого-то количество секторов, вы можете продолжить выполнение своей программы, не дожидаясь, пока процесс чтения секторов полностью не завершится. Конечно, для достижения аналогичного результата при использовании интерфейса SPTI достаточно всего лишь создать еще один поток, но… это уже не так элегантно и красиво. Демонстрационный пример программы, осуществляющей "сырое" чтение сектора с CD-диска показан в листинге 1.4.14.
Листинг 2.1.4.141414. [\etc\RAW.CD.READ\aspi32.raw.c]. Демонстрационный Пример программы, осуществляющийосуществляющей "сырое" чтение сектора с CD-диска
#include "scsidefs.h"
#include "wnaspi32.h"
void ASPI32Post (LPVOID);
#define F_NAME "raw.sector.dat"
/* ASPI SRB packet length */
#define ASPI_SRB_LEN 0x100
#define RAW_READ_CM 0xBE
#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
#define MY_CMD RAW_READ_CMD
HANDLE hEvent;
//-[DWORD READ_RAW_SECTOR_FROM_CD]---------------------------------------------
// функция читает один или несколько секторов с CD-ROM в "сыром"
// (RAW) виде, согласно переданным флагам
//
// ARG:
// adapter_id - номер шины (0 - primary, 1 - secondary)
// read_id - номер устройства на шине (0 - master, 1 - slaeyer)
// buf - буфер, куда читать
// buf_len - размер буфера в байтах
// StartSector - с какого сектора читать, считая от нуля
// N_SECTOR - сколько секторов читать \
// flags - что читать (см. спецификацию на ATAPI)
//
// RET:
// - ничего не возвращает
//
// NOTE:
// - функция возвращает управления до завершения выполнения запроса,
// поэтому на момент выхода из нее, содержимое буфера с данными еще
// пусто, и реально он заполняется только при вызове функции
// ASPI32Post (вы можете модифицировать ее по своему усмотрению)
// для сигнализации о завершении операции рекомендуется использовать
// события (Event)
//
// - функция работает и под 9x/ME/NT/W2K/XP и _не_ требует для себя
// прав администратора. Однако ASPI-драйвер должен быть установлен.
//-----------------------------------------------------------------------------
READ_RAW_SECTOR_FROM_CD(int adapter_id,int read_id,char *buf,int buf_len,
int StartSector,int N_SECTOR,int flags)
{
PSRB_ExecSCSICmd SRB;
DWORD ASPI32Status;
// выделяем память для SRB-запроса
SRB = malloc(ASPI_SRB_LEN); memset(SRB, 0, ASPI_SRB_LEN);
// ПОДГОТОВКА SRB-блока
SRB->SRB_Cmd = SC_EXEC_SCSI_CMD; // выполнить SCSI команду
SRB->SRB_HaId = adapter_id; // ID адаптера
SRB->SRB_Flags = SRB_DIR_IN|SRB_POSTING; // асинхр. чтение данных
SRB->SRB_Target = read_id; // ID устройства
SRB->SRB_BufPointer = buf; // сюда читаются данные
SRB->SRB_BufLen = buf_len; // длина буфера
SRB->SRB_SenseLen = SENSE_LEN; // длина SENSE-буфера
SRB->SRB_CDBLen = 12; // размер ATAPI-пакета
SRB->CDBByte [0] = MY_CMD; // ATAI-команда
SRB->CDBByte [1] = 0x0; // формат CD - любой
// номер первого сектора
SRB->CDBByte [2] = HIBYTE(HIWORD(StartSector));
SRB->CDBByte [3] = LOBYTE(HIWORD(StartSector));
SRB->CDBByte [4] = HIBYTE(LOWORD(StartSector));
SRB->CDBByte [5] = LOBYTE(LOWORD(StartSector));
// кол-во читаемых секторов
SRB->CDBByte [6] = LOBYTE(HIWORD(N_SECTOR));
SRB->CDBByte [7] = HIBYTE(LOWORD(N_SECTOR));
SRB->CDBByte [8] = LOBYTE(LOWORD(N_SECTOR));
SRB->CDBByte [9] = flags // что читать?
SRB->CDBByte [10] = 0; // данные подканала не нужны
SRB->CDBByte [11] = 0; // reserverd
// адрес процедуры, которая будет получать уведомления
SRB->SRB_PostProc = (void *) ASPI32Post;
// посылаем SRB-запрос устройству
SendASPI32Command(SRB);
// возвращаемся из функции _до_ завершения выполнения запроса
return 0;
}
//----------------------------------------------------------------------------
// эта callback-функция вызывается самим ASPI и получает управление при
// завершении выполнения запроса или же при возникновении ошибки.
// в качестве параметра она получает указатель на экземпляр структуры
// PSRB_ExecSCSICmd, содержащей всю необходимую информацию (статус, указатель
// на буфер и т.д.)
//----------------------------------------------------------------------------
void ASPI32Post (void *Srb)
{
FILE *f;
// наш запрос выполнен успешно?
if ((((PSRB_ExecSCSICmd) Srb)->SRB_Status) == SS_COMP)
{
// ЭТОТ КОД ВЫ МОЖЕТЕ МОДИФИЦИРОВАТЬ ПО СВОЕМУ УСМОТРЕНИЮ
//-------------------------------------------------------
// записывает содержимое сектора в файл
// внимание! PSRB_ExecSCSICmd) Srb)->SRB_BufLen содержит не актуальную
// длину прочитанных данных, а размер самого буфера. если количество
// байт, возвращенных устройством, окажется меньше размеров буфера, то
// его хвост будет содержать мусор! здесь мы используем поле SRB_BufLen
// только потому, что при вызове функции SendASPI32Command тщательно
// следим за соответствием размера буфера количеству возвращаемой нам
// информации
if (f=fopen(F_NAME, "w"))
{
// записывает сектор в файл
fwrite(((PSRB_ExecSCSICmd) Srb)->SRB_BufPointer,1,
((PSRB_ExecSCSICmd) Srb)->SRB_BufLen, f);
fclose(f);
}
// кукарекаем и "размораживаем" поток, давая понять, что процедура
// чтения закончилась
MessageBeep(0); SetEvent(hEvent);
//--------------------------------------------------------
}
}
main(int argc, char **argv)
{
void *p; int buf_len, TIME_OUT = 4000;
if (argc<5)
{
fprintf(stderr,"USAGE:\n\tRAW.CD.READ.EXE adapter_id"\
", read_id, StartSector, n_sec\n"); return 0;
}
// вычисляем длину буфера и выделяем для него память
// ВНИМАНИЕ: таким образом можно юзать только до 64КБ
// если же вам требуются буфера больших объемов,
// используйте функцию GetASPI32Buffer
buf_len = PACKET_LEN*atol(argv[4]); p = malloc(buf_len);
// создаем событие
if ((hEvent = CreateEvent(NULL,FALSE,FALSE,NULL)) == NULL) return -1;
// читаем один или несколько секторов с CD
READ_RAW_SECTOR_FROM_CD(atol(argv[1]), atol(argv[2]),p,buf_len,
atol(argv[3]), atol(argv[4]),WHATS_READ);
// ждем завершения выполнения операции
WaitForSingleObject(hEvent, TIME_OUT);
return 0;
}
Откомпилировав этот пример и запустив его на выполнение, убедитесь, что он успешно работает как под Windows 9x, так и под Windows NT, причем не требуя у вас наличия прав администратора! С одной стороны, это, бесспорно хорошо, но с другой… наличие ASPI-драйвера создает огромную "дыру" вс системе безопасности, позволяя зловредным программам вытворять с вашим оборудованием все, что угодно. Заразить MBR (Master Boot Record) — главную загрузочную запись — /boot-сектора? Пожалуйста! Уничтожить информацию со всего диска целиком –— да проще этого ничего нет! Поэтому, если вы заботитесь о собственной безопасности –— удалите ASPI32-драйвер со своего компьютера (для этого достаточно удалить файл ASPI.SYS из каталога WINNT\System32\Drivers). Разумеется, сказанное относиться только к NT, поскольку в операционных системах Windows 9x прямой доступ к оборудованию можно заполучить и без этого.
Доступ посредствомчерез Cooked-Modeмоде (режим блочного чтения)
Операционная система WindowsNT выгодно отличается тем, что поддерживает режим блочного чтения с устройства, –— так называемый, cooked?modeCooked-Mode в котором все содержимое диска трактуется как один большой файл. По этому "файлу" можно перемещаться вызовом функции SetFilePointer и читать/писать отдельные сектора посредством вызовов функций ReadFile/WriteFile соответственно. Текущая позиция указателя задается в байтах (не секторах!), однако значение указателя обязано должно быть кратным логической длине сектора (512 байт для гибких/жестких дисков и 2048 байт для CD-ROM), в противном случае произойдет ошибка. Количество байт, читаемых (записываемых) за один раз, также должно укладываться в целое число секторов. Попытка прочитать сектор по "кусочкам" ни к чему не приведет.
Несмотря на всю изящность и простоту программной реализации, данному способу взаимодействия с приводом присущи серьезные недостатки. Во-первых, он не работает с файловыми системами отличными от ISO 9660/Juliet и High Sierra File System. В переводе на нормальный человеческий язык это обозначает, что для чтения секторов с аудиодисков режим блочного чтения непригоден и подходит лишь для обработки дисков с данными. Во-вторых, чтение "сырых" секторов в cooked-modeCooked-Mode невозможно, и нам придется довольствоваться лишь той их частью, что содержит пользовательские данные (User-Data). Такое положение дел значительно ослабляет стойкость защитного механизма и позволяет легко ввести его в заблуждение. Допустим, защита, основанная на привязке к физическим дефектам поверхности носителя, пытается прочесть ключевой сектор на предмет проверки его читабельности. Поскольку содержимое кодов коррекции защитному механизму недоступно, он не может отличить действительные физические дефекты от их грубой имитации (то есть умышленного искажения кодов ECC/EDC кодов копировщиком с целью эмуляции неустранимых ошибок чтения).
Проверить, использует ли защита данный способ доступа к диску или нет можно следующим образом: просто установите точку останова на функцию CreateFile, заставив отладчик "всплывать" в том и только в том случае, если первые четыре символа имени открываемого файла равны "\\.\" (то есть функция открывает не файл, а устройство).
Например, это может выглядеть так: "bpx CreateFileA if (*esp->4=='\\\\.\\')", затем нам останется лишь убедиться в том, что за последней косой чертой следует буква именного того привода, который нам нужен (на компьютере автора это привод "\\.\G:"). Дождавшись выхода из функции CreateFile по команде "P RET" и подсмотрев возращенный ей дескриптор устройства (который будет содержаться в регистре EAX), мы сможем перехватить все вызовы функций SetFilePointer/ReadFile, анализ окрестностей которых и "разоблачит" алгоритм работы защитного механизма.
Демонстрационный пример, приведенный в листинге 1.4.5ниже, представляет собой вполне законченную утилиту для "грабежа" дисков с данными на секторном уровне с последующей записью всего "награбленного" в файл.
Листинг 2.1.4.5. [/cooked.sector.read.c] Пример, демонстрирующий технику чтения секторов в cooked-modeCooked-Mode
/*----------------------------------------------------------------------------
*
* ЧИТАЕТ СЕКТОРА С CD-ROM В БЛОЧНОМ РЕЖИМЕ
* ========================================
*
* данная программа работает только под Windows NT, не требуя для себя
* прав администратора
*
* Build 0x001 @ 19.05.03
---------------------------------------------------------------------------- */
#include <windows.h>
#include <winioctl.h>
#include <stdio.h>
// ПАРАМЕТРЫ ПО УМОЛЧАНИЮ
#define DEF_FN "sector"
#define DEF_TO 0x666
#define DEF_FROM 0x000
#define CDROM_SECTOR_SIZE 2048 // for MODE1/MODE2FORM1 only!
// АРГУМЕНТЫ КОМАНДНОЙ СТРОКИ
#define argCD (argv[1])
#define argFN ((argc > 2)?argv[2] :DEF_FN)
#define argFROM ((argc > 3)?atol(argv[3]):DEF_FROM)
#define argTO ((argc>4)?(atol(argv[4])>argFROM)?atol(argv[4]):argFROM:DEF_TO)
main(int argc, char **argv)
{
int a;
FILE *f;
HANDLE hCD;
char *buf;
DWORD x_read;
char buf_n[1024];
// ПРОВЕРЯЕМ АРГУМЕНТЫ
if (argc<2)
{
printf("USAGE: cooked.sector.read PhysCD [filename] [from] [to]\n");
printf("\tPhysCD - physical name of CD (\"\\\\.\\G:\")\n");
printf("\tfilename - file name to store follow sector\n");
printf("\tfrom - start sector\n");
printf("\tto - end sector\n");
return 0;
}
// TITLE
fprintf(stderr,"cooked sector reader for NT\n");
// ВЫДЕЛЯЕМ ПАМЯТЬ
buf=malloc(CDROM_SECTOR_SIZE);if (!buf){printf("-ERR:low memory\n");return -1;}
// ОТКРЫВАЕМ УСТРОЙСТВО
hCD=CreateFile(argCD, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
if (hCD == INVALID_HANDLE_VALUE) {
printf("-ERR: error CreateFile(%s,....)\n", argCD); return -1;
}
// INFO
printf("read sector from %04d to %04d in %s file\n", argFROM, argTO, argFN);
// ПОЗИЦИОНИРУЕМ УКАЗАТЕЛЬ НА ПЕРВЫЙ ЧИТАЕМЫЙ СЕКТОР
SetFilePointer (hCD, CDROM_SECTOR_SIZE * argFROM, NULL, FILE_BEGIN);
// ЧИТАЕМ СЕКТОРА ОДИН ЗА ДРУГИМ
for (a = argFROM; a <= argTO; a++)
{
// читаем очередной сектор
if (ReadFile(hCD, buf, CDROM_SECTOR_SIZE, &x_read, NULL) && x_read)
{
// записываем только что считанный сектор в файл
sprintf(buf_n,"%s[%04d].dat",argFN, a);
if (f=fopen(buf_n,"wb")){fwrite(buf, 1, x_read, f); fclose(f);}
printf("sector [%04d.%04d] read\r",a, argTO);
}
else
{
printf("sector %04d read error\n",a);
}
}
}
Доступ посредствомчерез драйвера CD-ROM-драйвер
Управление драйверами устройств в операционных системах семейства Windows осуществляется посредством вызова функции DeviceIoControl, отвечающей за посылку специальных FSCTL/IOCTL- – команд. Префикс FS-
свидетельствует о принадлежности данной команды к файловой системе и в контексте настоящей публикации не представляет для нас никакого интереса. Команды с префиксом IO- относятся к устройству ввода/вывода, а точнее –— к его драйверу.
Функция DeviceIoControl просто передает такую команду, как она есть, совершенно не задумываясь о ее "физическом смысле". Следовательно, совершенно бессмысленно искать перечень доступных IOCTL –- команд в описании функции DeviceIoControl. Их там нет! Точнее, здесь приводятся лишь стандартные IOCTL-команды, а вся остальная информация по этому вопросу содержится в DDK (Device Driver Kit).
Там, в частности, мы найдем, что для чтения оглавления диска используется команда IOCTL_CDROM_READ_TOC, а для перечисления адресов сессий многосессионных дисков –— IOCTL_CDROM_GET_LAST_SESSION. Также обратите свое внимание на команду IOCTL_CDROM_READ_Q_CHANNEL, обеспечивающую извлечение информации из Q-?канала подкода (для извлечения ключевых меток –— это актуально).
Чтение "сырых" секторов осуществляется командой IOCTL_CDROM_RAW_READ, возможности которой, к сожалению, ограничены только лишь дисками CD-DA (Compact Disk Digital Audio)-дисками только. Посекторное чтение с дисков CD-DATA-дисков ни на сыром, ни на "сухом" уровнях не поддерживается. В соответствии с принятой политикой безопасности, никакое приложение не должно действовать в обход системы безопасности, в противном случае, злоумышленник сможет без труда дорватьсядобраться до конфиденциальных данных, просто прочитав диск на секторном уровне. Штатные драйверадрайвераы, которыми укомплектованы операционные системы семейства Windows, всецело следуют этим требованиям, хотя сторонние разработчики могут, при желании, и нарушить этот запрет.
В состав NT DDK входит исходный текст демонстрационного драйвера CD-ROM-драйвера ("NTDDK\src\storage\class\cdrom\"), который после небольшой обработки "напильником", согласитьсясогласится читать диски всех типов, не задавая при этом глупых вопросов. Найдите в теле файла cdrom.c следующую строку "if (rawReadInfo->TrackMode == CDDA) {" и перейдите к ветке, чей код операции (OperationCode[Y100] ) равен SCSIOP_READ. А теперь модифицируйте код так, чтобы она (эта ветка) получала управление и во всех остальных случаях.
Замечание
Функция Команда IRP_MJ_READ, присутствующая в DDK, и по идее обеспечивающая возможность чтения отдельных логических блоков, является внутренней функцией драйвера и доступ к последней с прикладного уровня закрыт; пытаться использовать ее в паре с функцией DeviceIoControl — бессмысленно.
В таблице 1.4.1 приведено описание IOCTL-команд штатного драйвера CD-ROM (за более подробной информацией обращайтесь к DDK).
Таблица 21.4.11. Описание IOCTL-команд штатного драйвера CD-ROM драйвера (за более подробной информацией обращайтесь к DDK)
IOCTL-команда |
Описание |
IOCTL_CDROM_CHECK_VERIFY, IOCTL_STORAGE_CHECK_VERIFY (0x24800h) |
Определяет факт смены диска (открытия/закрытия лотка) |
IOCTL_CDROM_CLOSE_DOOR* IOCTL_STORAGE_LOAD_MEDIA (0x2D480Ch) |
Закрывает лоток привода |
IOCTL_CDROM_FIND_NEW_DEVICES, IOCTL_STORAGE_FIND_NEW_DEVICES (0x24818h) |
Перечисляет новые приводы, подключенные после загрузки системы или последнего вызова данной команды |
IOCTL_CDROM_GET_CONTROL |
Сообщает текущую позицию воспроизведения аудио |
IOCTL_CDROM_GET_DRIVE_GEOMETRY (0x2404Ch) |
Определяет тип лазерного диска и его геометрию (количест-во секторов на диске, размер одного сектора и т. д.) |
IOCTL_CDROM_GET_LAST_SESSION (0x24038h) |
Перечисляет стартовые адреса сессий и записывает их в буфер TOC, читаемый командой IOCTL_CDROM_READ_TOC |
IOCTL_CDROM_GET_VOLUME (0x24014h) |
Возвращает текущий уровень громкости с CD-ROM |
IOCTL_CDROM_PAUSE_AUDIO (0x2400Ch) |
Временно останавливает воспроизведение аудио |
IOCTL_CDROM_PLAY_AUDIO_MSF (0x24018h) |
Инициирует процесс воспроизведения аудио от сих до сих |
IOCTL_CDROM_RAW_READ (0x2403Eh) |
Выполняет "Сыроесырое" чтение секторов с аудиодисков |
IOCTL_CDROM_READ_Q_CHANNEL (0x2402Ch) |
Читаеттение данныех Q-канала подкода |
IOCTL_CDROM_READ_TOC (0x24000h) |
Читает оглавление диска |
IOCTL_CDROM_RESUME_AUDIO (0x24010h) |
Продолжаетить воспроизведение аудио |
IOCTL_CDROM_SEEK_AUDIO_MSF (0x24004h) |
Позиционирует оптическую головку |
IOCTL_CDROM_SET_VOLUME (0x24028h) |
Устанавливаетовить уровень громкости с CD-ROM |
IOCTL_CDROM_STOP_AUDIO (0x24008h) |
Останавливает воспроизведение аудио |
* — устарело и ныне удалено из DDK
Функции DeviceIoControl всегда предшествует вызов функции CreateFile, возвращающей дескриптор соответствующего устройства, задаваемого в виде "\\.\X:", где XX –— буквенное обозначение того привода, с которым мы собрались работать, причем флаг dwCreationDisposition должен быть установлен в состояние OPEN_EXISTING, иначе вы потерпите неудачу. Типовой пример вызова функции приведен далее в листингах 2.1.4.1—2.1.4.2.ниже
Замечание
Windows NT регистрирует устройство с именем \\.\CdRomx, где x —– номер привода, считая от нуля, которое ссылается на тот же самый драйвер, что и буквенное обозначение диска и обладает тем же самым набором функций.):
Листинг 2.1.4.1. Пример открытия устройства
HANDLE hCD; // дескриптор привода
hCD=CreateFile("\\\\.\\X:", GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0,0);
if (hCD == INVALID_HANDLE_VALUE) // ошибка
Прототип самой же функции DeviceIoControl выглядит так, как это показано в листинге 2.1.4.2.:
Листинг 2.1.4.2. Прототип функции DeviceIoControl
BOOL DeviceIoControl(
HANDLE hDevice, // дескриптор устройства
DWORD dwIoControlCode, // IOCTL-код команды для выполнения
LPVOID lpInBuffer, // указатель на входной буфер
// (Irp->AssociatedIrp.SystemBuffer)
DWORD nInBufferSize, // размер входного буфера в байтах
LPVOID lpOutBuffer, // указатель на выходной буфер
// (Irp->AssociatedIrp.SystemBuffer)
DWORD nOutBufferSize, // размер выходного буфера в байтах
LPDWORD lpBytesReturned, // указатель на счетчик кол-ва возвращенных байт
LPOVERLAPPED lpOverlapped // указатель на структуру для асинхронных операций
);
Здесь:
q hDevice —– тот самый дескриптор, который был только что возращен функцией CreateFile;
q dwIoControlCode —– IOCTL-код нашей операции;
q lpInBuffer —– указатель на буфер, содержащий данные, подготовленные для передачи устройству (как правило, аргументы команды). В процессе выполнения функции содержимое буфера копируется в Irp?>AssociatedIrp.SystemBuffer. Это на тот случай, чтобы, увидев такую абракадабру в DDK, вы не хватались за сердце и не пытались "скормить" функции DeviceIoControl всю IRP-структуру;
q nInBufferSize —– размер входного буфера в байтах. В процессе выполнения функции он копируется в структуру Parameters.DeviceIoControl.InputBufferLength;
q lpOutBuffer —– указатель на выходной буфер, в который помещается содержимое Irp?>AssociatedIrp.SystemBuffer;
q nOutBuffersSize —– указатель на двойное слово, в которое будет записано количест-во байт, возвращенных драйвером через выходной буфер.
Если операция завершилась успешно, функция возвращает ненулевое значение, и нуль —– в противном случае. За более подробной информацией об ошибке возвращайтесь к функции GetLastError.
Передача IOCTL-команд устройству не требует наличия прав администратора (за тем исключением, когда устройство открывается с флагом GENETIC_WRITE), что значительно увеличивает "эргономичность" защитных механизмов, базирующихся на ее основе (Кстати, о защитных механизмах, точнее их стойкости ко взлому. Поскольку функция DeviceIoControl к числу популярных явно не относится, она демаскирует "штаб-квартиру" защитного механизма, и его становится очень легко "запеленговать". Достаточно поставить на функцию DeviceIoControl точку останова и дождаться, пока передаваемая ей IOCTL-команда не примет одно из вышеперечисленных значений. На функцию CreateFile точку останова лучше не ставить, –— т. к. это даст множество ложных срабатываний (функция CreateFile вызывается всякий раз при открытии/создании какого- либо файла).
А вот попробовать поискать в теле программы текстовую строку ""\\.\"" все-таки стоит. И, если она действительно будет найдена, вам останется лишь подбежать курсором к перекрестной ссылке и нажатьдолбануть на клавишупо <Enter>'у. Все! Защитный код перед вами!)
Для лучшего понимания данного способа взаимодействия между прикладной программой и драйвером далее в листинге 1.4.3ниже приведен ключевой фрагмент функции, как раз и осуществляющей такое взаимодействие (обработка ошибок по соображениям наглядности опущена).:
Листинг 2.1.4.3. [/IOCTL.CDDA.raw.read.c] Функция, демонстрирующая технику чтения "сырых" секторов через CDFS-драйвер (только для дисков CD-DA дисков!)
//--[ReadCDDA]-----------------------------------------------------------------
//
// читает сектор в "сыром" виде с CDDA-дисков
// ==========================================
// ARG:
// drive - имя устройства, с которого читать (например "\\\\.\\X:")
// start_sector - номер первого читаемого сектора
// n_sec - сколько секторов читать
//
// RET:
// == 0 - ошибка
// != 0 - указатель на буфер, содержащий считанные сектора
//
// NOTE:
// функция поддерживает только диски тех типов, что поддерживает драйвер
// CDFS, который она и использует, а штатный драйвер Windows NT поддерживает
// лишь CDDA-диски
//----------------------------------------------------------------------------
char* ReadCDDA(char *drive, int start_sector, int n_sec)
{
// поддерживаемые типы треков
typedef enum _TRACK_MODE_TYPE {
YellowMode2, // native MODE 2 (не CD-data)
XAForm2, // XA MODE 2 Form 2 (VideoCD)
CDDA // Audio-CD
} TRACK_MODE_TYPE, *PTRACK_MODE_TYPE;
// аргумент IOCTL-команды IOCTL_RAW_READ
typedef struct __RAW_READ_INFO {
LARGE_INTEGER DiskOffset; // смещение в байтах лог. блоков
ULONG SectorCount; // кол-во секторов для чтения
TRACK_MODE_TYPE TrackMode; // режим читаемого трека
} RAW_READ_INFO, *PRAW_READ_INFO;
#define CDROM_RAW_SECTOR_SIZE 2352
#define CDROM_SECTOR_SIZE 2048
int a;
HANDLE hCD;
DWORD x_size;
char *szDrive;
BOOL fResult = 0;
unsigned char *buf;
RAW_READ_INFO rawRead;
// ПОДГОТАВЛИВАЕМ СТРУКТУРУ RAW_READ_INFO, передаваемую драйверу CD-ROM'а
rawRead.TrackMode = CDDA; // тип диска – Audio CD
rawRead.SectorCount = n_sec; // кол-во читаемых секторов
rawRead.DiskOffset.QuadPart = start_sector * CDROM_SECTOR_SIZE;
// ^^^^^^^^^^^^^^^^^^
// стартовый сектор задается отнюдь не своим логическим номером,
// а номером своего первого байта. сквозная нумерация байтов от
// первого до последнего байта диска теоритически обеспечивает
// полное абстрагирование от конкретного оборудования
// (размер одного сектора возвращается IOCTL-командой
// IOCTRL_CDROM_GET_DRIVE_GEOMETRY), но практически архитекторами
// драйвера допущен грубый ляп, "благодаря" которому драйвер
// принимает вовсе не сквозные номера байт,
// а start_address * CDROM_SECTOR_SIZE, где
// SECTOR_SIZE – размер логического блока, который в данном случае равен
// стандартному размеру сектора CDDATA-диска (2048 байт для справки),
// в то время как размер сектора CDDA-дисков составляет 2352 байта
// поэтому DiskOffset равен start_secor * CDROM_SECTOR_SIZE, а размер
// буфера должен быть равен start_secor * CDROM_RAW_SECTOR_SIZE
// ВЫДЕЛЯЕМ ПАМЯТЬ
buf = malloc(CDROM_RAW_SECTOR_SIZE * n_sec);
// ПОЛУЧАЕМ ДЕСКРИПТОР УСТРОЙСТВА
hCD = CreateFile(drive,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,0,0);
if (hCD != INVALID_HANDLE_VALUE)
// ПЕРЕДАЕМ ДРАЙВЕРУ ПРИВОДА КОМАНДУ IOCTL_CDROM_RAW_READ
fResult = DeviceIoControl( hCD, 0x2403E /* IOCTL_CDROM_RAW_READ */,
&rawRead, sizeof(RAW_READ_INFO),
buf, CDROM_RAW_SECTOR_SIZE*n_sec,
&x_size, (LPOVERLAPPED) NULL);
// ВЫВОДИМ РЕЗУЛЬТАТ (если есть, что выводить)
if (fResult)
for (a = 0; a <= x_size; ++a) printf("%02X%s",buf[a],(a%24)?" ":"\n");
else
printf("-ERROR"); printf("\n");
// СВАЛИВАЕМ
CloseHandle(hCD); return (fResult)?buf:0;
}
Еще один демонстрационный пример приведен в листинге 1.4.4, изучение которого бывает полезно при анализе некоторых защищенных дисковниже. Он иллюстрирует технику чтения TOC (Table Of Content) —– своеобразный аналог таблицы разделов лазерных аудиодисков.
Листинг 2.1.4.4. [/IOCTL.read.TOC.c] еще один Пример программы, взаимодействующей с CDFS-драйвером через IOCTL и читающей содержимое TOC'а (с расшифровкой), изучение которого бывает полезно при анализе некоторых защищенных дисков
/*--------------------------------------------------------------------------
*
* ЧТЕНИЕ И РАСШИФРОВКА TOC
* ========================
*
* build 0x001 @ 26.05.2003
--------------------------------------------------------------------------*/
main(int argc, char **argv)
{
int a;
HANDLE hCD;
unsigned char *buf;
WORD TOC_SIZE;
BYTE n_track;
DWORD x_size,b;
#define DEF_X "\\\\.\\G:" // привод по умолчанию
#define argCD ((argc>1)?argv[1]:DEF_X)
// ПРОВЕРКА АРГУМЕНТОВ
if (argc < 2) {fprintf(stderr, "USAGE: IOCTL.read.TOC \\\\.\\X:\n"); return 0;}
// TITLE
fprintf(stderr," simple TOC reader via IOCTL\n");
// ВЫДЕЛЯЕМ ПАМЯТЬ
buf = (char *) malloc(buf_len);
// ОТКРЫВАЕМ УСТРОЙСТВО
hCD=CreateFile(argv[1], GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
// ВЫХОДИМ, ЕСЛИ ОШИБКА
if (hCD == INVALID_HANDLE_VALUE)
{fprintf(stderr,"-ERR: %x\n", GetLastError()); return 0;}
// ПЕРЕДАЕМ ДРАЙВЕРУ КОМАНДУ CDROM_READ_TOC
if (DeviceIoControl( hCD, 0x24000 /* IOCTL_READ_TOC */,
0, 0, buf, buf_len, &x_size, 0) != 0)
{
// ПОЛУЧАЕМ ДЛИНУ ТОС'а (она записана в обратном порядке)
TOC_SIZE = buf[0]*0x100L + buf[1];
printf("TOC Data Length........%d\n",TOC_SIZE);
// декодируем остальную информацию
printf("First Session Number...%d\n",buf[2]);
printf("Last Session Number....%d\n\n",(n_track=buf[3]));
for (a = 1; a <= n_track; a++)
{
printf("track %d\n{\n",a);
printf("\treserved.............%x\n",buf[a * 8 - 4]);
printf("\tADR|control..........%d\n",buf[a * 8 - 3]);
printf("\ttrack number.........%d\n",buf[a * 8 - 2]);
printf("\treserved.............%d\n",buf[a * 8 - 1]);
printf("\treserved.............%d\n",buf[a * 8 + 0]);
printf("\tmin..................%d\n",buf[a * 8 + 1]);
printf("\tsec..................%d\n",buf[a * 8 + 2]);
printf("\tframe................%d\n",buf[a * 8 + 3]);
printf("}\n\n");
}
// выводим содержимое TOC'a в "сыром" виде
printf("\n\t\t\t* * * RAW * * *\n");
for(a = 0; a < x_size; a++)
printf("%02X%s",(unsigned char)buf[a],((a+1)%22)?" ":"\n");
printf("\n\t\t\t* * * * * * *\n");
}
}
Доступ посредствомчерез мини-порта интерфейса SCSI-мини порт
Драйвер SCSI-мини- порта интерфейса SCSI и есть тот самый драйвер, за счет которого системе удается абстрагироваться от особенностей физических интерфейсов конкретного оборудования. Условимся для краткости называть его просто "мини-драйвером", хотя это будет и не совсем верно, поскольку, помимо SCSI-мини-портов интерфейса SCSI, существуют драйверадрайверыа для видео и сетевых мини- портов. Однако поскольку ни те, ни другие к рассматриваемому нами контексту ни коим боком не относятся, то никаких разночтений и не возникает.
Иерархически драйвер мини- порта располагается между физическими (виртуальными) устройствами, подключенными к тем или иным интерфейсным шинам компьютера (IDE/PCI/SCSI) и драйвером SCSI-порта. Драйвер мини- порта представляет собой системно-независимый драйвер, но в то же время зависимый от специфики конкретных HBA (Host Bus Adapter), то есть того самого физического/виртуального оборудования, которое он обслуживает. Драйвер мини- порта экспортирует ряд функций семейства ScsiPortXXX, предназначенных для использования драйверами верхних уровней, и обычно реализуется как динамическая библиотека (то есть DLL), естественно, исполняющийся в нулевом кольце "ядерного" уровня.
Именно он транслирует SCSI-запросы в команды подключенного к нему устройства, именно он создает виртуальные SCSI-порты с именами типа "\Device\ScsiPortx", именно он обеспечивает поддержку накопителей с физическими интерфейсами, отличными от SCSI-интерфейса. Драйвер ATAPI.SYS, обслуживающий приводы CD-ROM приводы с ATAPI-интерфейсом, драйвер DISK.SYS, обслуживающий жесткие диски, –— все они реализованы как драйверадрайверы мини- порта.
Управление мини- портом осуществляется посредством специального IOCTL-кода, передаваемого функции DeviceIoControl и определенного в файле NTDDSCSI.H как IOCTL_SCSI_MINIPORT. Если же у вас нет NT DKK, то вот его непосредственное значение: 0x4D008. Естественно, прежде чем вызывать функцию DeviceIoControl, соответствующий SCSI-порт должен быть заблаговременно открыт функцией CreateFile.
Это может выглядеть, например, так как это показано в листинге 1.4.17. Причем обратите внимание на то, что имя порта должно выглядеть как SCSIx:, а не как ScsiPortx; причем в его конце обязательно должен присутствовать символ двоеточия, иначе ничего не получится.
:
Листинг 2.1.4.1187. Открытие SCSI-порта для управления драйвером мини-порта. Причем обратите внимание: имя порта должно выглядеть как "SCSIx:", но не как "ScsiPortx"; причем в его конце обязательно должен присутствовать символ двоеточия, иначе ничего не получится
h = CreateFile("\\\\.\\SCSI1:", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ |
FILE_SHARE_WRITE, NULL,OPEN_EXISTING, 0, NULL);
Здесь мы открываем первый, считая от нуля, SCSI-порт, который, как мы уже знаем, соответствует первому каналу IDE или, другими словами, Secondary IDE-контроллеру (на компьютере автора привод CD-ROM "висит" именно на нем). Для определения расположения приводов на неизвестном нам компьютере можно воспользоваться IOCTL-кодом IOCTL_SCSI_GET_INQUIRY_DATA, который заставит драйвер мини- порта перечислить все имеющеесяимеющееся в его наличии оборудование, после чего нам останется только определить его тип (подробнее см. "NTDDK\SRC\STORAGE\CLASS\SPTI[Y123] [n2k124] ").
Однако управление мини- портом осуществляется совсем не так, как SCSI-портом! На этом уровне никаких стандартных команд уже не существует, и мы вынуждены работать с учетом специфики и особенностей реализации конкретного оборудования. Вместо SRB-запросов, мини-драйверу передается структура SRB_IO_CONTROL, обеспечивающая управление драйвером мини-порта и определенная следующим образом (листинг 1.4.18).:
Листинг 2.1.4.1198. Назначение полей структуры SRB_IO_CONTROL, обеспечивающей управление драйвером мини-порта
typedef struct _SRB_IO_CONTROL
{
ULONG HeaderLength; // sizeof(SRB_IO_CONTROL)
UCHAR Signature[8]; // сигнатура мини-драйвера
ULONG Timeout; // макс. время ожидания выполнения запроса в сек
ULONG ControlCode; // код команды
ULONG ReturnCode; // здесь нам вернут статус завершения
ULONG Length; // длина всего передаваемого буфера целиком
} SRB_IO_CONTROL, *PSRB_IO_CONTROL;
Ну, с полем HeaderLength все более или менее ясно, но вот что эта за сигнатура такая?! Дело в том, что коды управления драйверами мини-порта не стандартизованы и определяются непосредственно самим разработчиком данного драйвера, а потому коды команд одного драйвера навряд ли подойдут к другому. Вот во избежание междоусобных конфликтов каждый драйвер мини- порта и содержит уникальную сигнатуру, которую тщательно сверяет с сигнатурой переданной приложением в поле Signature структуры SRB_IO_CONTROL. И, если эти сигнатуры не совпадают, драйвер "отвечает": SRB_STATUS_INVALID_REQUEST, указывая на ошибку запроса. (типа, отвали, моя черешня). К сожалению, интерфейс штатных мини-драйверов ATAPI.SYS и DISK.SYS абсолютно незадокументирован, и, если вы не умеете дизассемблировать, то вам остается лишь посочувствовать. Дизассемблер же сразу показывает, что сигнатуры обоих драйверов выглядят как "SCSIDISK", а сигнатура мини-драйвера от Alcohol 120% –— "Alcoholx" (впрочем, последний, в силу своей нештатности, не представляет для нас особенного интереса).
С кодами команды разобраться сложнее. Правда, специалистыспециалисты, постоянно читающие MSDN, и потому неплохо в нем ориентирующиеся, вероятно, смогут вспомнить, что: "…this specification describes the API for an application to issue SMART commands to an IDE drive under Microsoft Windows 95 and Windows NT. Under Windows 95, the API is implemented in a Vendor Specific Driver (VSD), Smartvsd.vxd. SMART functionality is implemented as a "pass through" mechanism whereby the application sets up the IDE registers in a structure and passes it to the driver through the DeviceIoControl API" ("…эта спецификация описывает интерфейс API [n2k125] для приложений, передающих SMART-команды жестким дискам с IDE-интерфейсомв под Microsoft Windows 95 и Windows NT.
Под Windows 95 API реализовано в драйвере, специфичном для конкретного производителя (VSD –— Vendor Specific Driver), называемом Smartvsd.vxd. SMART-функциональность реализована как "pass through"-механизм, посредством которого приложения устанавливают IDE-регистры, передавая их драйверу через специальную структуру, помещаемую во входной буфер функции DeviceIoControl ").
Примечание
SMART (Self-Monitoring Analysis and Reporting Technology) — технология самоконтроля и составления диагностических отчетов, направленная на активное конролирование за состоянием узлов диска. Поддерживается рядом производителей дисков и позволяет утилитам диагностировать их состояние. [n2k126]
Ага! Один из драйверов позволяет нам манипулировать регистрами IDE-контроллера по своему усмотрению, то есть фактически предоставляет низкоуровневый доступ к диску! Очень хорошо! Интерфейс со SMART-драйвером достаточно хорошо документирован (см[Y127] [n2k128] . "MSDN à Specifications à Platforms à SMART IOCTL API Specification"), правда, раздражает "гробовое молчание" насчет Windows NT. То, что в NT никаких драйверов VxD нет –— это и "ежу ясно". Но в то же время заявляется, что технология SMART API в ней как будто бы реализована… Если напрячь свои мозги и проявить чудеса интуиции, можно догадаться, что поддержка SMART в NT обеспечивается штатными средствами! Весь вопрос в томтом,: какими именно средствами и как? Ни SDK, ни DDK не содержат никакой информации на этот счет, но вот копание в заголовочных файлахов из комплекта NT DDK может кое-что дать! Посмотрите, что обнаруживается в файле scsi.h при тщательном его изучениипросмотре (листинг 1.4.19). Здесь представлены команды управления технологией SMART в Windows NT, которые мы можем передавать драйверу мини-порта через поле ControlCode структуры SRB_IO_CONTROL. :
Листинг 2.1.4.2019. Команды управления SMART в Windows NT, которые мы можем передавать драйверу мини-порта через поле ControlCode структуры SRB_IO_CONTROL
//
// SMART support in atapi
//
#define IOCTL_SCSI_MINIPORT_SMART_VERSION ((FILE_DEVICE_SCSI<<16)+0x0500)
#define IOCTL_SCSI_MINIPORT_IDENTIFY ((FILE_DEVICE_SCSI<<16)+0x0501)
#define IOCTL_SCSI_MINIPORT_READ_SMART_ATTRIBS ((FILE_DEVICE_SCSI<<16)+0x0502)
#define IOCTL_SCSI_MINIPORT_READ_SMART_THRESHOLDS ((FILE_DEVICE_SCSI<<16)+0x0503)
#define IOCTL_SCSI_MINIPORT_ENABLE_SMART ((FILE_DEVICE_SCSI<<16)+0x0504)
#define IOCTL_SCSI_MINIPORT_DISABLE_SMART ((FILE_DEVICE_SCSI<<16)+0x0505)
#define IOCTL_SCSI_MINIPORT_RETURN_STATUS ((FILE_DEVICE_SCSI<<16)+0x0506)
#define IOCTL_SCSI_MINIPORT_ENABLE_DISABLE_AUTOSAVE ((FILE_DEVICE_SCSI<<16)+0x0507)
#define IOCTL_SCSI_MINIPORT_SAVE_ATTRIBUTE_VALUES ((FILE_DEVICE_SCSI<<16)+0x0508)
#define IOCTL_SCSI_MINIPORT_EXECUTE_OFFLINE_DIAGS ((FILE_DEVICE_SCSI<<16)+0x0509)
#define IOCTL_SCSI_MINIPORT_ENABLE_DISABLE_AUTO_OFFLINE
(FILE_DEVICE_SCSI<<16)+0x050a
Нет сомненияОторви Тиггеру хвост, чтоесли в Windows NT функциональность SMART реализуется не в драйвере мини- порта! И дизассемблирование драйвера ATAPI.SYS действительно подтверждает это! Вот вам и качество документации от Microsoft, –— уродство сплошное в стиле маразм крепчает. Какой смысл включать в заголовочный файл IOCTL-команды, но не документировать их?! Причем согласно лицензии дизассемблирование любых компонентов операционной системы запрещено. Ладно, не будемт скулить по поводу и без, а лучше еще раз перечитаем [Y129] [n2k130] "SMART IOCTL API Specification", откуда поймем, что для управления драйвером мини- порта под Windows NT в поле ControlCode структуры SRB_IO_CONTROL мы должны передать код одной из приведенных выше команд. Пусть это будет, например, IOCTL_SCSI_MINIPORT_IDENTIFY.
Сразу же за концом структуры SRB_IO_CONTROL должна быть расположена структура SENDCMDINPARAMS, дающая прямой доступ к IDE-регистрам и, определенная как это показано в листинге 1.4.20.следующим образом:
Листинг 2.1.4.2120. Структура SENDCMDINPARAMS, дающая прямой доступ к IDE-регистрам
typedef struct _SENDCMDINPARAMS
{
DWORD cBufferSize; // размер буфера в байтах или нуль
IDEREGS irDriveRegs; // структура, содержащая значение IDE-регистров
BYTE bDriveNumber; // физический номер диска, считая от нуля
BYTE bReserved[3]; // зарезервировано
DWORD dwReserved[4]; // зарезервировано
BYTE bBuffer[1]; // отсюда начинается входной буфер
} SENDCMDINPARAMS, *PSENDCMDINPARAMS, *LPSENDCMDINPARAMS;
То есть входной буфер функции DeviceIoControl должен выглядеть как на рисунке 1.4.6. так:
Рис. 2.1.4.76. 0x038 Структура входного буфера функции DeviceIoControl для управления драйвером мини- порта под Windows 9x/NT
Первый элемент структуры –— cBufferSize, содержащий размер буфера bBuffer'a слишком очевиден и не интересен. А вот структура IDREGS обеспечивает низкоуровневый доступ к IDE-регистрам и представляет собой настоящий клад., Вот взгляните сами (листинг 1.4.21), (только не упадите со стула, ибо потрясение будет столь же острым, сколь и глубоким).:
Листинг 2.1.4.2221. Структура IDEREGS, предоставляющая низкоуровневый доступ к IDE-регистрам
typedef struct _IDEREGS
{
BYTE bFeaturesReg; // IDE Features-регистр
BYTE bSectorCountReg; // IDE SectorCount-регистр
BYTE bSectorNumberReg; // IDE SectorNumber-регистр
BYTE bCylLowReg; // IDE CylLowReg-регистр
BYTE bCylHighReg; // IDE CylHighReg-регистр
BYTE bDriveHeadReg; // IDE DriveHead-регистр
BYTE bCommandReg; // командный регистр
BYTE bReserved; // зарезервировано
} IDEREGS, *PIDEREGS, *LPIDEREGS;
Всякий, кто читал спецификацию на ATA/ATAPI (Advenced Technology Attachment/ Advenced Technology Attachment Packet Interface)[n2k131] и хоть однажды сталкивался с программированием устройств с интерфейсом IDE, должен немедленно узнать до боли знакомые регистры Command, Drive/Head, Cylinder High, Cylinder Low, Sector Number, Sector Count и Features, правда, в структуре IDEREGS они перечислены почему-то в обратном порядке, но это уже мелочи реализации.
Главное, что с помощью этой структуры мы можем проделыватьвытворять с приводом все мыслимые и немыслимые фокусы, на которые только способно "железо". Даже не верится, что в подсистеме безопасности существует такая "дыра" размерами со "слонопотама". И это при том, что для управления мини- портом наличие прав администратора совсем не обязательно! Дрожа и подпрыгивая от нетерпения, наскоро заполняем оставшиеся поля структуры SENDCMDINPARAMS, как-то: bDriveNumber –— физический номер привода, считая от нуля и буфер для передачи данных.
Внимание
Именно буфер, а не указатель на что-то..[n2k132]
Но ведь мы пока не собираемся записывать никаких данных на диск, верно? Вот и оставим это поле пустым.
Увы! При попытке "скормить" приводу команду, отличную от команд семейства SMART, нас постигает глубокое разочарование, ибо драйвер мини- порта "далеко не дурак" и проверяет содержимое структуры IDEREGS перед ее передачей IDE-приводу. Исключение составляет лишь команда идентификации накопителя (drive)драйва, –— 0xEC, о чем Microsoft прямо и заявляет "There are three IDE commands supported in this driver, ID (0xEC), ATAPI ID (0xA1), and SMART (0xB0). The "subcommands" of the SMART commands (features register values) are limited to the currently defined values (0xD0 through 0xD6, 0xD8 through 0xEF). SMART subcommand 0xD7, write threshold value, is not allowed. Any other command or SMART subcommand will result in an error being returned from the driver. Any SMART command that is not currently implemented on the target drive will result in an ABORT error from the IDE interface" ("Только три IDE-команды поддерживаются этим драйвером: ID (код 0xEC), ATAPI ID (0xA1) и SMART (0xB0). "Подкоманды" базовой команды SMART (передаваемые через feature-регистр), ограничены лишь теми значениями, которые специфицированы на настоящий момент: от 0xD0 до 0xD6 и от 0xD8 до 0xEF. Использование подкоманды с кодом 0xD7, записывающей пороговое значение SMART, заблокировано.
Любые другие команды и подкоманды будут игнорироваться драйвером, и возвращать сообщение об ошибке. Любые SMART-команды, что не реализованы на текущий момент, в целевом приводе будует возвращать ABORT-ошибку").
Кажется, что это полный провал, но нет! Ведь эту проверку в принципе можно и отключить! Давайте дизассемблируем драйвер ATAPI.SYS (листинг 1.4.22), отвечающий за проверку передаваемых IDE-команд на соответствие принадлежности к "белому" списку, и посмотрим, что мы можем сделать.
Листинг 2.1.4.2322. Фрагмент дизассемблерного листинга драйвера ATAPI.SYS, отвечающий за проверку передаваемых IDE-команд на соответствие принадлежности к "белому" списку.
.text:00013714 aScsidisk db 'SCSIDISK',0 ; DATA XREF: SCSI_MINIPORT+CCvo
; вот она наша сигнатура ^^^^^^^^
;
.text:000137DF
.text:000137DF loc_137DF: ; CODE XREF: SCSI_MINIPORT+B5^j
.text:000137DF mov [edi], ebx
.text:000137E1 mov eax, [ebx+18h]
.text:000137E4 push 8 ; длина сравниваемой строки
.text:000137E6 add eax, 4
.text:000137E9 push offset aScsidisk ; эталонная сигнатура
.text:000137EE push eax ; сигнатура, переданная приложением
.text:000137EF call ds:RtlCompareMemory; сигнатуры совпадают?
.text:000137F5 cmp eax, 8
.text:000137F8 jnz loc_13898 ; нет, не совпадают, сваливаем отсюда
.text:000137F8
.text:000137FE mov esi,[ebx+18h]
.text:00013801 mov eax,[esi+10h] ; извлекаем ControlCode
.text:00013804 cmp eax, 1B0500h ; IOCTL_SCSI_MINIPORT_SMART_VERSION
.text:00013809 jz loc_1389F ; à обработка …SMART_VERSION
.text:0001380F mov ecx, 1B0501h ; IOCTL_SCSI_MINIPORT_IDENTIFY
.text:00013814 cmp eax, ecx ;
.text:00013816 jz short loc_1382D ; à
обработка …IDENTIFY
.text:00013818 jbe short loc_13898 ; IF ControlCode < IDENTIFY THEN на выход
.text:0001381A cmp eax, 1B050Ah ; IOCTL_SCSI_MINIPORT_ENABLE_DISABLE…
.text:0001381F ja short loc_13898 ; IF ControlCode > ENABLE_DISAB… на выход
.text:00013821 push ebx ;
.text:00013822 push edi ;
.text:00013823 call sub_12412 ; обрабатываем остальные SMART-команды
.text:00013828 jmp loc_1393E
.text:0001382D ; -----------------------------------------------------------
.text:00012412 sub_12412 proc near ; CODE XREF: SCSI_MINIPORT+106vp
…
.text:00012433 cmp [ebp+var_1E], 0B0h ; SMART-command
.text:00012437 jnz loc_12633 ; если это не SMART, то выходим
.text:00012437 ; отсюда начинаются проверки
.text:0001243D movzx eax, [ebp+var_1C]
.text:00012441 mov eax, [ebx+eax*4+0B0h] ; загружаем Drive/Head-регистр в EAX
.text:00012448 test al, 1 ; сравниваем младший бит AL с единицей
.text:0001244A jz loc_1262F ; если младший бит равен нулю, выходим
.text:00012450 test al, 2 ; сравниваем следующий бит AL с единицей
.text:00012452 jnz loc_1262F ; если он не равен нулю, то выходим
.text:00012458 mov al, [ebp+var_24] ; загружаем Feature-регистр в AL
.text:0001245B cmp al, 0D0h ; это SMART READ DATA?
.text:0001245D mov [ebx+0CCh], al
.text:00012463 jz loc_12523 ; если да, то переходим к его обработке
.text:00012469 cmp al, 0D1h ; это Obsolete?
.text:0001246B jz loc_12523 ; если да, то переходим к его обработке
.text:00012471 cmp al, 0D8h ; это SMART ENABLE OPERATIONS?
.text:00012473 jz short loc_12491 ; если да, то переходим к его обработке
.text:00012475 cmp al, 0D9h ; это SMART DISABLE OPERATIONS?
.text:00012477 jz short loc_12491 ; если да, то переходим к его обработке
.text:00012479 cmp al, 0DA ; это SMART RETURN STATUS?
.text:0001247B jz short loc_12491 ; если да, то переходим к его обработке
.text:0001247D cmp al, 0D2h ; это SMART ENBL/DSBL ATTRIBUTE AUTOSAVE?
.text:0001247D cmp al, 0D2h ; процессор, ты не ошибся в натуре?!
.text:0001247F jz short loc_12491 ; если да, то переходим к его обработке
.text:00012481 cmp al, 0D4h ; это SMART EXECUTE OFF-LINE IMMEDIATE?
.text:00012483 jz short loc_12491 ; если да, то переходим к его обработке
.text:00012485 cmp al, 0D3h ; это SMART SAVE ATTRIBUTE VALUES?
.text:00012487 jz short loc_12491 ; если да, то переходим к его обработке
.text:00012489 cmp al, 0DBh ; это SMART ENABLE OPERATIONS?
.text:0001248B jnz loc_12633 ; если нет, то сваливаем
.text:00012491
.text:00012491 loc_12491: ; CODE XREF: sub_12412+61^j
.text:00012491 ; отсюда начинается обработка команд
.text:00012491 ;
.text:00012491 push 1
.text:00012493 pop eax
.text:00012494 cmp ds:0FFDF02C0h, eax
.text:0001249A jnz short loc_124A5
.text:0001249C cmp dword ptr [ebx+4], 640h
.text:000124A3 jz short loc_124A7
.text:000124A5
.text:000124A5 loc_124A5: ; CODE XREF: sub_12412+88^j
.text:000124A5 xor eax, eax
.text:000124A7
.text:000124A7 loc_124A7: ; CODE XREF: sub_12412+91^j
.text:000124A7 ; отсюда начинается запись в порт!
.text:000124A7 ;
.text:000124A7 mov esi, ds:WRITE_PORT_UCHAR
.text:000124AD test al, al
.text:000124AF jz short loc_124C0
.text:000124B1 mov al, [ebp+var_1C]
.text:000124B4 shr al, 1
.text:000124B6 and al, 1
.text:000124B8 push eax
.text:000124B9 push 432h
.text:000124BE call esi ; WRITE_PORT_UCHAR
Таким образом, чтобы разрешить драйверу отправлять IDE-приводу любые команды, мы должны удалить условный переход, расположенный по адресу 0х12437 (в листинге он выделен жирным шрифтом и обведенвзят прямоугольникомв квадратик) на безусловный переход, передающий управление на команду записи по адресу 0x12491. Только не забудьте после модификации драйвера скорректировать его контрольную сумму, что можно сделать, например, с помощью утилиты EDITBIN.EXE, входящей в состав Microsoft Visual Studio, иначе Windows NT наотрез откажется загружать такой "взломанный"хакнутый драйвер.
Разумеется, такую операцию допустимо проделывать только на своем собственном драйвере, поскольку всем остальным навряд ли понравится "дыра", проделанная в системе безопасности! К тому же, распространение модифицированного драйвера ATAPI.SYS вопиющим образом нарушает авторское право самой Microsoft со всеми вытекающими отсюда последствиями. Тем не менее, ваше приложение может безбоязненно "падчить" (patch) драйвер ATAPI.SYS непосредственно на компьютерах пользователей, естественно, запрашивая у них подтверждение на правомерность такой операции (или, на худой конец, должно быть выполненопросто упоминиеая этогот аспекта в сопроводительной документации).
В любом случае, данный способ взаимодействия с приводом не стоит сбрасывать со счетов, поскольку это значительно усложняет взлом защиты, созданной на его основе. Ведь далеко не все хакеры осведомлены о тонкостях управления мини- портом и потому с вероятностью близкой к единице "сядут в глубокую лужу", если, конечно, не упадут в яму глубокого информационного вакуума.
Пример программы, приведенной в листинге 1.4.23ниже, как раз и демонстрирует передачу ATA-команд IDE-приводу посредствомчерез драйвера мини- порта.
Листинг 2.1.4.2423. [/etc/SCSI.mini-port.c] Пример программы, демонстрирующий технику взаимодействия со SCSI мини- портом интерфейса SCSI
int ATAPI_MINIPORT_DEMO(void)
{
int a;
HANDLE h;
char *buf;
int LU = 0;
DWORD returned;
int controller;
char ScsiPort [16];
char buffer [sizeof (SRB_IO_CONTROL) + SENDIDLENGTH];
SRB_IO_CONTROL *p = (SRB_IO_CONTROL *) buffer;
SENDCMDINPARAMS *pin = (SENDCMDINPARAMS *) (buffer + sizeof (SRB_IO_CONTROL));
// перебираем оба IDE-контроллера в цикле
for (controller = 0; controller < 2; controller++)
{
// формируем ScsiPort для каждого из котроллеров
sprintf (ScsiPort, "\\\\.\\Scsi%d:", controller);
// открываем соответствующий ScsiPort
h= CreateFile (ScsiPort,GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0,0);
if (h == INVALID_HANDLE_VALUE) { // ЕСЛИ ПРОИЗОШЛА ОШИБКА - СВАЛИВАЕМ
printf("-ERR:Unable to open ScsiPort%d\n",controller);return -1;
}
// перебираем оба устройства на каждом из IDE-контроллеров
for (LU = 0; LU < 2; LU++)
{
// инициализируем входной буфер
memset (buffer, 0, sizeof (buffer));
// ПОДГОТАВЛИВАЕМ СТРУКТУРУ SRB_IO_CONTROL,
// предназначенную для драйвера мини- порта
p -> Timeout = 10000; // ждать до черта
p -> Length = SENDIDLENGTH; // макс. длина
p -> HeaderLength = sizeof (SRB_IO_CONTROL); // размер заголовка
p -> ControlCode = IOCTL_SCSI_MINIPORT_IDENTIFY;
// ^^^ код команды, посылаемой драйверу
// сигнатура. для ATAPI.SYS это "SCSIDISK"
strncpy ((char *) p -> Signature, "SCSIDISK", 8);
// ПОДГОТАВЛИВАЕМ СТРУКТУРУ SENDCMDINPARAMS,
// содержащую ATA-команды, передаваемые IDE-приводу
pin -> bDriveNumber = LU;
pin -> irDriveRegs.bCommandReg = IDE_ATA_IDENTIFY;
// ПОСЫЛАЕМ УПРАВЛЯЮЩИЙ ЗАПРОС ДРАЙВЕРУ МИНИ- ПОРТА
if (DeviceIoControl (h, IOCTL_SCSI_MINIPORT, buffer,
sizeof (SRB_IO_CONTROL) + sizeof (SENDCMDINPARAMS) - 1,
buffer, sizeof (SRB_IO_CONTROL) + SENDIDLENGTH, &returned, 0))
if (buffer[98]!=0)
{// в ответ нам возвращается строка с идентификационным
// именем IDE-привода, которую мы и выводим на экран
for (a = 98; a < 136; a+=2 )
printf("%c%c",buffer[a+1],buffer[a]);
printf("\n");
}
}
CloseHandle (h); // закрыть дескриптор данного SCSI мини- порта
}
return 0;
}
Доступ через SMART-драйвер. (задел на будущее)
Доступ посредствомчерез SCSI-порта
Как уже говорилось ранеевыше (см. разд. "Доступ посредствомчерез SPTI" этой главы), независимо от физического интерфейса дискового накопителя (SCSI или IDE) мы можем взаимодействовать с ним посредствомчерез унифицированногоый SCSI-интерфейса. Другими словами, драйвер конкретного устройства (и привода CD-ROM, в частности) полностью абстрагирован от особенностей реализации шинного интерфейса данного устройства. Даже если завтра появится накопители, работающие через инфракрасный порт, драйвер CDROM.SYS ничего об этом не "узнает" и будет по прежнемупо-прежнему управлять ими через SCSI-порт.
Даже если на вашем компьютере не установлено ни одного SCSI-контролера, пара-тройка вполне работоспособных SCSI-портов у вас обязательно есть. Конечно, это виртуальные, а не физические порты, но с точки зрения программного обеспечения они выглядят точь-в-точь как настоящие. Попробуйте с помощью функции CreateFile отрыть устройство "\\.\SCSI0:", и оно успешно откроется, подтверждая наличие существования виртуальных SCSI-портов (только не забудьте про двоеточие на конце). Посылая определенные IOCTL-команды SCSI-порту, мы можем управлять подключенным к этому порту физическим или виртуальным устройством. Да! Между SCSI-портом (виртуальным) и интерфейсной шиной (физической) расположен еще один уровень абстракции, занимаемый SCSI-мини- портом интерфейса SCSI, который, собственно, и "отвязывает" драйвер SCSI-порта от конкретного физического оборудования (подробнее см. разд. "Доступ посредствомчерез SCSI мини- порта SCSI" этой главы).
Естественно, прежде чем посылать IOCTL-команды в SCSI-порт, неплохо бы узнать, какое именно оборудование к этому порту подключено. Существует множество способов решения этой проблемы: от послать устройству команду идентификации IOCTL_SCSI_GET_INQUIRY_DATA (см. исходный текст демонстрационного примера в NT DDK ""NTDDK\src\storage\class\spti"), и тогда оноо (устройство) среди прочей информации сообщит нам, как его наименованиезовут (например,типа "PHILIPS CDRW2412A"), до заглянуть в таблицу объектов, чем мы сейчас и займемся.
В состав NT DDK входит утилита objdir.exe, которая, как и следует из ее названия, позволяет отображать содержимое дерева объектов в виде каталогадиректории. Устройства, доступные для открытия функцией CreateFile, хранятся в каталоге с довольно нелепым именем "\DosDevices\", глядя на которое можно подумать, что оно содержит имена устройств, видимых из-под MS-DOS, котороюкоторую Windows NT вынуждена эмулировать для сохранения обратной совместимости. На самом же деле этот каталог активно используется подсистемой win32-подсистемой операционной системы Windows NT, и всякий раз, когда функция CreateFile обращается к тому или иному логическому устройству (например, пытается открыть файл "C:\MYDIR\myfile.txt"), подсистема Win32 обращается к каталогу "\DosDevices\" чтобы выяснить, с каким именно внутренним устройством это логическое устройство связано. Внутренние устройства видны лишь из-под Native-NT, а для всех ее подсистем они лишены всякого смысла. В частности, диск "С:" под Native-NT именуетсязовется как "\Device\HarddiskVolume1", а полный путь к файлу myfile.txt выглядит так: "\Device\HarddiskVolume1\MYDIR\myfile.txt". Только не пытайтесь "скормить" эту строчку функции CreateFile –— она скорее "поперхнется", чем "поймет", что же от нее хотят.
Таким образом, каталог "\DosDevices\" служит своеобразным связующим звеном между подсистемой win32 и ядром системы Windows NT. Вот и давайте, в плане "возращения к нашим баранам", посмотрим с каким native-устройством ассоциировано логическое устройство с именем "SCSI". Запустив утилиту objdir с ключом "\Dos\Devices" и не забыв перенаправить весь вывод в файл ("objdir \DosDevices | MORE" –— как альтернативный результат), мы среди "моря" прочей информации обнаружим следующие строки (при отсутствии DDK можно воспользоваться отладчиком Soft-Ice в котором для достижения аналогичного результата следует набрать команду "objdir \??", –— именно так! Здесь имеется два знака вопроса, поскольку каталогдиректория \DosDevices на самом деле никакойая не каталогдиректория, а символическая ссылка на каталогдиректорию \?? или, если так угодно, ее ярлык).
В листинге 1.4.15 показан пример взаимосвязи SCSI-устройств с native-устройствами.:
Листинг 2.1.4.15. Взаимосвязь логических SCSI-устройств с native-NT устройствами
Scsi0: SymbolicLink - \Device\Ide\IdePort0
Scsi1: SymbolicLink - \Device\Ide\IdePort1
Scsi2: SymbolicLink - \Device\Scsi\axsaki1
Оказывается, устройства Scsi0: и Scsi1: представляют собой ни что иное, как символические ссылки на IDE-порты с номерами 0- и 1- соответственно. Впрочем, устройства с именами IdePort0 и IdePort1 не являются IDE-портами в физическом смысле этого слова. Это виртуальные SCSI-порты, создаваемые драйвером ATAPI.SYS в процессе его инициализации. Он же создает символические связи с устройствами с именами "\DosDevices\SCSI0:" и "\DosDevices\SCSI1:" к ним, а также ярлыки "\Device\ScsiPort0" и "\Device\ScsiPort1", недоступные подсистеме win32, но предназначенные для внутреннего использования исключительно на уровне драйверов. Разумеется, драйвер ATAPI.SYS не только создает все ранее вышеперечисленные устройства, но и обслуживает их, предоставляя драйверам более высоких уровней унифицированный интерфейс для взаимодействия с установленным оборудованием.
А вот устройство с именем "Scsi2:" ни с какими физическими шинами вообще не связно, и к соответствующему ему SCSI-порту подключен виртуальный привод CD-ROM, создаваемый программой Alcohol 120%, а точнее ее драйвером –— AXSAKI.SYS! ДрайвераДрайверыа высокого уровня (в частности, драйвер CDROM.SYS), не заподозрив никакого подвоха, будут работать с виртуальным диском точно так же как и с настоящим, что, собственно, и не удивительно, т. к. концепция SCSI-порта обеспечивает независимость драйверов верхнего уровня от особенностей оборудования, с которым они, с позволения сказать, "работают". Именно поэтому под Windows NT так легко реализуются эмуляторы физических устройств!
Кстати, на счет авторов программы Alcohol 120%.
Посмотрите, что удается обнаружить При ее дизассемблировании драйвера AXSAKI.SYS (программы Alcohol 120%) в листинге обнаруживаются нецензурные выражения.:
Листинг 2.16. Фрагмент дизассемблерного листинга драйвера AXSAKI.SYS
.text:000239EC aDf394b_tmp db 'df394b.tmp',0
.text:000239F7 a08lx_256 db '%08lx.256',0
.text:00023A01 a08lx_016 db '%08lx.016',0
.text:00023A0B aGandoniEbanie_ db 'ГАНДОНЫ ЕБАНЫЕ!_SetVectors_If32@16',0
.text:00023A2E a0x02x0x02x0x02 db '0x%02X, 0x%02X, 0x%02X, ',0
.text:00023A47 aLaunchingProdu db 'Launching Product',0
.text:00023A59 aSAfjklIwww2312 db '%s afjkl;iwww23120x%s%sas%s%ss%',0
Управлять SCSI-устройствами можно и с прикладного уровня посредствомчерез интерфейса STPI-интерфейс, однако вместо буквенного имени привода следует задавать имя SCSI-порта, к которому этот привод подключен. Основное достоинство такого способа управления заключается в том, что для взаимодействия с приводом совершенно необязательно обладать правами администратора! Привилегий простого смертного пользователя будет более чем достаточно. К тому же, прямая работа со SCSI-портом несколько производительнее взаимодействия с устройством через длинную цепочку драйверов верхнего уровня многочисленных фильтров, окружающих их.
Однако все попытки передачи SRB-блока посредствомчерез SCSI-порта заканчиваются неизменной ошибкой. Следующий код (листинг 1.4.16) наотрез отказывается работать. Почему?
Листинг 2.1.4.1617. Пример неправильной работы с виртуальным SCSI-портом
// получаем дескриптор SCSI-порта
hCD = CreateFile ("\\\\.\\SCSI1", GENERIC_WRITE|GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE,0,OPEN_EXISTING,0,0);
// ФОРМИРУЕМ SRB-блок
…
// ОТПРАВЛЯЕМ SRB-блок непосредственно на SCSI-порт
status = DeviceIoControl(hCD, IOCTL_SCSI_PASS_THROUGH_DIRECT, &srb,
sizeof(SCSI_PASS_THROUGH), &srb, 0, &returned, FALSE);
Зарубежные телеконференции буквально кишат вопросами на этот счет, –— у одних этот код исправно работает, а других –— нет (и их большинство).
А ответ, между тем, находится в DDK (если, конечно, читать его сверху вниз, а не наискосок по диагонали). Вот, пожалуйста, цитата из раздела 9.2 SCSI Port I/O Control Codes: "If a class driver for the target type of device exists, the request must be sent to that class driver. Thus, an application can send this request directly to the system port driver for a target logical unit only if there is no class driver for the type of device connected to that LU"[5]
("Если класс-драйвер для целевого устройства установлен, управляющие запросы должны посылаться класс-драйверу, но не самому порту устройства. Таким образом, приложения могут посылать непосредственные запросы драйверу системного порта для целевых логических устройств только если класс-драйвер для соответствующего типа устройств, подключенных к данному LU[Y120] [n2k121] , не установлен"). В переводе на нетехнический язык, непосредственное управление портом с прикладного уровня возможно для тех и только тех
устройств, чей класс-драйвер не установлен. Скажем, если вы подключили к компьютеру какую-то нестандартную "железяку", то управлять ей напрямую посредствомчерез SCSI-порта вполне возможно (ведь класс-драйвера для нее нет!). Но приводы CD-ROM, про которые мы собственно и говорим, –— совсем иное дело! Класс-драйвер для них всегда установлен и потому операционная система всячески препятствует прямому взаимодействую с оборудованием посредствомчерез SCSI-порта, поскольку это единственный надежный путь избежать конфликтов.
Выходит, доступ к приводам посредствомчерез SCSI-порта невозможен? И так, и не так! Прямой доступ к SCSI-порту действительно блокируется системой, но та же самая система предоставляет возможность управления устройством посредствомчерез мини-порта интерфейса SCSI-мини порт. Мини- порт? Что это такое?! А вот воб этом мы сейчас и разберемсярасскажем!
Рис. 2.1.4.65. 0х036 Архитектура подсистемы ввода/вывода в Windows NT[Y122]
Доступ посредствомчерез 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 |
Зарезервировано |
Краткое описание стандартных 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 соответственно.
Дважды одинаковый трек
Стойкая, несложная в реализации, хорошо совместимая с оборудованием и при всем этом в высшей степени элегантная защита. Все, что нам требуется сделать так это – изменить номер второго трека на первый, в результате чего на диске образуются два трека с идентичными номерами, но совершенно различным содержимым (не забудьте так же скорректировать указателиpoint'ы 0xA0 или /0xA1 и принудительно отключить восстановление субканальных данных!).
Защищенный диск нормально читается на всех известных мне приводах, однако не копируется ни одним доступным мне копировщиком (за исключением, моих собственных копировщиков разумеется). Два "первых" трека смотрятся довольно забавно (рис. 6.17см… ) и не создают никаких проблем ни для самого привода, ни для операционной системы. Микропрограмма приводов обычно ищет лишь первый трек, (который по наивному мнению некоторых разработчиков всегда имеет номер "один"), а номера всех остальных —– игнорирует. Операционная система, (а, точнее, – файловая система лазерных дисков,) так же адресует треки не по их номерам, ано по абсолютным секторным адресам, поэтому номера всех треков, кроме первого, как бы выпадают из поля зрения компьютера. Разумеется, сказанное относиться только к дискам с данными.
![](image/dvazhdy-odinakovyj-trek_1.gif)
Рис. 6.17. унок 12 0x069 Защищенный диск, содержащий два первых трека
Теоретически, копирование таких дисков не должно вызывать каких- либо проблем. Действительно, содержимое треков прекрасно читается на секторном уровне, номера треков в абсолютной адресации вообще не участвуют и грамотно спроектированного копировщику всего-то и остается: прочитать TOC и извлечь с диска содержимое всех его треков, не взирая на их номера. Конечно, если недальновидный разработчик решил помещать данные трека N в ячейку массива с индексом N, то наличие двух (или более) треков с идентичными номерами "разобьют" этот хлипкий алгоритм в пух и прах. Но…
Штатные копировщики со всем естеством своей штатной натуры копировать такие диски на отрез отказываются, ссылаясь на "Invalid disk" (Stomp Record Now!) и [Y176] "…." (Ahead Nero).
EasyCD Creator
Программа Easy CD Creator обращается к приводу непосредственно по его "родному" имени (в моем случае это "CDR4_2K"), а затем открывает устройство "MbDlDp32", которое сам драйвер CDR4_2K, собственно, и регистрирует.
Следовательно, программа Easy CD Creator работает с диском черепосредствомз своегой собственногоый драйвера и, чтобы разобраться с ним, нам потребуется:
9. а) Дизассемблировать драйвер CDR4_2K и проанализировать, каким IOCTL-кодам какие действия драйвера соответствуют.;
10. б) Отследить все вызовы функции DeviceIoControl (просто поставьте на нее условную точку останова, всплывающую при передаче "своего" дескриптора, возращенного функцией CreateFileA("\\\\.\\CRDR_2K", …) и CreateFileA("\\\\.\\MbDlDp32", …).
Оформив последовательность IOCTL-вызовов в виде импровизированной программы, мы сможем воссоздать протокол взаимодействия с диском и найти защиту (если она там есть).
F1-, F2- и F3-фреймы, CIRC кодирование
Следующий "производственный" цикл начинается с того, что наскоро отскремблированный сектор "нарезается" на 24-байтовые "ломтики" данных, называемые F1-?фреймами (F1-?frames). Путем несложных математических подсчетов можно заключить, что одного 2352байтового сектора аккурат хватает на 98 F1-фреймов.
F1-фрейм.
Структура F1-фреймов чрезвычайно проста: каждый фрейм состоит из 24 байтов (12 слов), пронумерованных от 0 до 23, последовательно отображаемых на соответствующие ячейки сектора (рис. 1.18,0x042 а). Причем, границы фреймов и секторов не обязаны совпадать и сектор может начинаться с любой из следующих позицией первого фрейма: 0, 4, 8, 12, 16 или 20 (см. рис. 1.18,0x042 бснизу). Стартовая позиция сектора в F1-фрейме нигде не храниться (да и где ее здесь хранить-то?), вместо этого начало сектора опознается по синхрогруппе, которую трудно не заметить!
![](image/index-image020.jpg)
Рис. 1.18. Схема отображения секторов на фреймы
Стандарт довольно туманно описывает процесс отображения секторов на фреймы, но дает понять, что за концом одного сектора непосредственно следует начало другого (Byte 2.351 of a Sector is immediately followed by byte 0 of the next Sector), следовательно изменение стартовой позиции сектора не "заворачивает" хвост сектора в начало первого фрейма, а переносит его на следующий фрейм. Короче говоря, стартовая позиция сектора не равна нулю, то каждый 49-й фрейм содержит байты сразу двух секторов (как нетрудно сообразить это будут первый и последний фреймы сектора, а, поскольку, в одном секторе содержится 98 фреймов, то 98/2 == 49)!
Изменение стартовой позиции начального байта сектора во фрейме приводит к значительным изменениям его DSV (см. разд. "Синхрогруппы, объединяющие биты и DSV???DSV" этой главы), в результате чего у записывающей аппаратуры появляется возможность "нормализации" секторов с неприлично высоким значением DSV. Микропрограмма привода должна выбирать стартовую позицию с наименьшим возможным DSV или, по крайней мере, следить за тем, чтобы величина DSV не выходила за рамки предельно допустимых значений. К сожалению, подавляющее большинство пишущих приводов CD-R'ов "писцов" бытового класса слишком тупы для подобной задачи и всегда начинают фрейм с нулевого байта сектора.
Как следствие, —– копия диска на фреймовом уровне может очень значительно отличаться от своего оригинала. Несмотря на то, что программно определить стартовую позицию невозможно (штатные приводы CD-ROM отказываются "разглашать" эту информацию), разработчикам защитного механизма ничего не стоит сформировать "слабый" (weak) сектор, имеющий крайне высокое DSV при стартовой позиции 0, но вполне нормальное —– при всех остальных (см. разд. "Защиты, основанные на «слабых» секторах???weak" главы 9). Скопировать такой сектор обычным пишущим приводом"писцом" практически невозможно, —– лишь немногие модели приводов CD-ROM приводов окажутся способными прочесть сектор с высоким DSV. Мой ASUS-50x как будто бы читает такие сектора, но нестабильно и не на всех дисках. Причем, возможности "ручного" выбора стартовой позиции у известных мне пишущих приводовписцов нет (во всяком случае, в стандарте она отсутствует, да и не спустились еще такие записывающие приводыписцы на такой низкий уровень). Можно, конечно, схитрить и умышленно исказить несколько байт сектора, не трогая при этом корректирующие коды (даже незначительная модификация исходных данных ведет к чудовищному изменению DSV), так что микропроцессорная начинка привода самостоятельно восстановит первоначальное содержимое искаженных данных "налету". Однако защита (если она, конечно, не совсем дура) может запросто разоблачить такую грубую подделку от оригинала, —– ведь при чтении сектора в "сыром" виде все махинации с корректирующими кодами сразу же становятся видны!
В тоже время большинство пишущих приводов CD-RW писцов (если не все они) тщательно следят за значением DSV и корректно выбирают стартовую позицию. Что ж! Все логично, —– контрастность CD-RW носителей и так до безобразия низка, а потому и требования к величине DSV здесь значительно жестче, чем у CD-R дисков. Отсюда: если защищенный диск не копируется на CD-R, попробуйте скопировать его на CD-RW… нет, не на CD-RW приводе, но на приводе CD-RW приводе на одноименнуюCD-RW болванку, ибо ряд пишущих приводов CD-RW писцов (Plextor, PHILIPS) при записи CD-R болванок всегда начинают фрейм с нулевого байта сектора, но вместе с этим грамотно определяют стартовую позицию сектора при записи на болванку типа CD-RW! (маразм, конечно, но против него не попрешь).
![](image/index-image020.jpg)
Рисунок 18 0х047 Схема отображения секторов на фреймы
Порядок следования байт в секторе отличеный от порядка следования байт в F1-фрейме. То есть, попросту говоря, при отображении содержимого сектора на фрейм, четные и нечетные байты меняются местами (см. рис. 1.190x048). Такое перемешивание призвано уменьшить пагубное влияние дефектов диска, затрагивающих оба соседних байта сразу.
![](image/index-image021.jpg)
Рис. 1.19.унок 19 0х048 Схема перемешивания байтов в F1- фрейме
Программная реализация разбиения сектора на фреймы приведена в следующем листинге 1.8 (здесь: sector —– указатель на исходный сектор, F1_frames —– массив из 98 фреймов по 24 байта каждый).:
Листинг 1.8. Пример, демонстрирующий технику формирования F1-фреймов (рассмотрен случай, когда границы фреймов и секторов совпадают)
/* generate F1 frames */
for (a = 0; a < 98; a++)
{
for (b = 0; b < 24; b++)
{
F1_frame[a][b]=((*sector&0xff00ff00UL)>>8)|((*sector&0x00ff00ffUL)<<8);
}
}
F2-фрейм.
Недавно [n2k49]Свежесформированные F1-фреймы поступают на вход специального кодера (Cross Interleaved Reed-Solomon Coder, по своим первым буквам так же называемый CIRС- кодером), где к их 24-байтам добавляют еще 8 байт контрольной суммы Рида-Соломона, в результате чего на выходе кодера образуются F2-фрйемы в 32 -байта длинной.
Содержимое байт, слагающих F1-фрейм на битовом уровне остается неизменным ("The bit pattern of each of the 24 8-bit bytes of an F1-Frame remains unchanged"), но сами эти байты перераспределяются по 106 F2-фреймам, в результате чего F1-фреймы как бы "размазываются" вдоль спиральной дорожки, становясь менее чувствительными к радиальным царапинам диска (и вообще всяким локальным дефектам).
Перемешивание достигается за счет так называемых линий задержки (delay line) и осуществляется по следующей схеме (см. рис. 1.200х049). Первая линия задержки (first delay section) "заглатывает" поступающие на ее вход F1-фреймы уже разбитые на 12 двухбайтовых слов, младший и старший байты которых условно обозначены литерами А и B соответственно, а сами слова последовательно пронумерованы от W12n до W12n+11. Таким образом, первый байт фрейма имеет номер "W12n,A", а последний —– "W12n+11,B".
![](image/index-image022.jpg)
Рис. 1.20.унок 20 0х049 Процесс кодирования байт
Первая линия задержки расщепляет содержимое фрейма на две группы слов, одна из которых (W12n+2, W12n+3, W12n+6, W12n+7, W12n+10, W12n+11) беспрепятственно проходит на выход, а другая (W12n+0, W12n+1, W12n+4, W12n+5, W12n+8, W12n+9) принудительно удерживается на время обработки двух последующих F1-фрймов. Слова с номера W12n+1…W12n+10 тщательно перемешивается по строго определенной схеме, которую легче изобразить графически (см. рис. 1.20 0x049), чем описать словесно описать.
Перемешенные слова поступают на вход C21--кодера
(encoder С21 encoder[Y50] [n2k51] ), где к ним добавляют четыре байта четности, рассчитанные по кодам Рида-Соломона и последовательно пронумерованные от Q12n+0 до Q12n+3.
Затем, слова, обогащенные Q- байтами четности, попадают на вторую линию задержки (second delay lines), где они задерживаются на промежуток от 1D до 27D времени обработки F1-фрймов, где D равно 4.
ВышедшиеОткинувшиеся "на свободу" слова направляются в очередной кодер Рида-Соломона, условно обозначаемый "C2", где к ним добавляют четыре байта четности, последовательно пронумерованных от Pn+0 до Pn+3. Подобная двухуровневая схема избыточного кодирования значительно уменьшает вероятность возникновения неустранимых ошибок, ведь между C1 и C2 кодерами, обрабатываемые данные хорошенько перемешиваются!
Наконец, последняя, третья линия задержки, задерживает все четные байты потока данных на время обработки одного F1-фрейма. Все! С выхода третьей линии задержки сходит новехонький F2-фрейм, состоящий из 32 байт, последовательно пронумерованных от 0 до 32: 24 байт полезных данных, 4 Q-байт четности и 4 P-байта четности. Причем, в 24 байта полезных данных F2-фрейма входят данные множества различных F1-фрймеов! Другими словами говоря, F2-фрейм нельзя рассматривать как F1-фрем с контрольной суммой.
При считывании данных с лазерного диска происходит обратный процесс (см. рис. 1.21 0x050). Сначала считываемые байты проходят через линию задержки, "захватывающую" четные байты на один фрейм, затем они поступают в C1 декодер (С1 decoder), проверяющий истинность контрольной суммы и пытающийся восстановить сбойные байты при необходимости. Далее следует еще одна линия задержки (1D-27D Delay lines) и еще один декодер (C2 decoder), восстанавливающий то, что не удалось восстановить еще предшественнику. Наконец, с выхода последней линии задержки сходят вполне готовые к употреблению F1-фреймы, которые впоследствии собираются в сектора, но о секторах мы уже говорили ранеевыше, так что не будем повторяться.
![](image/index-image023.jpg)
Рис. 1.21.унок 21 0x050 Процесс декодирования байт
При данной избыточности корректирующие коды в состоянии исправлять до двух сбойных байт на каждый 24/28-байтовый "ломтик" исходных данных.
При искажении трех и более байт, декодер способен лишь констатировать сам факт искажения, но бессилен восстановить оригинальное содержимое разрушенных байт. Правда, установить какие именно байты были разрушены все-таки возможно, а, значит, их приблизительное значение может быть определено путем интерполяции. Конечно, для дисков с данными такой способ "восстановления" не пригоден, но для Audio-CD он дает вполне удовлетворительный результат. Собственно, Количество неисправимых ошибок даже на качественных носителях достаточно велико и CD-приводам приходится активно заниматься их интерполяцией, о присутствии которой подавляющее большинство меломанов даже и не подозревают.
Компьютерные приводы CD-ROM'ы первого поколения формально поддерживали аудиодиски и даже ухитрялись качественно проигрывать их, но… при попытке цифрового "грабежа" диска в динамиках стоял сплошной треск, подобный тому, что издается поцарапанным винилом. Что ж, ностальгия —– это прекрасно, но, черт возьми, время пластинок давно прошло и причина их стремительного вытеснения компакт-дисками не в последнюю очередь объясняется тем, что качество их звучания компакт-дисков не падает с течением времени, в то время как пластинки неизбежно уродуются со временем. Теперь же оказывается, что приобретаемые нами компакт- диски уже изуродованные и уже "шипят".
Так происходит потому, что приводыранние CD-ROM'ы первого поколения читали аудиодиски "как есть" и при возникновении неисправимых ошибок не осуществляли никаких попыток самостоятельного исправления сбойных байт. Более того, они не сообщали и номера этих байт, в результате чего прикладное программное обеспечение просто не знало что и с чем следует интерполировать! Если искажение затрагивало младшие биты разряда, то – для человеческого уха оно оставалось практически незаметным (даже для уха меломана), но если сбойный бит угодил в старший разряд, —– такое искажение воспринималось человеком как резкий щелочек, даже если по его ушам потоптался китайский медведь панда.
Вообще-то, сбойный сектор можно было попробовать перечитать несколько раз (вдруг с одной из попыток он прочтетсяпочитается нормально?) или проанализировать считанные данные на предмет поиска и "сглаживания" всех крутых "пиков" и "спадов", но это же все полумера, вы понимаете! Качественный "грабеж" аудио на таких приоводах в принципе невозможен. К тому же некоторые производители дисков, озабоченные проблемой их несанкционированного копирования, стали умышленно вносить на них большое количество неисправимых ошибок, в результате чего в аудио-режиме диск читался нормально (даже на компьютерном приводе), но попытка его "грабежа", равно как и попытка копирования на болванку CD-R болванку терпели неудачу, ибо качество звучания было на редкость удручающим и едва ли могло соперничать с дедушкой граммофоном.
Сейчас, однако, ситуация понемногу начала исправляться и некоторые из современных приводов CD-ROM приводов уже способны возвращать в потоке данных указатели на искаженные биты. На программном уровне это достигается передачей приводу команды BEh (READ CD) c ненулевым значением поля "Error Flags" (оно расположено в 1—-2 битах 9-го байта ATAPI/SCSI-пакета, см. так же демонстрационный пример [/etc/RAW.CD.READ/aspi32.C2.c]). Подробнее об этом можно прочитать в Стандарте на DVD/CD-ROM приводы (http://www.stanford.edu/~csapuntz/specs/INF-8020.PDF
page 143), здесь же мы сосредоточимся не сколько на описании формата полей команды READ CD, сколько на самих C2-указателях (C2- pointers), которые строго говоря, никакими указателями не являются, а представляют собой обычную битовую карту, помещаемую в самый конец возвращаемых приводом данных. Каждому байту исходных данных соответствует "свой" бит C2-pointers бит, анализ значения которого и позволяет однозначно установить: является ли данный байт сбойным или нет. Учитывая, что длина сектора составляет 2352 байта, становится нетрудно рассчитать совокупный размер всех битов C2-pointers битов вместе взятых, который равен 2352/8 = 294 байтам, причем первый байт сектора соответствует первому биту C2-pointers биту (см.
рис. 1.22 0x051).
![](image/index-image024.jpg)
Рис. 1.22.унок 22 0х051 Последовательность фреймов, образующая блок
Узнать поддерживает ли данный конкретный привод такую возможность или нет можно посылкой команды MODE SENSE 10 (5Ah) c кодом страницы (Page Code) равным 2Ah (C/DVD Capabilities and Mechanical Status Page Format) тогда единичное значение 4-го бита 13-го байта возращенных данных укажет на то, что C2 Pointers is Supported и, соответственно, наоборот (см. так же демонстрационный пример ["/etc/RAW.CD.READ/aspi32.cfg.c]"). В частности, мой PHILIPS CDRW 2400 ничего подобного, увы, не поддерживает.
Но довольно о грустном. Лучше вернемся к нашим C1- и C2-декодерам, а точнее, – к методике подсчета количества ошибок. Существует как минимум шесть типов ошибок, по три для каждой из двух стадий исправления: а) одно-символьные (исправимые) ошибки, соответствующие первой стадии восстановления (т. е. исправляемые декодером C1); б) двухх символьные (исправимые) ошибки, соответствующие первой стадии исправления и в) трехсимвольные (уже, увы, неисправимые) ошибки, соответствующие все той же стадии. Аналогичная картина наблюдается и на втором уровне восстановления, относящемуся к декодеру C2. Ошибки принято обозначать заглавной буквой "E" (лат. — от слова Error) с непосредственно примыкающим к ней двухзначным числом, первая цифра которого обозначает число ошибок (1, 2 или 3), а вторая —– стадию исправления (1 или 2). Для большей наглядности все возможные комбинации этих цифр сведены в таблицу (табл.лице 1.2), приведенной ниже.
Таблица 1.2. Условное обозначение различных типов ошибок
Обозначение |
Пояснение |
E11 |
Количест-во одно-символьных (исправимых ошибок) на стадии C1 |
E21 |
Количест-во двух символьных (исправимых ошибок) на стадии C1 |
E31 |
Количест-во трехсимвольных (неисправимых ошибок) на стадии C1 |
E12 |
Количест-во одно-символьных (исправимых ошибок) на стадии C2 |
E22 |
Количест-во двух символьных (исправимых ошибок) на стадии C2 |
E32 |
Количест-во трехсимвольных (неисправимых ошибок) на стадии C2 |
Таблица 2 условное обозначение различных типов ошибок
Трехсимвольные ошибки, неустранимые на стадии C1 (т. е. ошибки E31 ошибки) в большинстве случаев могут быть успешно восстановлены на следующей стадии исправления, однако, одна-единственная ошибка E31 ошибка может вызвать до 30 ошибок E12, ведь между C1- и C2 декодерамдекодерамии данные 160 F1-фреймов тщательно перемешиваются!
Трехсимвольные ошибки, неустранимые на стадии C2 (т. е. ошибки E32 ошибки), свидетельствует о серьезном физическом дефекте поверхности диска, которые (в силу несовершенства технологических процессов) даже на "свежих" дисках отнюдь не так редки, как это может показаться на первый взгляд. Именно поэтому и приходится использовать дополнительные корректирующие коды на дисках с данными (для аудиодисков в этом случае используется интерполяция, но для данных интерполяция бессмысленна). Подробнее об этом можно прочесть в разд.главе "«"Сырые» и «сухие» сектора"Сырые" и "сухие" сектора::ECC/EDC этой главы, здесь же мы не будет возвращаться к уже рассмотренным вопросам.
F3-фреймм.
Когда к вышедшемусошедшему изс CIRC-декодера F2-фрейму добавляют еще один байт служебных данных, называемый контрольным байтом (ControlByte) образуется F3-фрейм, с которым мы уже сталкивались в разд.главе "Кратко о питах, лендах, EFM-словах, фреймовых кадрах и секторахPit'ы, land'ы, EFM-слова, фреймовые кадры и сектора" этой главы. Структура F3-фрейма до предела проста: первым идет контрольный байт, а за ним следуют 32 байта, доставшихся в наследство от F2-фрейма. Контрольный байт содержит в себе восемь бит подкода, которые в свою очередь образуют каналы, простирающиеся в длину на 98 байт, т. е. захватывающие весь сектор целиком, поскольку сектор как раз и состоит из 98 F3-фреймов (подробнее см. разд.главу "Каналы подкода" этой главы).
Структура, образованная 98 F3-фреймами, называется секцией (section) и представляет собой вполне самодостаточную сущность, не привязанную к границам сектора. Позвольте зачитать пару цитат из Стандарта ECMA-130: "These Sections are asynchronous with the Sectors, i.e. there is no prescribed relation between the number of the F1-Frame in which the first byte of a Sector is placed and the number of the F3 -Frame in which the first Control byte of the table is placed" ("Эти секции асинхронны по отношению к секторам, т. е. между номером F1-фрейма, хранящим первый байт сектора и номером F3-фрейма, хранящим первый контрольный байт таблицы, нет жестко заданного отношения"). Так же: "The address of a Sector is recorded in the Sector Header, also as an absolute time. It has no prescribed relation to the addresses of the Sections, because the mapping of a Sector on the Sections during recording is implementation-dependent due to the freedom left in clause 16. Therefore, the address of a Sector is filled in just before the Sector enters the CIRC encoder" ("Адрес сектора, записанный в его заголовке, так же выражается в абсолютном времени.
Он не имеет никакого заранее заданного отношения к адресу секции, поскольку отображение секторов на секции, происходящее в процессе записи последних, есть implementation-dependent, то есть зависит от воли левого мизинца правой пятки разработчикиаа записывающего привода, а, если говорить серьезно, оно в значительной мере обусловлено свободой выбора стартовой позиции начала сектора в F1-фрйеме. Поэтому, адрес сектора заполняется непосредственно перед входом сектора на CIRC-кодер").
Первые байты двух F3-фреймов каждой секции (т. е. первые два контрольных байта секции) обрабатываются особым образом. В то время как остальные байты секции (коих насчитывается 3 .232) преобразуются в 14-битовые EFM-последовательности, непосредственно записываемые на диск, эти два "товарища" замещаются фиксированными синхрогруппами, именуемых SYNC0 (00100000000001) и SYNC1 (00000000010010) соответственно (см. рис. 1.23 0x052).
![](image/f3-frejmm_1.gif)
Рис. 1.23.унок 23 0х052 Устройство секции
Фиктивный трек, совпадающий с подлинным треком
Создание фиктивного трека, совмещенного с подлинным треком, приводит к тому, что длина первого трека обращается в ноль. Почему в ноль, а не в отрицательное число, ведь длина подлинного трека в данном случае равна: &Track2– &Track1 – sizeof(post-gap) – sizeof(pre-gap), что в конкретных цифрах выглядит так: 00:02:00 – 00:02:00 – 00:02:00 – 00:02:00 == –00:04:00 или в переводе в LBA-адреса: – 300.
Все дело в том, что адрес 00:02:00 —– особенный. Трек, начинающийся с этого адреса, не имеет Pre-gap (вернее, не позволяет обрабатывать свою областьй Pre-gap нормальным образом) и потому вычисление фактической длины такого трека происходит в отдельной ветке программы, которая оказывается достаточно интеллектуальной для того, чтобы догадаться не вычитать sizeof(post-?gap) из нулевого значения. Особенности реализаций конкретных копировщиков сейчас нам неинтересны, достаточно лишь знать, что подавляющее большинство из них вычисляют длину первого и всех последующих за ним треков вполне корректно (правда, от проблем копирования защищенного диска это их все равно не избавляет).
Внедренение X-сектора в подлинный трек (см. защиту типа "Шакал" в разд. "Фиктивный трек в Pre-gap подлинного трека" этой главы) приводит к следующему побочному эффекту: попытка чтения X-сектора командой READ CD возвращает ошибку, позицирование головки на X-сектор командой SEEK проходит без ошибок, но и без перемещения самой головки. Чтение заголовка X-сектора командой READ HEADER вновь возвращает ошибку. Забегая вперед отметим, что копия диска, полученная с помощью Clone CDCloneCD читает заголовк и выполняет позиционирование головки без ошибок (головка при этом действительно перемещается), благодаря чему дубликат диска элементарно отличается от оригинала.
При открытии оригинального образа защищенного диска, Clone CDCloneCD обнаруживат лишь второй —– фиктивный —– трек в первой сессии, "в упор не видя" настоящего (см.
листинг 6.27 ниже). К счастью, несмотря на это, "прожиг" диска проходит успешно.
Листинг 6.27. Реакция CloneCD на фиктивный трек, совпадающий с настоящим треком
ИНФОРМАЦИЯ О СЕССИИ 1:
Размер сессии: 4726 Кбайт
Число треков: 2
Track 2: Данные Mode 1, размер: 4726 Кбайт
ИНФОРМАЦИЯ О СЕССИИ 2:
Размер сессии: 3939 Кбайт
Число треков: 1
Track 3: Данные Mode 1, размер: 3939 Кбайт
Листинг 19 реакция Clone CD на фиктивный трек, совпадающий с настоящим треком
Просмотр геометрии диска подтвержает существование двух треков с одинаковыми стартовыми адресами, причем длина первого трека действительно равна нулю, а не отрицательному числу (см. рис. 6.150x102). Еще интереснее то, что тип первого трека определен как "ISO 9660/Joliet", что мягко говоря не совсем соответствует истине. Идентификатор типа файловой системы ISO 9660 содержиться в шестнадцатом, а Joliet —– в семнадцатом секторе трека. При условии, что длина трека равна нулю (а это так и есть!) возникает вполне законный вопрос: какое отношение эти сектора имеют к первому треку? Сдается мне, что процедура идентификации типа файловой системы вообще не проверяет диапазон доступных адресов на переполнение!
![](image/fiktivnyj-trek-sovpadajushhij-s-podlinnym-trekom_1.gif)
Рис. 6.15. унок 10 0x102 Первый и второй трек имют идентичные стартовые адреса в результате чего длина первого трека обращается в ноль
Приводы NEC и TEAC оказались достаточно интеллектуальными, чтобы суметь прочитать такой диск, а вот ASUS к своему стыду увидел лишь первую сессию. Следовательно, на вторую и все последующие сессии закладываться нельзя.
Попытка копирования защищенного диска программой Alcohol 120% Алкоголиком традиционно проходит неуспешно (если режим "попуска ошибок чтения" не включен, Alcohol 120% Алкоголик вообще не хочет копировать такой диск, а в режиме "пропуска ошибок чтения" "врезается" в область Lead-outLead-Out и входит в дурной цикл, не позволяя себя прервать иначе, чем принудительным завершением процесса).Копировщик Clone CDCloneCD, как уже говорилось, обрабатывает фиктивный трек вполне нормально, но удаляет защиту типа "Шакала" из области Post-gap, "благодаря" чему дубликат диска легко отличается от оригинала.
Фиктивный трек в Lead-outLead-Out
Фиктивный трек, размещенный в области Lead-outLead-Out, не препятствует нормальному чтению диска, но серьезно озадачивает штатных копировщиков. Во-первых, длина фиктивного трека, вычисляемая как разность стартовых адресов Lead-outLead-Out и самого этого трека минус sizeof(post-gap), в данном случае выражается отрицательным числом, а мы уже знаем какую головную боль для копировщиков представляет всякая отрицательная величина! Во-вторых, содержимое области Lead-?out в силу его недоступности на сектором уровне, очень легко принять за плохой сектор (badsector)'a со всеми вытекающими отсюда последствиями. В-третьих, предпоследний сектор области Post-gap'a подлинного трека (условно называемый нами X-сектором), по загадочным причинам не обрабатыается ни командой READ CD, ни командой SEEK, ни командой READ HEADER.
Обратимся к pointуказателю'у A2h, хранящему адрес выводной области, и скопируем принадлежащие ему PMin, PSec и PFrame в соответствующие поля фиктивного сектора, увеличив PSec последнего на единицу или любую другую величину, находящуюся в границах области Lead-outLead-Out'a.
Пропустив традиционное ругательство программы Clone CDCloneCD на ненормальную длину второго трека, мы "заливаем" модифицированный нами образ на диск CD-R/CD-RW диск. Как и в предыдущем примере, приводы NEC и TEAC "видят" все, доступные им сессии, а ASUS только первую из них, поэтому на вторую и последующие сессии мы не должны закладыватьсярасчитывать.
Копировщики Stop Record Now! и Ahead Nero откажутся копировать такой диск, а информация окна "Disk iInfo", возращенная последним, окажется некорректнойа (см. рис. 6.100x072). Конкретно: копировщик Ahed Nero не сможет определить режим (mode)MODE и длину третьего трека. Странно! Третий трек вообще не имеет никакого отношения к первым двум и для определения егое атрибутов достаточно просто прочитать TOC.
![](image/fiktivnyj-trek-v-lead-outlead-out_1.gif)
Рис. 6.10. унок 5 0x072 Ahead Nero неправильно определил длину второго —– фиктивного —– трека и, что увидительно, не справилься с определением длины и mode третьего трека, лежащего совсем в другой сессии
Попытка копирования защищенного диска с помощью программы Alchol 120% ни к чему хорошему не приводит. Если галочка флажок "Пропуск ошибок чтения" была сброшена, то на экране появляется сообщение возникает "Ошибка: [05/64/00] – Illegal Mode For This Track" и процесс создания образа аварийно прерывается (см. рис. 6.110x100).
![](image/index-image044.jpg)
Рис. 6.11. унок 6 0x100 Alcohol 120% Алкоголь без пропуска ошибок
Листинг 6.18. Реакция Alcohol 120% на фиктивный трек в области Lead-Out
(режим пропуска ошибок выключен)
01:25:17 Информация о процессоре:
Pentium III (0.18 um) 256KB OnDie L2 Cache (736 MHz)
01:25:17 Дамп диска: (G:) TEAC CD-W552E (1:1)
01:25:18 Режим чтения: Режим RAW
01:25:18 Информация об источнике: Сессия: 2, Трек: 3, Длина: 29.6 MB / 003:22:23
01:25:18 Запись файла-образа: L:\CD-hack\030713_1649.img
01:25:20 Ошибка чтения диска: 2048
01:25:20 Во время дампа диска произошла ошибка!
01:25:20 Ошибка: [05/64/00] - Illegal Mode For This Track
01:25:20 L:\CD-hack\030713_1649.ccd: Запись файла-образа отменена!
Листинг 10 реакция Алкоголя на фиктивный трек в Lead-Out (пропуск ошибок выключен)
Если же опция режим "пропуска ошибок чтения" была включен (листинг 6.19)а, то создание образа как будто бы проходит успешно, пускай не без "пулеметной очереди" сбойных секторов, начинающихся с адреса 2058 и заканчивающихся адресом 2172 (очевидно, что эти сектора принадлежат области Pre-gap/Post-gap области фиктивного трека, тщетно "пытаемой" Alcohol 120%Алкоголем).
Листинг 6.19. Реакция Alcohol 120% на фиктивный трек в области Lead-Out
(режим пропуск ошибок включен)
01:32:11 Информация о процессоре:
Pentium III (0.18 um) With 256 KB On-Die L2 Cache (736MHz)
01:32:11 Дамп диска: (G:) TEAC CD-W552E (1:1)
01:32:12 Режим чтения: Режим RAW , Пропуск ошибок чтения
01:32:12 Информация об источнике: Сессия: 2, Трек: 3, Длина: 29.6 MB / 003:22:23
01:32:12 Запись файла-образа: L:\CD-hack\030713_1649.img
01:32:21 Ошибка чтения диска: 2056
01:32:21 Ошибка чтения диска: 2058
01:32:21 Ошибка чтения диска: 2059
01:32:21 Ошибка чтения диска: 2060
…
01:32:22 Ошибка чтения диска: 2169
01:32:22 Ошибка чтения диска: 2170
01:32:22 Ошибка чтения диска: 2171
01:32:22 Ошибка чтения диска: 2172
01:32:32 L:\CD-hack\030713_1649.ccd: Запись файла-образа завершена!
01:32:32 Дамп диска завершён!
Листинг 11 реакция Алкоголя на фиктивный трек в Lead-Out (пропуск ошибок включен)
На что он надеялся —– неизвестно, но образ, созданный Alcohol 120% Алкоголиком оказывается неработоспособным и выдает ошибку типа "Sector not fount. Cannot read folder contents" (см. рис. 6.12 ниже).:
г=========== Error ===========¬
¦ Sector not found ¦
¦ Cannot read folder contents ¦
¦ Ok ¦
L=============================-
Рис. 6.12. унок 7 Диск, скопированный Alcohol 120% Алкоголем становится нечитаемымбельным и при попытке просмотра его содержимого операционная система начинает "жутко ругаться" материться
Зато Clone CDCloneCD копирует такой диск без каких- либо ошибок и его дубликат оказывается вполне работоспосбен, однако, он все-таки отличается от оригинала. Чтение X-сектора командой READ CD по-прежнему возращает ошибку, но вот позиционирование головки командой SEEK и чтение заголовка командой READ HEADER выполняется нормально (напоминаем, что X-сектор оригинального диска не обрабатывался ни одной из этих команд!).
Листинг 6.20. Попытка чтения X-сектора оригинального диска командой READ CD дает ошибку Sense Key == 3 MEDIUM ERROR (выше), дубликат диска, полученный CloneCD ведет себя аналогично (ниже)
>cd_raw_read.exe 1.1 2056 1 >cd_raw_read.exe 1.1 2056 1
-ERR:F0 00 03 00 00 00 00 0A 00 00 00 00 11 00 -ERR:F0 00 03 00 00 00 00 0A 00 00 00 00 11 00
>cd_raw_read.exe 1.1 2056 1
-ERR: F0 00 03 00 00 00 00 0A 00 00 00 00 11 00
Листинг 6.21. Попытка чтения заголовка оригинального диска командой READ HEADER дает ошибку Sense Key == 5 ILLEGAL REQUEST (выше), в то время как дубликат диска, полученный CloneCD обрабатывается нормально (ниже)Листинг 12 попытка чтения x-сектора оригинального диска командой READ CD дает ошибку Sense Key == 3 MEDIUM ERROR (слева), дубликат диска, полученный Clone CD ведет себя аналогично (справа)
>read.header.exe 1.1 2056 2056 >read.header.exe 1.1 2056 2056
READ HEADER (44h) SCSI/ATAPI commad demo by KK READ HEADER (44h) SCSI/ATAPI commad demo by KK
-ERR:f0 00 05 00 00 00 00 0a 00 00 00 00 64 00 LBA:0808h
--> MSF:00:1D:1F (MODE-1 [L-EC symb])
>read.header.exe 1.1 2056 2056
READ HEADER (44h) SCSI/ATAPI commad demo by KK
LBA:0808h
--> MSF:00:1D:1F (MODE-1 [L-EC symb])
Попытка позиционирования на X-сектор оригинального диска командой SEEK, с последующим чтением субканальной информации командой READ SUBCHANALL приводит к тому любопытному эффекту (листинг 6.22): привод не диагностирует ошибку, но и не перемещает оптическую головку, возвращая с командой READ SUBCHANALL субканальные данные с места ее предыдущего позиционирования (выше). Дубликат диска, полученный программой CloneCD, напротив, никак не препятствует позиционированию головки на X-сектор, успешно возвращая субканальные данные последнего (ниже).
Листинг 6.22. Попытка позиционирования на X-сектор оригинального диска командой SEEK, с последующим чтением субканальной информации командой READ SUBCHANALL (выше). Дубликат диска, полученный CloneCD (ниже)Листинг 13 попытка чтения заголовка оригинального диска командой READ HEADER дает ошибку Sense Key == 5 ILLEGAL REQUEST (слева), в то время как дубликат диска, полученный Clone CD обрабатывается нормально
>seek_and_Q.exe 1.1 2056 >seek_and_Q.exe 1.1 2056
seek CD-ROM & read Q-subcode by KK seek CD-ROM & read Q-subcode by KK
00 15 00 0C 01 14 01 01 00 00 05 83 00 00 05 83 00 15 00 0C 01 14 01 01 00 00 08 08 00 00 08 08
>seek_and_Q.exe 1.1 2056
seek CD-ROM & read Q-subcode by KK
00 15 00 0C 01 14 01 01 00 00 08 08 00 00 08 08
Листинг 14 попытка позиционирования на x-сектор оригинального диска командой SEEK, с последующим чтением субканальной информации командой READ SUBCHANALL приводит к тому любопытному эффекту: привод не диагностирует ошибку, но и не перемещает оптическую головку, возвращая с командой READ SUBCHANALL субканальные данные с места ее предыдущего позиционирования (слева). Дубликат диска, полученный Clone CD, напротив, никак не препятствует позиционированию головки на x?сектор, успешно возвращая субканальные данные последнего (справа)
Таким образом, помещение фиктивного трека в область Lead-outLead-Out вкупе с обработкой X-сектора командами READ HEADER и SEEK/READ SUBCHANALL позволяет надежно отличить оригинальный диск от его копии (защита с кодовым наменованием "Волк").
Как скопировать "Волка"? При записи отредактированного образа на диск, Clone CDCloneCD выдаст следующую информацию о его геометрии (листинг 6.23).:
Листинг 6.23. Встретив диск с фиктивный треков в области Post-gap, CloneCD неправильно вычисляет его длину (выделена полужирным шрифтом)
ИНФОРМАЦИЯ О СЕССИИ 1:
Размер сессии: 4726 Кбайт
Число треков: 2
Track 1: Данные Mode 1, размер: 299397 Кбайт
Track 2: Данные Mode 1, размер: 4294672626 Кбайт
Листинг 15 встретив диск с фиктивный треков в post-gap, Clone CD неправильно вычисляет его длину (выделена жирным шрифтом)
По скромному мнению Clone CDCloneCD, длина второго трека составляет целых 4 .294 .672 .626 Ккилобайт или 4 Ттерабайт! К счастью, это ничуть не мешает "прожигу" диска и если все было сделано правильно, то защищенный диск должен нормально обрабатываться операционной системой.
А вот Ahead Nero (рис. 6.13) ( и подавляющее большинство остальных копировщиков) поведет себя иначе, наотрез отказываясь "переваривать" фиктивный трек и аварийно завершая свою работу с криком "Illegal track mode" (неверный тип трека) или что-то типа того.
![](image/fiktivnyj-trek-v-lead-outlead-out_2.gif)
Рис. 6.13. унок 8 0x0342 Информация, выдаваемая Ahead Nero при анализе геометрии защищенного диска, чудовищная ошибка определения длины фиктивного трека препятствует его нормальному копированию
Копировщик CRWin по не совсем понятным причинам отказывается определять тип фиктивного трека (рис. 6.14) (хотя тип трека явно прописан в заголовках каждого из принадлежащему ему секторов) и препятствует его select'у, то есть попросту говоря выделению курсором, без которого трек извлекчаться ни за что не будет. Извлечение первого —– вполне нормального трека —– так же прерывается сообщением об ошибке.
![](image/index-image046.gif)
Рис. 6.14. унок 9 0х343 Копировщик CDRWin отказывается определить тип фиктивного трека
А теперь попытаемся скопировать защищенный диск программой Alcohol 120%Алкоголем. Если галочка флажок "пропуска ошибок" не была заблаговременно установленвзведена, то Alcohol 120%Алкоголик, невнятно "ругнувшись", выведя сообщение типана "Illegal Mode For This Track", просто прервет чтение диска в самом начале снятия образа (листинг 6.24).:
Листинг 6.24. Alcohol 120% без пропуска ошибок
02:40:38 Информация о процессоре: Pentium III (0.18um) 256KB OnDie L2Cache (736MHz)
02:40:38 Дамп диска: (G:) TEAC CD-W552E (1:1)
02:40:46 Режим чтения: Режим RAW
02:40:46 Информация об источнике: Сессия: 2, Трек: 3, Длина: 29.6 MB / 003:22:23
02:40:53 Запись файла-образа: L:\CD-hack\030713_1649.img
02:40:58 Ошибка чтения диска: 2048
02:40:58 Во время дампа диска произошла ошибка!
02:40:58 Ошибка: [05/64/00] - Illegal Mode For This Track
02:40:58 L:\CD-hack\030713_1649.ccd: Запись файла-образа отменена!
Листинг 16 Алкоголь без пропуска ошибок
Хорошо, галочка флажок "Пропуск ошибок чтения"
установленвзведена. Пытаемся скопировать защищенный диск опять. Благополучно достигнув X-сектора (LBA-адрес которого составляет 2056), программа Alcohol 120% Алкоголик выдает сообщение об ошибке чтения и… со всего маху "врезается" в выводную область, обдавая нас "пулеметной очередью" сбойных секторов. Достигнув 100% он по непонятным причинам продолжает процесс чтения и дальше. Затем, видимо испугавшись, что его могут неправильно понять, блокирует кнопку "Отмена"
и… "зависает". То есть, не то, чтобы совсем "зависает" ("пулеметная очередь" сбойных секторов "раскаленными гильзами" по-прежнему продолжает устилать экран), но и остановить разбушевавшегося Alcohol 120% Алкоголика становится невозможно. Приходится его "убивать"… Тоже мне, культура программирования!, блин.
Листинг 6.25. Alcohol 120% с пропуском ошибок (при достижении 100% он "зависает", но продолжает сообщать о сбойных секторах)
09:52:22 Информация о процессоре: Pentium III (0.18 um) 256KB OnDie L2 Cache (736MHz)
Pentium III (0.18 um) 256KB OnDie L2 Cache (736MHz)
09:52:22 Дамп диска: (G:) TEAC CD-W552E (1:1)
09:52:29 Режим чтения: Режим RAW , Пропуск ошибок чтения
09:52:29 Информация об источнике: Сессия: 2, Трек: 3, Длина: 29.6 MB / 003:22:23
09:52:38 Ошибка чтения диска: 0
09:52:39 Запись файла-образа: L:\CD-hack\030713_1649.img
09:52:53 Ошибка чтения диска: 2056
09:52:57 Ошибка чтения диска: 2057
09:52:57 Ошибка чтения диска: 2058
…
09:53:01 Ошибка чтения диска: 2707
09:53:01 L:\CD-hack\030713_1649.ccd: Запись файла-образа отменена!
09:53:01 Дамп диска отменён!
Листинг 17 Алкоголь с пропуском ошибок (при достижении 100% он зависает, но продолжает чесать сбойные сектора)
Третья попытка копирования защищенного диска начинается с перезапуска Alcohol 120% Алкоголика и взведения галочкиустановки флажка "быстрогоый пропуска ошибочных блоков".На этот раз Alcohol 120% Алкоголик уже не "виснет", но… и не копирует, отказывать читать весь диск целиком. По мнению программы Alcohol 120% Алкоголика сбойные сектора начинаются с первого (ну, в смысле нулевого) адреса LBA адреса до последнего. Чудеса, да и только!
Листинг 6.26. Alcohol 120% с быстрым пропуском ошибок
02:52:18 Информация о процессоре: Pentium III (0.18 um) With 256 KB On-Die L2 Cache (736MHz)
Pentium III (0.18 um) With 256 KB On-Die L2 Cache (736MHz)
02:52:18 Дамп диска: (G:) TEAC CD-W552E (1:1)
02:52:25 Режим чтения: Режим RAW , Быстрый пропуск ошибочных блоков
02:52:25 Информация об источнике: Сессия: 2, Трек: 3, Длина: 29.6 MB / 003:22:23
02:52:25 Ошибка чтения диска: 0
02:52:26 Ошибка чтения диска: 1
02:52:26 Ошибка чтения диска: 2
02:52:26 Ошибка чтения диска: 3
Фиктивный трек в области данных подлинного трека
Самое простое (и, с точки зрения совместимости — – самое надежное) это расположить фиктивный трек в области данных подлинного трека, выбрав стартовый адрес фиктивного трека так, чтобы до начала следующего трека оставалось не менее 350 секторов, которыей пойдут на покрытие областей Post-gap/Pre-gap (для последнего трека сессии достаточно и 150 секторов, поскольку здесь не нужно тратиться на Pre-gap). Между стартовым адресом подлинного трека и стартовым адресом фиктивного трека также должно помещаться по меньшей мере 350 секторов, занимаемых постзазором подлинного и предзазором фиктивного трека. Нарушение этого правила приводит к усилению защиты, но и порождает определенные побочные эффекты, о которых мы поговорим позднее (см. разд. "Фиктивный трек в Post-gap подлинного трека" и "Фиктивный трек в Pre-gap подлинного трека" этой главы).
Сейчас же нас больше всего интересует как именно добавить новое Entry в файл IMAGE.CCD файл, не нарушив его работоспособности. Алгоритм создания фиктивного трека, подробно рассмотренный в предыдущеимх главеразделе, все еще представляет собой абстрактную теорию, достаточно далеко отстоящую от практики. В ходе осуществления творческого замысла на нашем пути могут встретиться различные трудности и нам придется "сразиться" с ними, или… просто обойти их.
Давайте рассмотрим самый сложный случай —– многосессионный диск. Правила хорошего тона обязывают нас скорректировать не только содержимое той сессии, к которой добавляется новый трек, но и атрибуты всех последующих сессий, поскольку нумерация треков на диске —– сквозная. Частая ошибка начинающих —– перенумеровать треки. Перенумеровать-то они перенумеруют, а вот переустановить указатели на первый и последний трек каждой сессии —– забывают. Как следствие, этого – защищенный диск перестает читаться совсем или читается неправильно (конкретное поведение зависит от "темперамента" привода на который этот диск попадет).
Конкретный пример добавления нового трека в уже существующий приведен далее в листинге 6.14ниже. По понятным причнам его наглядность оставляет желать лучшего (все таки печатная книга это вам не утилита WinDiff), но это все же лучше, чем совсем ничего. Первая и третья колонки, залитые серым цветом, содержат оригинальные значения редактируемого файла, вторая и четвертая —– измененные. Непосредственно сами изменения выделены полужирным шрифтом.
Листинг 6.14. Создание фиктивного трека – трека номер два; все изменения выделены полужирным шрифтом, значение оригинальных полей залито серым цветом (нечетные колонки)
[CloneCD] |
[CloneCD] |
[Entry 6] |
[Entry 7] |
Version=3 |
Version=3 |
Session=1 |
Session=1 |
Point=0xc1 |
Point=0xc1 |
||
[Disc] |
[Disc] |
ADR=0x05 |
ADR=0x05 |
TocEntries=12 |
TocEntries=13 |
Control=0x04 |
Control=0x04 |
Sessions=2 |
Sessions=2 |
TrackNo=0 |
TrackNo=0 |
DataTracksScrambled=0 |
DataTracksScrambled=0 |
AMin=4 |
AMin=4 |
CDTextLength=0 |
CDTextLength=0 |
ASec=120 |
ASec=120 |
AFrame=96 |
AFrame=96 |
||
[Session 1] |
[Session 1] |
ALBA=26946 |
ALBA=26946 |
PreGapMode=1 |
PreGapMode=1 |
Zero=0 |
Zero=0 |
PreGapSubC=0 |
PreGapSubC=0 |
PMin=0 |
PMin=0 |
PSec=0 |
PSec=0 |
||
[Session 2] |
[Session 2] |
PFrame=0 |
PFrame=0 |
PreGapMode=1 |
PreGapMode=1 |
PLBA=-150 |
PLBA=-150 |
PreGapSubC=0 |
PreGapSubC=0 |
||
[Entry 0] |
[Entry 0] |
[Entry 7] |
[Entry 8] |
Session=1 |
Session=1 |
Session=2 |
Session=2 |
Point=0xa0 |
Point=0xa0 |
Point=0xa0 |
Point=0xa0 |
ADR=0x01 |
ADR=0x01 |
ADR=0x01 |
ADR=0x01 |
Control=0x04 |
Control=0x04 |
Control=0x04 |
Control=0x04 |
TrackNo=0 |
TrackNo=0 |
TrackNo=0 |
TrackNo=0 |
AMin=0 |
AMin=0 |
AMin=0 |
AMin=0 |
ASec=0 |
ASec=0 |
ASec=0 |
ASec=0 |
AFrame=0 |
AFrame=0 |
AFrame=0 |
AFrame=0 |
ALBA=-150 |
ALBA=-150 |
ALBA=-150 |
ALBA=-150 |
Zero=0 |
Zero=0 |
Zero=0 |
Zero=0 |
PMin=1 |
PMin=1 |
PMin=2 |
PMin=3 |
PSec=0 |
PSec=0 |
PSec=0 |
PSec=0 |
PFrame=0 |
PFrame=0 |
PFrame=0 |
PFrame=0 |
PLBA=4350 |
PLBA=4350 |
PLBA=8850 |
PLBA=-1 |
[Entry 0] |
[Entry 0] |
[Entry 8] |
[Entry 9] |
Session=1 |
Session=1 |
Session=2 |
Session=2 |
Point=0xa0 |
Point=0xa0 |
Point=0xa1 |
Point=0xa1 |
ADR=0x01 |
ADR=0x01 |
ADR=0x01 |
ADR=0x01 |
Control=0x04 |
Control=0x04 |
Control=0x04 |
Control=0x04 |
TrackNo=0 |
TrackNo=0 |
TrackNo=0 |
TrackNo=0 |
AMin=0 |
AMin=0 |
AMin=0 |
AMin=0 |
ASec=0 |
ASec=0 |
ASec=0 |
ASec=0 |
AFrame=0 |
AFrame=0 |
AFrame=0 |
AFrame=0 |
ALBA=-150 |
ALBA=-150 |
ALBA=-150 |
ALBA=-150 |
Zero=0 |
Zero=0 |
Zero=0 |
Zero=0 |
PMin=1 |
PMin=1 |
PMin=2 |
PMin=3 |
PSec=0 |
PSec=0 |
PSec=0 |
PSec=0 |
PFrame=0 |
PFrame=0 |
PFrame=0 |
PFrame=0 |
PLBA=4350 |
PLBA=4350 |
PLBA=8850 |
PLBA=-1 |
[Entry 1] |
[Entry 1] |
[Entry 9] |
[Entry 10] |
Session=1 |
Session=1 |
Session=2 |
Session=2 |
Point=0xa1 |
Point=0xa1 |
Point=0xa2 |
Point=0xa2 |
ADR=0x01 |
ADR=0x01 |
ADR=0x01 |
ADR=0x01 |
Control=0x04 |
Control=0x04 |
Control=0x04 |
Control=0x04 |
TrackNo=0 |
TrackNo=0 |
TrackNo=0 |
TrackNo=0 |
AMin=0 |
AMin=0 |
AMin=0 |
AMin=0 |
ASec=0 |
ASec=0 |
ASec=0 |
ASec=0 |
AFrame=0 |
AFrame=0 |
AFrame=0 |
AFrame=0 |
ALBA=-150 |
ALBA=-150 |
ALBA=-150 |
ALBA=-150 |
Zero=0 |
Zero=0 |
Zero=0 |
Zero=0 |
PMin=1 |
PMin=2 |
PMin=3 |
PMin=3 |
PSec=0 |
PSec=0 |
PSec=24 |
PSec=24 |
PFrame=0 |
PFrame=0 |
PFrame=23 |
PFrame=23 |
PLBA=4350 |
PLBA=-1 |
PLBA=15173 |
PLBA=15173 |
[Entry 2] |
[Entry 2] |
[Entry 10] |
[Entry 11] |
Session=1 |
Session=1 |
Session=2 |
Session=2 |
Point=0xa2 |
Point=0xa2 |
Point=0x02 |
Point=0x03 |
ADR=0x01 |
ADR=0x01 |
ADR=0x01 |
ADR=0x01 |
Control=0x04 |
Control=0x04 |
Control=0x04 |
Control=0x04 |
TrackNo=0 |
TrackNo=0 |
TrackNo=0 |
TrackNo=0 |
AMin=0 |
AMin=0 |
AMin=0 |
AMin=0 |
ASec=0 |
ASec=0 |
ASec=0 |
ASec=0 |
AFrame=0 |
AFrame=0 |
AFrame=0 |
AFrame=0 |
ALBA=-150 |
ALBA=-150 |
ALBA=-150 |
ALBA=-150 |
Zero=0 |
Zero=0 |
Zero=0 |
Zero=0 |
PMin=0 |
PMin=0 |
PMin=3 |
PMin=3 |
PSec=29 |
PSec=29 |
PSec=1 |
PSec=1 |
PFrame=33 |
PFrame=33 |
PFrame=33 |
PFrame=33 |
PLBA=2058 |
PLBA=2058 |
PLBA=13458 |
PLBA=13458 |
[Entry 3] |
[Entry 3] |
[Entry 11] |
[Entry 12] |
Session=1 |
Session=1 |
Session=2 |
Session=2 |
Point=0x01 |
Point=0x01 |
Point=0xb0 |
Point=0xb0 |
ADR=0x01 |
ADR=0x01 |
ADR=0x05 |
ADR=0x05 |
Control=0x04 |
Control=0x04 |
Control=0x04 |
Control=0x04 |
TrackNo=0 |
TrackNo=0 |
TrackNo=0 |
TrackNo=0 |
AMin=0 |
AMin=0 |
AMin=4 |
AMin=4 |
ASec=0 |
ASec=0 |
ASec=54 |
ASec=54 |
AFrame=0 |
AFrame=0 |
AFrame=23 |
AFrame=23 |
ALBA=-150 |
ALBA=-150 |
ALBA=21923 |
ALBA=21923 |
Zero=0 |
Zero=0 |
Zero=1 |
Zero=1 |
PMin=0 |
PMin=0 |
PMin=22 |
PMin=22 |
PSec=2 |
PSec=2 |
PSec=14 |
PSec=14 |
PFrame=0 |
PFrame=0 |
PFrame=34 |
PFrame=34 |
PLBA=0 |
PLBA=0 |
PLBA=99934 |
PLBA=99934 |
[Entry 4] |
[Entry 5] |
[Entry 6] |
|
Session=1 |
Session=1 |
Session=1 |
|
Point=0x02 |
Point=0xc0 |
Point=0xc0 |
|
ADR=0x01 |
ADR=0x05 |
ADR=0x05 |
|
Control=0x04 |
Control=0x04 |
Control=0x04 |
|
TrackNo=0 |
TrackNo=0 |
TrackNo=0 |
|
AMin=0 |
AMin=162 |
TrackNo=0 |
|
ASec=0 |
ASec=128 |
ASec=128 |
|
AFrame=0 |
AFrame=140 |
AFrame=140 |
|
ALBA=-150 |
ALBA=288590 |
ALBA=288590 |
|
Zero=0 |
Zero=0 |
Zero=0 |
|
PMin=22 |
PMin=97 |
PMin=97 |
|
PSec=0 |
PSec=27 |
PSec=27 |
|
PFrame=0 |
PFrame=21 |
PFrame=21 |
|
PLBA=-1 |
PLBA=-11604 |
PLBA=-11604 |
|
[Entry 4] |
[Entry 5] |
[TRACK 1] |
[TRACK 1] |
Session=1 |
Session=1 |
MODE=1 |
MODE=1 |
Point=0xb0 |
Point=0xb0 |
INDEX 1=0 |
INDEX 1=0 |
ADR=0x05 |
ADR=0x05 |
||
Control=0x04 |
Control=0x04 |
[TRACK 2] |
|
TrackNo=0 |
TrackNo=0 |
MODE=1 |
|
AMin=2 |
AMin=2 |
INDEX 1=0 |
|
ASec=59 |
ASec=59 |
||
AFrame=33 |
AFrame=33 |
[TRACK 2] |
[TRACK 3] |
ALBA=13308 |
ALBA=13308 |
MODE=1 |
MODE=1 |
Zero=3 |
Zero=3 |
INDEX 1=0 |
INDEX 1=0 |
PMin=22 |
PMin=22 |
||
PSec=14 |
PSec=14 |
||
PFrame=34 |
PFrame=34 |
||
PLBA=99934 |
PLBA=99934 |
Листинг 6 создание фиктивного трека – трека номер два; все изменения выделены жирным шрифтом, значение оригинальных полей залито серым цветом (нечетные колонки)
Сохранив отредактированный файл IMAGE.CCD на диск, "залейте" полученный образ на болванку с помощью Clone CDCloneCD или Alcohol 120%. Убедитесь, что защищенный диск нормально обрабатывается операционной системой и что к двум прежним трекам диска добавился еще один (рис. 6.8).
![](image/fiktivnyj-trek-v-oblasti-dannyh-podlinnogo-treka_1.gif)
Рис. 6.8.унок 3 0x071 В первой сессии находятся два трека, первый нормальный, второй — "рукотворный" фиктивный
Попытка копирования защищенного диска штатными копировщиками (такими например, как Stomp Record Now! или Ahead Nero) как будто бы проходит успешно, но при ближайшем рассмотрении между первым и вторым треком обнаруживается "дыра" из 300 секторов, заполненных нулями. Что ж! Этого следовало ожидать! Штатные копировщики слишком буквально понимают тот пункт стандарта, что утверждает будто бы области Post-gap и Pre-gap области не содержат никаких данных.
Изменяется и субканальная информация. Копировщики, не выполняющие фактического чтения Q-канала подкода копируемого диска и самостоятельно воссоздающие его с нуля, исправно маркируют субканальные данные порядковыми номерами соответствующих им треков, что позволяет легко отличить оригинал от его грубой копии.
Листинг 6.15. Демонстрация изменения субканальной информации при копировании диска
# читаем TOC защищенного диска для определения стартового адреса 2го трека
$toc TEAC 0
00 14 01 00 00 00 00 00
00 14 02 00 00 00 02 A3 # стартовыйй адрес 2го трека =равен 2A3h или 675 в десят. нотац.
00 14 03 00 00 00 34 92
00 14 AA 00 00 00 3B 45
# читаем субканальные данные из 2го трека защищенного дика
$seek_and_Q TEAC 675
seek CD-ROM & read Q-subcode by KK
LBA - 02A3: 00 15 00 0C 01 14 01 01 00 00 02 A3 00 00 02 A3
# поле TNO 2го трека содержит ^^ номер один
# копируем диск Ahead Nero/Easy CD Creator/Record Now!
# или Clone CD/Alcohol 120% Алкоголем без чтения субканальной информации
# читаем субканальные данные из 2го трека дика-копии
$seek_and_Q TEAC 675
seek CD-ROM & read Q-subcode by KK
LBA - 02A3: 00 15 00 0C 01 14 02 01 00 00 02 A3 00 00 00 00
# поле TNO 2го трека содержит ^^ номер два
# субканальная информация изменилась!
Листинг 7 демонстрация изменения субканальной информации при копировании диска
Забавно, но MP3--файл, расположенный на оригинальном диске, даже будучи жестоко продырявленнымповрежденным[n2k171] , воспроизводится вполне нормально, негромко "булькая" в месте своего "ранения", что конечно неприятно, но все таки не смертельно. Конечно, чем больше фиктивных треков содержит защищенный диск и чем теснее эти треки расположены друг к другу, – тем сильнее несанкционированный дубликат будет отличаться от оригинала. Копия диска, "нашпигованного" фиктивными треками "по самую макушку" уже не играет, а свистит и булькает, вызывая у доморощенных пиратов смесь злости с недоумением. Вот так и рождаются легенды о неизбежном падении качества при копировании лазерных дисков. Разумеется, речь идет исключительно о дисках с формата [n2k172] MP3- или видеодисках. Диски с данными гораздо более ранимы и если "дыра", оставленная копировщиком попадет на исполняемый файл и/или архив, с вероятностью близкой к единице он окажется полностью выведеным из строя.
Впрочем, стойкость защиты данного типа очень и очень невелика и такой диск вполне успешно копируется копировщиками Alcohol 120% Алкоголь и Clone CDCloneCD (конечно, при условии, что опция чтения субканальных данных включена), которые судя по всему вообще игнорируют стартовые адреса треков и читают всю читабельную область каждой сессии от конца Lead-inLead-In до начала Lead-outLead-Out целиком. Что ж! Тем хуже для тех, кто применяет эту защиту для затруднения копирования своих программ.Что же касается хваленого Blind Write, то он вообще не копирует такой диск, вылетая по исключению (exceptionэкскепшену) в читающем устройстве,[n2k173] (цитирую,) "read engine". Чем ему не понравился фиктивный трек —– загадка!?.
Фиктивный трек в Post-gap подлинного трека
Засунуть Разместить фиктивный трек в серединеу настоящего (как это и показано на рис.6.80x074) —– неинтересно. Лучше разместить фиктивный трек целиком в области Post-gap области подлинного трека. Тогда, при попытке вычисления длины фиктивного трека, все копировщики сойдут с ума. Вспомним, что длина всякого нормального трека по стандарту равна: min(&Lead-?Out, &NexTrack – 150) – &MyTrack – 150. Если расположить начало трека так, чтобы min(&Lead-?Out, &NexTrack – 150) < (&MyTrack – 150), то – его расчетная длина окажется отрицательной и многие из копировщиков просто не поймут что с таким треком вообще следует делать. К тому же, большинство копировщиков хранят длину треков в переменных типа unsigned long, а потому небольшое по модулю отрицательное число, ошибочно интерпретируемое процессором как беззнаковое, превращается в очччень и очень большое положительное, и для записи "содержимого" фиктивного трека потребуется около четырех4 Ггигабайт свободного места на диске и еще столько же —– на "прожигаемой" болванке.
Обратимся к point'ууказателю A2h, хранящему адрес выводной области, и скопируем принадлежащие ему PMin, PSec и PFrame в соответствующие поля фиктивного сектора, уменьшив PFrame последнего на некоторое значение (о том, как создаются фиктивные сектора, мы уже говорили, см. двае предыдущих раздела этой главыглавы).